/src/poppler/goo/gfile.cc
Line | Count | Source |
1 | | //======================================================================== |
2 | | // |
3 | | // gfile.cc |
4 | | // |
5 | | // Miscellaneous file and directory name manipulation. |
6 | | // |
7 | | // Copyright 1996-2003 Glyph & Cog, LLC |
8 | | // |
9 | | //======================================================================== |
10 | | |
11 | | //======================================================================== |
12 | | // |
13 | | // Modified under the Poppler project - http://poppler.freedesktop.org |
14 | | // |
15 | | // All changes made under the Poppler project to this file are licensed |
16 | | // under GPL version 2 or later |
17 | | // |
18 | | // Copyright (C) 2006 Takashi Iwai <tiwai@suse.de> |
19 | | // Copyright (C) 2006 Kristian Høgsberg <krh@redhat.com> |
20 | | // Copyright (C) 2008 Adam Batkin <adam@batkin.net> |
21 | | // Copyright (C) 2008, 2010, 2012, 2013 Hib Eris <hib@hiberis.nl> |
22 | | // Copyright (C) 2009, 2012, 2014, 2017, 2018, 2021, 2022, 2024, 2025 Albert Astals Cid <aacid@kde.org> |
23 | | // Copyright (C) 2009 Kovid Goyal <kovid@kovidgoyal.net> |
24 | | // Copyright (C) 2013, 2018 Adam Reichold <adamreichold@myopera.com> |
25 | | // Copyright (C) 2013, 2017 Adrian Johnson <ajohnson@redneon.com> |
26 | | // Copyright (C) 2013 Peter Breitenlohner <peb@mppmu.mpg.de> |
27 | | // Copyright (C) 2013, 2017 Thomas Freitag <Thomas.Freitag@alfa.de> |
28 | | // Copyright (C) 2017 Christoph Cullmann <cullmann@kde.org> |
29 | | // Copyright (C) 2018 Mojca Miklavec <mojca@macports.org> |
30 | | // Copyright (C) 2019, 2021 Christian Persch <chpe@src.gnome.org> |
31 | | // Copyright (C) 2025, 2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk> |
32 | | // Copyright (C) 2026 Kleis Auke Wolthuizen <github@kleisauke.nl> |
33 | | // |
34 | | // To see a description of the changes please see the Changelog file that |
35 | | // came with your tarball or type make ChangeLog if you are building from git |
36 | | // |
37 | | //======================================================================== |
38 | | |
39 | | #include <config.h> |
40 | | |
41 | | #ifndef _WIN32 |
42 | | # include <sys/types.h> |
43 | | # include <sys/stat.h> |
44 | | # include <fcntl.h> |
45 | | # include <cstring> |
46 | | # include <pwd.h> |
47 | | #endif // _WIN32 |
48 | | #include <cstdio> |
49 | | #include <limits> |
50 | | #include "GooString.h" |
51 | | #include "gfile.h" |
52 | | |
53 | | // Some systems don't define this, so just make it something reasonably |
54 | | // large. |
55 | | #ifndef PATH_MAX |
56 | | # define PATH_MAX 1024 |
57 | | #endif |
58 | | |
59 | | #ifndef _WIN32 |
60 | | |
61 | | using namespace std::string_literals; |
62 | | |
63 | | namespace { |
64 | | |
65 | | template<typename...> |
66 | | struct void_type |
67 | | { |
68 | | using type = void; |
69 | | }; |
70 | | |
71 | | template<typename... Args> |
72 | | using void_t = typename void_type<Args...>::type; |
73 | | |
74 | | template<typename Stat, typename = void_t<>> |
75 | | struct StatMtim |
76 | | { |
77 | 7.21k | static const struct timespec &value(const Stat &stbuf) { return stbuf.st_mtim; } |
78 | | }; |
79 | | |
80 | | // Mac OS X uses a different field name than POSIX and this detects it. |
81 | | template<typename Stat> |
82 | | struct StatMtim<Stat, void_t<decltype(Stat::st_mtimespec)>> |
83 | | { |
84 | | static const struct timespec &value(const Stat &stbuf) { return stbuf.st_mtimespec; } |
85 | | }; |
86 | | |
87 | | inline const struct timespec &mtim(const struct stat &stbuf) |
88 | 7.21k | { |
89 | 7.21k | return StatMtim<struct stat>::value(stbuf); |
90 | 7.21k | } |
91 | | |
92 | | } |
93 | | |
94 | | #endif |
95 | | |
96 | | //------------------------------------------------------------------------ |
97 | | |
98 | | GooString *appendToPath(GooString *path, const char *fileName) |
99 | 998 | { |
100 | | #ifdef _WIN32 |
101 | | //---------- Win32 ---------- |
102 | | char buf[256]; |
103 | | char *fp; |
104 | | |
105 | | auto tmp = path->copy(); |
106 | | tmp->push_back('/'); |
107 | | tmp->append(fileName); |
108 | | GetFullPathNameA(tmp->c_str(), sizeof(buf), buf, &fp); |
109 | | path->clear(); |
110 | | path->append(buf); |
111 | | return path; |
112 | | |
113 | | #else |
114 | | //---------- Unix ---------- |
115 | 998 | int i; |
116 | | |
117 | | // appending "." does nothing |
118 | 998 | if (!strcmp(fileName, ".")) { |
119 | 0 | return path; |
120 | 0 | } |
121 | | |
122 | | // appending ".." goes up one directory |
123 | 998 | if (!strcmp(fileName, "..")) { |
124 | 0 | for (i = path->size() - 2; i >= 0; --i) { |
125 | 0 | if (path->getChar(i) == '/') { |
126 | 0 | break; |
127 | 0 | } |
128 | 0 | } |
129 | 0 | if (i <= 0) { |
130 | 0 | if (path->getChar(0) == '/') { |
131 | 0 | path->erase(1, path->size() - 1); |
132 | 0 | } else { |
133 | 0 | path->clear(); |
134 | 0 | path->append(".."); |
135 | 0 | } |
136 | 0 | } else { |
137 | 0 | path->erase(i, path->size() - i); |
138 | 0 | } |
139 | 0 | return path; |
140 | 0 | } |
141 | | |
142 | | // otherwise, append "/" and new path component |
143 | 998 | if (!path->empty() && path->getChar(path->size() - 1) != '/') { |
144 | 998 | path->push_back('/'); |
145 | 998 | } |
146 | 998 | path->append(fileName); |
147 | 998 | return path; |
148 | 998 | #endif |
149 | 998 | } |
150 | | |
151 | | #ifndef _WIN32 |
152 | | |
153 | | static bool makeFileDescriptorCloexec(int fd) |
154 | 0 | { |
155 | 0 | # ifdef FD_CLOEXEC |
156 | 0 | int flags = fcntl(fd, F_GETFD); |
157 | 0 | if (flags >= 0 && !(flags & FD_CLOEXEC)) { |
158 | 0 | flags = fcntl(fd, F_SETFD, flags | FD_CLOEXEC); |
159 | 0 | } |
160 | |
|
161 | 0 | return flags >= 0; |
162 | | # else |
163 | | return true; |
164 | | # endif |
165 | 0 | } |
166 | | |
167 | | int openFileDescriptor(const char *path, int flags) |
168 | 7.21k | { |
169 | 7.21k | # ifdef O_CLOEXEC |
170 | 7.21k | return open(path, flags | O_CLOEXEC); |
171 | | # else |
172 | | int fd = open(path, flags); |
173 | | if (fd == -1) |
174 | | return fd; |
175 | | |
176 | | if (!makeFileDescriptorCloexec(fd)) { |
177 | | close(fd); |
178 | | return -1; |
179 | | } |
180 | | |
181 | | return fd; |
182 | | # endif |
183 | 7.21k | } |
184 | | |
185 | | #endif |
186 | | |
187 | | FILE *openFile(const char *path, const char *mode) |
188 | 364k | { |
189 | | #ifdef _WIN32 |
190 | | OSVERSIONINFO version; |
191 | | wchar_t wPath[MAX_PATH + 1]; |
192 | | char nPath[MAX_PATH + 1]; |
193 | | wchar_t wMode[8]; |
194 | | const char *p; |
195 | | size_t i; |
196 | | |
197 | | // NB: _wfopen is only available in NT |
198 | | version.dwOSVersionInfoSize = sizeof(version); |
199 | | GetVersionEx(&version); |
200 | | if (version.dwPlatformId == VER_PLATFORM_WIN32_NT) { |
201 | | for (p = path, i = 0; *p && i < MAX_PATH; ++i) { |
202 | | if ((p[0] & 0xe0) == 0xc0 && p[1] && (p[1] & 0xc0) == 0x80) { |
203 | | wPath[i] = (wchar_t)(((p[0] & 0x1f) << 6) | (p[1] & 0x3f)); |
204 | | p += 2; |
205 | | } else if ((p[0] & 0xf0) == 0xe0 && p[1] && (p[1] & 0xc0) == 0x80 && p[2] && (p[2] & 0xc0) == 0x80) { |
206 | | wPath[i] = (wchar_t)(((p[0] & 0x0f) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f)); |
207 | | p += 3; |
208 | | } else { |
209 | | wPath[i] = (wchar_t)(p[0] & 0xff); |
210 | | p += 1; |
211 | | } |
212 | | } |
213 | | wPath[i] = (wchar_t)0; |
214 | | for (i = 0; (i < sizeof(mode) - 1) && mode[i]; ++i) { |
215 | | wMode[i] = (wchar_t)(mode[i] & 0xff); |
216 | | } |
217 | | wMode[i] = (wchar_t)0; |
218 | | return _wfopen(wPath, wMode); |
219 | | } else { |
220 | | for (p = path, i = 0; *p && i < MAX_PATH; ++i) { |
221 | | if ((p[0] & 0xe0) == 0xc0 && p[1] && (p[1] & 0xc0) == 0x80) { |
222 | | nPath[i] = (char)(((p[0] & 0x1f) << 6) | (p[1] & 0x3f)); |
223 | | p += 2; |
224 | | } else if ((p[0] & 0xf0) == 0xe0 && p[1] && (p[1] & 0xc0) == 0x80 && p[2] && (p[2] & 0xc0) == 0x80) { |
225 | | nPath[i] = (char)(((p[1] & 0x3f) << 6) | (p[2] & 0x3f)); |
226 | | p += 3; |
227 | | } else { |
228 | | nPath[i] = p[0]; |
229 | | p += 1; |
230 | | } |
231 | | } |
232 | | nPath[i] = '\0'; |
233 | | return fopen(nPath, mode); |
234 | | } |
235 | | #else |
236 | | // First try to atomically open the file with CLOEXEC |
237 | 364k | const std::string modeStr = mode + "e"s; |
238 | 364k | FILE *file = fopen(path, modeStr.c_str()); |
239 | 364k | if (file != nullptr) { |
240 | 364k | return file; |
241 | 364k | } |
242 | | |
243 | | // Fall back to the provided mode and apply CLOEXEC afterwards |
244 | 492 | file = fopen(path, mode); |
245 | 492 | if (file == nullptr) { |
246 | 492 | return nullptr; |
247 | 492 | } |
248 | | |
249 | 0 | if (!makeFileDescriptorCloexec(fileno(file))) { |
250 | 0 | fclose(file); |
251 | 0 | return nullptr; |
252 | 0 | } |
253 | | |
254 | 0 | return file; |
255 | 0 | #endif |
256 | 0 | } |
257 | | |
258 | | char *getLine(char *buf, int size, FILE *f) |
259 | 12.9M | { |
260 | 12.9M | int c, i; |
261 | | |
262 | 12.9M | i = 0; |
263 | 199M | while (i < size - 1) { |
264 | 199M | if ((c = fgetc(f)) == EOF) { |
265 | 139k | break; |
266 | 139k | } |
267 | 199M | buf[i++] = (char)c; |
268 | 199M | if (c == '\x0a') { |
269 | 12.8M | break; |
270 | 12.8M | } |
271 | 186M | if (c == '\x0d') { |
272 | 0 | c = fgetc(f); |
273 | 0 | if (c == '\x0a' && i < size - 1) { |
274 | 0 | buf[i++] = (char)c; |
275 | 0 | } else if (c != EOF) { |
276 | 0 | ungetc(c, f); |
277 | 0 | } |
278 | 0 | break; |
279 | 0 | } |
280 | 186M | } |
281 | 12.9M | buf[i] = '\0'; |
282 | 12.9M | if (i == 0) { |
283 | 139k | return nullptr; |
284 | 139k | } |
285 | 12.8M | return buf; |
286 | 12.9M | } |
287 | | |
288 | | int Gfseek(FILE *f, Goffset offset, int whence) |
289 | 0 | { |
290 | 0 | #if HAVE_FSEEKO |
291 | 0 | return fseeko(f, offset, whence); |
292 | | #elif HAVE_FSEEK64 |
293 | | return fseek64(f, offset, whence); |
294 | | #elif defined(__MINGW32__) |
295 | | return fseeko64(f, offset, whence); |
296 | | #elif defined(_WIN32) |
297 | | return _fseeki64(f, offset, whence); |
298 | | #else |
299 | | return fseek(f, offset, whence); |
300 | | #endif |
301 | 0 | } |
302 | | |
303 | | Goffset Gftell(FILE *f) |
304 | 0 | { |
305 | 0 | #if HAVE_FSEEKO |
306 | 0 | return ftello(f); |
307 | | #elif HAVE_FSEEK64 |
308 | | return ftell64(f); |
309 | | #elif defined(__MINGW32__) |
310 | | return ftello64(f); |
311 | | #elif defined(_WIN32) |
312 | | return _ftelli64(f); |
313 | | #else |
314 | | return ftell(f); |
315 | | #endif |
316 | 0 | } |
317 | | |
318 | | Goffset GoffsetMax() |
319 | 5.96M | { |
320 | 5.96M | #if HAVE_FSEEKO |
321 | 5.96M | return (std::numeric_limits<off_t>::max)(); |
322 | | #elif HAVE_FSEEK64 || defined(__MINGW32__) |
323 | | return (std::numeric_limits<off64_t>::max)(); |
324 | | #elif defined(_WIN32) |
325 | | return (std::numeric_limits<__int64>::max)(); |
326 | | #else |
327 | | return (std::numeric_limits<long>::max)(); |
328 | | #endif |
329 | 5.96M | } |
330 | | |
331 | | //------------------------------------------------------------------------ |
332 | | // GooFile |
333 | | //------------------------------------------------------------------------ |
334 | | |
335 | | #ifdef _WIN32 |
336 | | |
337 | | GooFile::GooFile(HANDLE handleA) : handle(handleA) |
338 | | { |
339 | | GetFileTime(handleA, nullptr, nullptr, &modifiedTimeOnOpen); |
340 | | } |
341 | | |
342 | | int GooFile::read(char *buf, int n, Goffset offset) const |
343 | | { |
344 | | DWORD m; |
345 | | |
346 | | LARGE_INTEGER largeInteger = {}; |
347 | | largeInteger.QuadPart = offset; |
348 | | |
349 | | OVERLAPPED overlapped = {}; |
350 | | overlapped.Offset = largeInteger.LowPart; |
351 | | overlapped.OffsetHigh = largeInteger.HighPart; |
352 | | |
353 | | return FALSE == ReadFile(handle, buf, n, &m, &overlapped) ? -1 : m; |
354 | | } |
355 | | |
356 | | Goffset GooFile::size() const |
357 | | { |
358 | | LARGE_INTEGER size = { (DWORD)-1, -1 }; |
359 | | |
360 | | GetFileSizeEx(handle, &size); |
361 | | |
362 | | return size.QuadPart; |
363 | | } |
364 | | |
365 | | std::unique_ptr<GooFile> GooFile::open(const std::string &fileName) |
366 | | { |
367 | | HANDLE handle = CreateFileA(fileName.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); |
368 | | |
369 | | return handle == INVALID_HANDLE_VALUE ? std::unique_ptr<GooFile>() : std::unique_ptr<GooFile>(new GooFile(handle)); |
370 | | } |
371 | | |
372 | | std::unique_ptr<GooFile> GooFile::open(const wchar_t *fileName) |
373 | | { |
374 | | HANDLE handle = CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); |
375 | | |
376 | | return handle == INVALID_HANDLE_VALUE ? std::unique_ptr<GooFile>() : std::unique_ptr<GooFile>(new GooFile(handle)); |
377 | | } |
378 | | |
379 | | bool GooFile::modificationTimeChangedSinceOpen() const |
380 | | { |
381 | | struct _FILETIME lastModified; |
382 | | GetFileTime(handle, nullptr, nullptr, &lastModified); |
383 | | |
384 | | return modifiedTimeOnOpen.dwHighDateTime != lastModified.dwHighDateTime || modifiedTimeOnOpen.dwLowDateTime != lastModified.dwLowDateTime; |
385 | | } |
386 | | |
387 | | #else |
388 | | |
389 | | int GooFile::read(char *buf, int n, Goffset offset) const |
390 | 2.26M | { |
391 | 2.26M | # if HAVE_PREAD64 |
392 | 2.26M | return pread64(fd, buf, n, offset); |
393 | | # else |
394 | | return pread(fd, buf, n, offset); |
395 | | # endif |
396 | 2.26M | } |
397 | | |
398 | | Goffset GooFile::size() const |
399 | 73.0k | { |
400 | 73.0k | # if HAVE_LSEEK64 |
401 | 73.0k | return lseek64(fd, 0, SEEK_END); |
402 | | # else |
403 | | return lseek(fd, 0, SEEK_END); |
404 | | # endif |
405 | 73.0k | } |
406 | | |
407 | | std::unique_ptr<GooFile> GooFile::open(const std::string &fileName) |
408 | 7.21k | { |
409 | 7.21k | int fd = openFileDescriptor(fileName.c_str(), O_RDONLY); |
410 | | |
411 | 7.21k | return GooFile::open(fd); |
412 | 7.21k | } |
413 | | |
414 | | std::unique_ptr<GooFile> GooFile::open(int fdA) |
415 | 7.21k | { |
416 | 7.21k | return fdA < 0 ? std::unique_ptr<GooFile>() : std::unique_ptr<GooFile>(new GooFile(fdA)); |
417 | 7.21k | } |
418 | | |
419 | 7.21k | GooFile::GooFile(int fdA) : fd(fdA) |
420 | 7.21k | { |
421 | 7.21k | struct stat statbuf; |
422 | 7.21k | fstat(fd, &statbuf); |
423 | 7.21k | modifiedTimeOnOpen = mtim(statbuf); |
424 | 7.21k | } |
425 | | |
426 | | bool GooFile::modificationTimeChangedSinceOpen() const |
427 | 0 | { |
428 | 0 | struct stat statbuf; |
429 | 0 | fstat(fd, &statbuf); |
430 | |
|
431 | 0 | return modifiedTimeOnOpen.tv_sec != mtim(statbuf).tv_sec || modifiedTimeOnOpen.tv_nsec != mtim(statbuf).tv_nsec; |
432 | 0 | } |
433 | | |
434 | | #endif // _WIN32 |