Coverage Report

Created: 2026-02-10 07:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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