/src/mupdf/source/fitz/directory.c
Line | Count | Source |
1 | | // Copyright (C) 2004-2026 Artifex Software, Inc. |
2 | | // |
3 | | // This file is part of MuPDF. |
4 | | // |
5 | | // MuPDF is free software: you can redistribute it and/or modify it under the |
6 | | // terms of the GNU Affero General Public License as published by the Free |
7 | | // Software Foundation, either version 3 of the License, or (at your option) |
8 | | // any later version. |
9 | | // |
10 | | // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY |
11 | | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
12 | | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more |
13 | | // details. |
14 | | // |
15 | | // You should have received a copy of the GNU Affero General Public License |
16 | | // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> |
17 | | // |
18 | | // Alternative licensing terms are available from the licensor. |
19 | | // For commercial licensing, see <https://www.artifex.com/> or contact |
20 | | // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, |
21 | | // CA 94129, USA, for further information. |
22 | | |
23 | | #include "mupdf/fitz.h" |
24 | | |
25 | | #include <string.h> |
26 | | #include <errno.h> |
27 | | #include <sys/stat.h> |
28 | | |
29 | | #ifdef _WIN32 |
30 | | #include <windows.h> |
31 | | #include <errno.h> |
32 | | #define stat _stat |
33 | | #else |
34 | | #include <dirent.h> |
35 | | #endif |
36 | | |
37 | | typedef struct |
38 | | { |
39 | | fz_archive super; |
40 | | |
41 | | char *path; |
42 | | |
43 | | int max_entries; |
44 | | int num_entries; |
45 | | char **entries; |
46 | | } fz_directory; |
47 | | |
48 | | static void drop_directory(fz_context *ctx, fz_archive *arch) |
49 | 0 | { |
50 | 0 | fz_directory *dir = (fz_directory *) arch; |
51 | 0 | int i; |
52 | |
|
53 | 0 | fz_free(ctx, dir->path); |
54 | 0 | for (i = 0; i < dir->num_entries; i++) |
55 | 0 | fz_free(ctx, dir->entries[i]); |
56 | 0 | fz_free(ctx, dir->entries); |
57 | 0 | } |
58 | | |
59 | | static void make_dir_path(char *output, fz_archive *arch, const char *tail, size_t size) |
60 | 0 | { |
61 | | /* Skip any leading ../ path segments, so we don't look outside the |
62 | | * directory itself. The paths coming here have already been |
63 | | * canonicalized with fz_cleanname so any remaining ".." parts are |
64 | | * guaranteed to be at the start of the path. |
65 | | */ |
66 | 0 | fz_directory *dir = (fz_directory *) arch; |
67 | 0 | while (tail[0] == '.' && tail[1] == '.' && tail[2] == '/') |
68 | 0 | tail += 3; |
69 | 0 | fz_strlcpy(output, dir->path, size); |
70 | 0 | fz_strlcat(output, "/", size); |
71 | 0 | fz_strlcat(output, tail, size); |
72 | 0 | } |
73 | | |
74 | | static fz_stream *open_dir_entry(fz_context *ctx, fz_archive *arch, const char *name) |
75 | 0 | { |
76 | 0 | char path[PATH_MAX]; |
77 | 0 | make_dir_path(path, arch, name, sizeof path); |
78 | 0 | return fz_try_open_file(ctx, path); |
79 | 0 | } |
80 | | |
81 | | static fz_buffer *read_dir_entry(fz_context *ctx, fz_archive *arch, const char *name) |
82 | 0 | { |
83 | 0 | char path[PATH_MAX]; |
84 | 0 | make_dir_path(path, arch, name, sizeof path); |
85 | 0 | return fz_try_read_file(ctx, path); |
86 | 0 | } |
87 | | |
88 | | static int has_dir_entry(fz_context *ctx, fz_archive *arch, const char *name) |
89 | 0 | { |
90 | 0 | char path[PATH_MAX]; |
91 | 0 | make_dir_path(path, arch, name, sizeof path); |
92 | 0 | return fz_file_exists(ctx, path); |
93 | 0 | } |
94 | | |
95 | | int |
96 | | fz_is_directory(fz_context *ctx, const char *path) |
97 | 0 | { |
98 | | #ifdef _WIN32 |
99 | | wchar_t *wpath = fz_wchar_from_utf8(ctx, path); |
100 | | struct stat info; |
101 | | int ret; |
102 | | |
103 | | ret = _wstat(wpath, &info); |
104 | | fz_free(ctx, wpath); |
105 | | if (ret < 0) |
106 | | return 0; |
107 | | |
108 | | return S_ISDIR(info.st_mode); |
109 | | #else |
110 | 0 | struct stat info; |
111 | |
|
112 | 0 | if (stat(path, &info) < 0) |
113 | 0 | return 0; |
114 | | |
115 | 0 | return S_ISDIR(info.st_mode); |
116 | 0 | #endif |
117 | 0 | } |
118 | | |
119 | | static int |
120 | | count_dir_entries(fz_context *ctx, fz_archive *arch) |
121 | 0 | { |
122 | 0 | fz_directory *dir = (fz_directory *) arch; |
123 | |
|
124 | 0 | return dir->num_entries; |
125 | 0 | } |
126 | | |
127 | | static const char * |
128 | | list_dir_entry(fz_context *ctx, fz_archive *arch, int n) |
129 | 0 | { |
130 | 0 | fz_directory *dir = (fz_directory *) arch; |
131 | |
|
132 | 0 | if (n < 0 || n >= dir->num_entries) |
133 | 0 | return NULL; |
134 | | |
135 | 0 | return dir->entries[n]; |
136 | 0 | } |
137 | | |
138 | | fz_archive * |
139 | | fz_open_directory(fz_context *ctx, const char *path) |
140 | 0 | { |
141 | 0 | fz_directory *dir; |
142 | | #ifdef _WIN32 |
143 | | WCHAR *wpath = NULL; |
144 | | size_t z = 3; |
145 | | HANDLE h = NULL; |
146 | | WIN32_FIND_DATAW dw; |
147 | | |
148 | | fz_var(wpath); |
149 | | fz_var(h); |
150 | | #else |
151 | 0 | DIR *dp = NULL; |
152 | 0 | struct dirent *ep; |
153 | |
|
154 | 0 | fz_var(dp); |
155 | 0 | #endif |
156 | |
|
157 | 0 | if (!fz_is_directory(ctx, path)) |
158 | 0 | fz_throw(ctx, FZ_ERROR_FORMAT, "'%s' is not a directory", path); |
159 | | |
160 | 0 | dir = fz_new_derived_archive(ctx, NULL, fz_directory); |
161 | 0 | dir->super.format = "dir"; |
162 | 0 | dir->super.count_entries = count_dir_entries; |
163 | 0 | dir->super.list_entry = list_dir_entry; |
164 | 0 | dir->super.has_entry = has_dir_entry; |
165 | 0 | dir->super.read_entry = read_dir_entry; |
166 | 0 | dir->super.open_entry = open_dir_entry; |
167 | 0 | dir->super.drop_archive = drop_directory; |
168 | |
|
169 | 0 | fz_try(ctx) |
170 | 0 | { |
171 | | #ifdef _WIN32 |
172 | | char const *p = path; |
173 | | WCHAR *w; |
174 | | while (*p) |
175 | | { |
176 | | int rune; |
177 | | p += fz_chartorune(&rune, p); |
178 | | if (rune >= 0x10000 || rune < 0) |
179 | | { |
180 | | errno = EINVAL; |
181 | | fz_throw(ctx, FZ_ERROR_SYSTEM, "Unrepresentable UTF-8 char in directory name"); |
182 | | } |
183 | | z++; |
184 | | } |
185 | | w = wpath = fz_malloc(ctx, z * sizeof(WCHAR)); |
186 | | p = path; |
187 | | while (*p) |
188 | | { |
189 | | int rune; |
190 | | p += fz_chartorune(&rune, p); |
191 | | *w++ = rune; |
192 | | } |
193 | | w[0] = '\\'; |
194 | | w[1] = '*'; |
195 | | w[2] = 0; |
196 | | |
197 | | /* Now enumerate the paths. */ |
198 | | h = FindFirstFileW(wpath, &dw); |
199 | | if (h == INVALID_HANDLE_VALUE) |
200 | | break; |
201 | | |
202 | | do |
203 | | { |
204 | | char *u; |
205 | | |
206 | | if (dir->max_entries == dir->num_entries) |
207 | | { |
208 | | int newmax = dir->max_entries * 2; |
209 | | if (newmax == 0) |
210 | | newmax = 32; |
211 | | |
212 | | dir->entries = fz_realloc(ctx, dir->entries, sizeof(*dir->entries) * newmax); |
213 | | dir->max_entries = newmax; |
214 | | } |
215 | | |
216 | | /* Count the len as utf-8. */ |
217 | | w = dw.cFileName; |
218 | | z = 1; |
219 | | while (*w) |
220 | | z += fz_runelen(*w++); |
221 | | |
222 | | u = dir->entries[dir->num_entries] = fz_malloc(ctx, z); |
223 | | dir->num_entries++; |
224 | | |
225 | | /* Copy the name across. */ |
226 | | w = dw.cFileName; |
227 | | while (*w) |
228 | | u += fz_runetochar(u, *w++); |
229 | | *u = 0; |
230 | | } |
231 | | while (FindNextFileW(h, &dw)); |
232 | | #else |
233 | 0 | dp = opendir(path); |
234 | 0 | if (dp == NULL) |
235 | 0 | break; |
236 | | |
237 | 0 | while ((ep = readdir(dp)) != NULL) |
238 | 0 | { |
239 | 0 | if (dir->max_entries == dir->num_entries) |
240 | 0 | { |
241 | 0 | int newmax = dir->max_entries * 2; |
242 | 0 | if (newmax == 0) |
243 | 0 | newmax = 32; |
244 | |
|
245 | 0 | dir->entries = fz_realloc(ctx, dir->entries, sizeof(*dir->entries) * newmax); |
246 | 0 | dir->max_entries = newmax; |
247 | 0 | } |
248 | |
|
249 | 0 | dir->entries[dir->num_entries] = fz_strdup(ctx, ep->d_name); |
250 | 0 | dir->num_entries++; |
251 | 0 | } |
252 | 0 | #endif |
253 | 0 | dir->path = fz_strdup(ctx, path); |
254 | 0 | } |
255 | 0 | fz_always(ctx) |
256 | 0 | { |
257 | | #ifdef _WIN32 |
258 | | fz_free(ctx, wpath); |
259 | | if (h) |
260 | | (void)FindClose(h); |
261 | | #else |
262 | 0 | if (dp) |
263 | 0 | (void)closedir(dp); |
264 | 0 | #endif |
265 | 0 | } |
266 | 0 | fz_catch(ctx) |
267 | 0 | { |
268 | 0 | fz_drop_archive(ctx, &dir->super); |
269 | 0 | fz_rethrow(ctx); |
270 | 0 | } |
271 | | |
272 | 0 | return &dir->super; |
273 | 0 | } |