/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 |