Coverage Report

Created: 2025-07-11 06:34

/src/libass/libass/ass_filesystem.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (C) 2021 libass contributors
3
 *
4
 * This file is part of libass.
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include "config.h"
20
#include "ass_compat.h"
21
22
#include "ass_filesystem.h"
23
#include "ass_utils.h"
24
25
26
static inline bool check_add_size(size_t *size, size_t amount)
27
0
{
28
0
    size_t res = *size + amount;
29
0
    if (res < amount)
30
0
        return false;
31
0
    *size = res;
32
0
    return true;
33
0
}
34
35
0
#define NAME_BUF_SIZE  256
36
// NAME_BUF_SIZE + 2 <= SIZE_MAX
37
38
static bool alloc_path(ASS_Dir *dir, size_t size)
39
0
{
40
0
    if (size <= dir->max_path)
41
0
        return true;
42
0
    if (!check_add_size(&size, NAME_BUF_SIZE))
43
0
        return false;
44
0
    char *path = realloc(dir->path, size);
45
0
    if (!path)
46
0
        return false;
47
0
    dir->path = path;
48
0
    dir->max_path = size;
49
0
    return true;
50
0
}
51
52
53
#if !defined(_WIN32) || defined(__CYGWIN__)
54
55
#include <dirent.h>
56
57
FILE *ass_open_file(const char *filename, FileNameSource hint)
58
0
{
59
0
    return fopen(filename, "rb");
60
0
}
61
62
bool ass_open_dir(ASS_Dir *dir, const char *path)
63
0
{
64
0
    dir->handle = NULL;
65
0
    dir->path = NULL;
66
0
    dir->name = NULL;
67
68
0
    size_t len = strlen(path);
69
0
    if (len && path[len - 1] == '/')
70
0
        len--;
71
72
0
    size_t size = NAME_BUF_SIZE + 2;
73
0
    if (!check_add_size(&size, len))
74
0
        return false;
75
0
    dir->path = malloc(size);
76
0
    if (!dir->path)
77
0
        return false;
78
0
    dir->max_path = size;
79
0
    memcpy(dir->path, path, len);
80
0
    dir->path[len] = '/';
81
0
    dir->prefix = len + 1;
82
83
0
    dir->handle = opendir(path);
84
0
    if (dir->handle)
85
0
        return true;
86
87
0
    free(dir->path);
88
0
    dir->path = NULL;
89
0
    return false;
90
0
}
91
92
const char *ass_read_dir(ASS_Dir *dir)
93
0
{
94
0
    struct dirent *entry = readdir(dir->handle);
95
0
    return dir->name = entry ? entry->d_name : NULL;
96
0
}
97
98
const char *ass_current_file_path(ASS_Dir *dir)
99
0
{
100
0
    size_t size = dir->prefix + 1, len = strlen(dir->name);
101
0
    if (!check_add_size(&size, len) || !alloc_path(dir, size))
102
0
        return NULL;
103
0
    memcpy(dir->path + dir->prefix, dir->name, len + 1);
104
0
    return dir->path;
105
0
}
106
107
void ass_close_dir(ASS_Dir *dir)
108
0
{
109
0
    if (dir->handle)
110
0
        closedir(dir->handle);
111
0
    free(dir->path);
112
0
    dir->handle = NULL;
113
0
    dir->path = NULL;
114
0
    dir->name = NULL;
115
0
}
116
117
118
#else  // Windows
119
120
#include <windows.h>
121
122
123
static const uint8_t wtf8_len_table[256] = {
124
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 0x
125
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 1x
126
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 2x
127
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 3x
128
129
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 4x
130
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 5x
131
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 6x
132
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 7x
133
134
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 8x
135
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 9x
136
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // Ax
137
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // Bx
138
139
    0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,  // Cx
140
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,  // Dx
141
    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,  // Ex
142
    4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // Fx
143
};
144
145
static const uint8_t wtf8_len4_range[5][2] = {
146
    { 0x90, 0x30 }, { 0x80, 0x40 }, { 0x80, 0x40 }, { 0x80, 0x40 }, { 0x80, 0x10 }
147
};
148
149
static inline bool check_add_size_wtf8to16(size_t *size, size_t len)
150
{
151
    return len > SIZE_MAX / sizeof(WCHAR) ? false : check_add_size(size, len * sizeof(WCHAR));
152
}
153
154
// does not append zero termination implicitly
155
// expects preallocated buffer (use check_add_size_wtf8to16() for size)
156
static WCHAR *convert_wtf8to16(WCHAR *dst, ASS_StringView src)
157
{
158
    const char *str = src.str;
159
    for (const char *end = str + src.len; str < end; str++) {
160
        uint8_t ch = *str;
161
        switch (wtf8_len_table[ch]) {
162
        case 1:  // 1 -> 1w
163
            *dst++ = ch;
164
            continue;
165
166
        case 2:  // 2 -> 1w
167
            if (str + 1 < end) {
168
                uint8_t next = *++str;
169
                if ((next & 0xC0) != 0x80)
170
                    return NULL;
171
                *dst++ = (ch & 0x1F) << 6 | (next & 0x3F);
172
                continue;
173
            }
174
            return NULL;
175
176
        case 3:  // 3 -> 1w
177
            if (str + 2 < end) {
178
                ch &= 0xF;
179
                uint8_t next1 = *++str, next2 = *++str;
180
                if (next1 < (ch ? 0x80 : 0xA0) || next1 >= 0xC0 || (next2 & 0xC0) != 0x80)
181
                    return NULL;
182
                *dst++ = ch << 12 | (next1 & 0x3F) << 6 | (next2 & 0x3F);
183
                continue;
184
            }
185
            return NULL;
186
187
        case 4:  // 4 -> 2w
188
            if (str + 3 < end) {
189
                ch &= 0x7;
190
                uint8_t next1 = *++str, next2 = *++str, next3 = *++str;
191
                if ((uint8_t) (next1 - wtf8_len4_range[ch][0]) >= wtf8_len4_range[ch][1] ||
192
                    (next2 & 0xC0) != 0x80 || (next3 & 0xC0) != 0x80)
193
                    return NULL;
194
                *dst++ = 0xD800 | ((ch << 8 | (next1 & 0x3F) << 2) - 0x40) | (next2 & 0x3F) >> 4;
195
                *dst++ = 0xDC00 | (next2 & 0xF) << 6 | (next3 & 0x3F);
196
                continue;
197
            }
198
            return NULL;
199
200
        default:
201
            return NULL;
202
        }
203
    }
204
    return dst;
205
}
206
207
static inline bool check_add_size_wtf16to8(size_t *size, size_t wlen)
208
{
209
    enum { max_bytes_per_wchar = 3 };
210
    return wlen > SIZE_MAX / max_bytes_per_wchar ? false :
211
        check_add_size(size, max_bytes_per_wchar * wlen);
212
}
213
214
// does not append zero termination implicitly
215
// expects preallocated buffer (use check_add_size_wtf16to8() for size)
216
static char *convert_wtf16to8(char *dst, const WCHAR *src, size_t wlen)
217
{
218
    for (const WCHAR *end = src + wlen; src < end; src++) {
219
        uint16_t wch = *src;
220
        if (wch < 0x80) {
221
            // 1w -> 1
222
            *dst++ = wch;
223
            continue;
224
        }
225
        if (wch < 0x800) {
226
            // 1w -> 2
227
            *dst++ = 0xC0 | wch >> 6;
228
            *dst++ = 0x80 | (wch & 0x3F);
229
            continue;
230
        }
231
        if (wch >= 0xD800 && wch < 0xDC00 && src + 1 < end) {
232
            uint16_t next = src[1];
233
            if (next >= 0xDC00 && next < 0xE000) {  // correctly paired surrogates
234
                src++;  // 2w -> 4
235
                uint32_t full = (uint32_t) ((wch & 0x3FF) + 0x40) << 10 | (next & 0x3FF);
236
                *dst++ = 0xF0 | full >> 18;
237
                *dst++ = 0x80 | ((full >> 12) & 0x3F);
238
                *dst++ = 0x80 | ((full >> 6) & 0x3F);
239
                *dst++ = 0x80 | (full & 0x3F);
240
                continue;
241
            }
242
        }
243
        // unpaired surrogates fall through here
244
        // 1w -> 3
245
        *dst++ = 0xE0 | wch >> 12;
246
        *dst++ = 0x80 | ((wch >> 6) & 0x3F);
247
        *dst++ = 0x80 | (wch & 0x3F);
248
    }
249
    return dst;
250
}
251
252
static FILE *open_file_wtf8(const char *filename)
253
{
254
    size_t size = sizeof(WCHAR);
255
    ASS_StringView name = { filename, strlen(filename) };
256
    if (!check_add_size_wtf8to16(&size, name.len))
257
        return NULL;
258
    WCHAR *wname = malloc(size);
259
    if (!wname)
260
        return NULL;
261
    WCHAR *end = convert_wtf8to16(wname, name);
262
    FILE *fp = NULL;
263
    if (end) {
264
        *end = L'\0';
265
        fp = _wfopen(wname, L"rb");
266
    }
267
    free(wname);
268
    return fp;
269
}
270
271
FILE *ass_open_file(const char *filename, FileNameSource hint)
272
{
273
    FILE *fp = open_file_wtf8(filename);
274
    if (fp || hint == FN_DIR_LIST)
275
        return fp;
276
    return fopen(filename, "rb");
277
}
278
279
280
static const WCHAR dir_tail[] = L"\\*";
281
282
static bool append_tail(WCHAR *wpath, size_t wlen)
283
{
284
    size_t offs = 0;
285
    if (wlen == 2 && wpath[1] == L':')
286
        offs = 1;
287
    else if (wlen && (wpath[wlen - 1] == L'/' || wpath[wlen - 1] == L'\\'))
288
        offs = 1;
289
    memcpy(wpath + wlen, dir_tail + offs, sizeof(dir_tail) - offs * sizeof(dir_tail[0]));
290
    return !offs;
291
}
292
293
static bool open_dir_wtf8(ASS_Dir *dir, ASS_StringView path)
294
{
295
    assert(dir->handle == INVALID_HANDLE_VALUE && !dir->path);
296
297
    size_t size = sizeof(dir_tail);
298
    if (!check_add_size_wtf8to16(&size, path.len))
299
        return false;
300
301
    WCHAR *wpath = malloc(size);
302
    if (!wpath)
303
        return false;
304
305
    WIN32_FIND_DATAW data;
306
    WCHAR *end = convert_wtf8to16(wpath, path);
307
    if (!end) {
308
        free(wpath);
309
        return false;
310
    }
311
312
    bool add_separator = append_tail(wpath, end - wpath);
313
    dir->handle = FindFirstFileExW(wpath, FindExInfoBasic, &data, FindExSearchNameMatch, NULL, 0);
314
315
    free(wpath);
316
    if (dir->handle == INVALID_HANDLE_VALUE)
317
        return false;
318
319
    size = NAME_BUF_SIZE + 2;
320
    size_t wlen = wcslen(data.cFileName);
321
    if (!check_add_size(&size, path.len) || !check_add_size_wtf16to8(&size, wlen) ||
322
            !(dir->path = malloc(size))) {
323
        FindClose(dir->handle);
324
        return false;
325
    }
326
    dir->max_path = size;
327
    memcpy(dir->path, path.str, path.len);
328
    if (add_separator)
329
        dir->path[path.len++] = '\\';
330
    *convert_wtf16to8(dir->path + path.len, data.cFileName, wlen) = '\0';
331
    dir->prefix = path.len;
332
    return true;
333
}
334
335
bool ass_open_dir(ASS_Dir *dir, const char *path)
336
{
337
    dir->handle = INVALID_HANDLE_VALUE;
338
    dir->path = NULL;
339
    dir->name = NULL;
340
341
    size_t len = strlen(path);
342
    if (open_dir_wtf8(dir, (ASS_StringView) { path, len }))
343
        return true;
344
345
    if (len > INT_MAX)
346
        return false;
347
348
    UINT cp = CP_ACP;
349
#if ASS_WINAPI_DESKTOP
350
    if (!AreFileApisANSI())
351
        cp = CP_OEMCP;
352
#endif
353
    size_t wlen = MultiByteToWideChar(cp, 0, path, len, NULL, 0);
354
    if (wlen > (SIZE_MAX - sizeof(dir_tail)) / sizeof(WCHAR))
355
        return false;
356
    WCHAR *wpath = malloc(wlen * sizeof(WCHAR) + sizeof(dir_tail));
357
    if (!wpath)
358
        return false;
359
    MultiByteToWideChar(cp, 0, path, len, wpath, wlen);
360
    bool add_separator = append_tail(wpath, wlen);
361
362
    WIN32_FIND_DATAW data;
363
    dir->handle = FindFirstFileExW(wpath, FindExInfoBasic, &data, FindExSearchNameMatch, NULL, 0);
364
    if (dir->handle == INVALID_HANDLE_VALUE) {
365
        free(wpath);
366
        return false;
367
    }
368
    size_t size = NAME_BUF_SIZE + 2, wlen1 = wcslen(data.cFileName);
369
    if (!check_add_size_wtf16to8(&size, wlen) || !check_add_size_wtf16to8(&size, wlen1) ||
370
            !(dir->path = malloc(size))) {
371
        FindClose(dir->handle);
372
        return false;
373
    }
374
    dir->max_path = size;
375
    char *ptr = convert_wtf16to8(dir->path, wpath, wlen);
376
    if (add_separator)
377
        *ptr++ = '\\';
378
    convert_wtf16to8(ptr, data.cFileName, wlen1 + 1);
379
    dir->prefix = ptr - dir->path;
380
    return true;
381
}
382
383
const char *ass_read_dir(ASS_Dir *dir)
384
{
385
    if (!dir->name)  // first invocation
386
        return dir->name = dir->path + dir->prefix;
387
388
    WIN32_FIND_DATAW data;
389
    while (FindNextFileW(dir->handle, &data)) {
390
        size_t size = dir->prefix + 1, wlen = wcslen(data.cFileName);
391
        if (!check_add_size_wtf16to8(&size, wlen) || !alloc_path(dir, size))
392
            continue;
393
        convert_wtf16to8(dir->path + dir->prefix, data.cFileName, wlen + 1);
394
        return dir->name = dir->path + dir->prefix;
395
    }
396
    return NULL;
397
}
398
399
const char *ass_current_file_path(ASS_Dir *dir)
400
{
401
    return dir->path;
402
}
403
404
void ass_close_dir(ASS_Dir *dir)
405
{
406
    if (dir->handle != INVALID_HANDLE_VALUE)
407
        FindClose(dir->handle);
408
    free(dir->path);
409
    dir->handle = INVALID_HANDLE_VALUE;
410
    dir->path = NULL;
411
    dir->name = NULL;
412
}
413
414
#endif  // Windows