Coverage Report

Created: 2025-08-28 06:57

/src/proj/src/filemanager.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 * Project:  PROJ
3
 * Purpose:  File manager
4
 * Author:   Even Rouault, <even.rouault at spatialys.com>
5
 *
6
 ******************************************************************************
7
 * Copyright (c) 2019, Even Rouault, <even.rouault at spatialys.com>
8
 *
9
 * Permission is hereby granted, free of charge, to any person obtaining a
10
 * copy of this software and associated documentation files (the "Software"),
11
 * to deal in the Software without restriction, including without limitation
12
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
13
 * and/or sell copies of the Software, and to permit persons to whom the
14
 * Software is furnished to do so, subject to the following conditions:
15
 *
16
 * The above copyright notice and this permission notice shall be included
17
 * in all copies or substantial portions of the Software.
18
 *
19
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
22
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25
 * DEALINGS IN THE SOFTWARE.
26
 *****************************************************************************/
27
28
#ifndef FROM_PROJ_CPP
29
#define FROM_PROJ_CPP
30
#endif
31
32
// proj_config.h must be included before testing HAVE_LIBDL
33
#include "proj_config.h"
34
35
#if defined(__CYGWIN__) && defined(HAVE_LIBDL) && !defined(_GNU_SOURCE)
36
// Required for dladdr() on Cygwin
37
#define _GNU_SOURCE
38
#endif
39
40
#include <errno.h>
41
#include <stdlib.h>
42
43
#include <algorithm>
44
#include <cstdint>
45
#include <limits>
46
#include <string>
47
48
#include "filemanager.hpp"
49
#include "proj.h"
50
#include "proj/internal/internal.hpp"
51
#include "proj/internal/io_internal.hpp"
52
#include "proj/io.hpp"
53
#include "proj_internal.h"
54
55
#include <sys/stat.h>
56
57
#ifdef _WIN32
58
#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
59
#define UWP 1
60
#else
61
#define UWP 0
62
#endif
63
#include <shlobj.h>
64
#include <windows.h>
65
#else
66
#ifdef HAVE_LIBDL
67
#include <dlfcn.h>
68
#endif
69
#include <sys/types.h>
70
#include <unistd.h>
71
#endif
72
73
#ifdef EMBED_RESOURCE_FILES
74
#include "embedded_resources.h"
75
#endif
76
77
//! @cond Doxygen_Suppress
78
79
using namespace NS_PROJ::internal;
80
81
NS_PROJ_START
82
83
// ---------------------------------------------------------------------------
84
85
10.4k
File::File(const std::string &filename) : name_(filename) {}
86
87
// ---------------------------------------------------------------------------
88
89
10.4k
File::~File() = default;
90
91
// ---------------------------------------------------------------------------
92
93
std::string File::read_line(size_t maxLen, bool &maxLenReached,
94
8.10k
                            bool &eofReached) {
95
8.10k
    constexpr size_t MAX_MAXLEN = 1024 * 1024;
96
8.10k
    maxLen = std::min(maxLen, MAX_MAXLEN);
97
8.34k
    while (true) {
98
        // Consume existing lines in buffer
99
8.34k
        size_t pos = readLineBuffer_.find_first_of("\r\n");
100
8.34k
        if (pos != std::string::npos) {
101
8.08k
            if (pos > maxLen) {
102
0
                std::string ret(readLineBuffer_.substr(0, maxLen));
103
0
                readLineBuffer_ = readLineBuffer_.substr(maxLen);
104
0
                maxLenReached = true;
105
0
                eofReached = false;
106
0
                return ret;
107
0
            }
108
8.08k
            std::string ret(readLineBuffer_.substr(0, pos));
109
8.08k
            if (readLineBuffer_[pos] == '\r' &&
110
8.08k
                readLineBuffer_[pos + 1] == '\n') {
111
0
                pos += 1;
112
0
            }
113
8.08k
            readLineBuffer_ = readLineBuffer_.substr(pos + 1);
114
8.08k
            maxLenReached = false;
115
8.08k
            eofReached = false;
116
8.08k
            return ret;
117
8.08k
        }
118
119
259
        const size_t prevSize = readLineBuffer_.size();
120
259
        if (maxLen <= prevSize) {
121
0
            std::string ret(readLineBuffer_.substr(0, maxLen));
122
0
            readLineBuffer_ = readLineBuffer_.substr(maxLen);
123
0
            maxLenReached = true;
124
0
            eofReached = false;
125
0
            return ret;
126
0
        }
127
128
259
        if (eofReadLine_) {
129
27
            std::string ret = readLineBuffer_;
130
27
            readLineBuffer_.clear();
131
27
            maxLenReached = false;
132
27
            eofReached = ret.empty();
133
27
            return ret;
134
27
        }
135
136
232
        readLineBuffer_.resize(maxLen);
137
232
        const size_t nRead =
138
232
            read(&readLineBuffer_[prevSize], maxLen - prevSize);
139
232
        if (nRead < maxLen - prevSize)
140
27
            eofReadLine_ = true;
141
232
        readLineBuffer_.resize(prevSize + nRead);
142
232
    }
143
8.10k
}
144
145
// ---------------------------------------------------------------------------
146
147
#ifdef _WIN32
148
149
/* The bulk of utf8towc()/utf8fromwc() is derived from the utf.c module from
150
 * FLTK. It was originally downloaded from:
151
 *    http://svn.easysw.com/public/fltk/fltk/trunk/src/utf.c
152
 * And already used by GDAL
153
 */
154
/************************************************************************/
155
/* ==================================================================== */
156
/*      UTF.C code from FLTK with some modifications.                   */
157
/* ==================================================================== */
158
/************************************************************************/
159
160
/* Set to 1 to turn bad UTF8 bytes into ISO-8859-1. If this is to zero
161
   they are instead turned into the Unicode REPLACEMENT CHARACTER, of
162
   value 0xfffd.
163
   If this is on utf8decode will correctly map most (perhaps all)
164
   human-readable text that is in ISO-8859-1. This may allow you
165
   to completely ignore character sets in your code because virtually
166
   everything is either ISO-8859-1 or UTF-8.
167
*/
168
#define ERRORS_TO_ISO8859_1 1
169
170
/* Set to 1 to turn bad UTF8 bytes in the 0x80-0x9f range into the
171
   Unicode index for Microsoft's CP1252 character set. You should
172
   also set ERRORS_TO_ISO8859_1. With this a huge amount of more
173
   available text (such as all web pages) are correctly converted
174
   to Unicode.
175
*/
176
#define ERRORS_TO_CP1252 1
177
178
/* A number of Unicode code points are in fact illegal and should not
179
   be produced by a UTF-8 converter. Turn this on will replace the
180
   bytes in those encodings with errors. If you do this then converting
181
   arbitrary 16-bit data to UTF-8 and then back is not an identity,
182
   which will probably break a lot of software.
183
*/
184
#define STRICT_RFC3629 0
185
186
#if ERRORS_TO_CP1252
187
// Codes 0x80..0x9f from the Microsoft CP1252 character set, translated
188
// to Unicode:
189
constexpr unsigned short cp1252[32] = {
190
    0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021,
191
    0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f,
192
    0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014,
193
    0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178};
194
#endif
195
196
/************************************************************************/
197
/*                             utf8decode()                             */
198
/************************************************************************/
199
200
/*
201
    Decode a single UTF-8 encoded character starting at \e p. The
202
    resulting Unicode value (in the range 0-0x10ffff) is returned,
203
    and \e len is set the number of bytes in the UTF-8 encoding
204
    (adding \e len to \e p will point at the next character).
205
206
    If \a p points at an illegal UTF-8 encoding, including one that
207
    would go past \e end, or where a code is uses more bytes than
208
    necessary, then *reinterpret_cast<const unsigned char*>(p) is translated as
209
though it is
210
    in the Microsoft CP1252 character set and \e len is set to 1.
211
    Treating errors this way allows this to decode almost any
212
    ISO-8859-1 or CP1252 text that has been mistakenly placed where
213
    UTF-8 is expected, and has proven very useful.
214
215
    If you want errors to be converted to error characters (as the
216
    standards recommend), adding a test to see if the length is
217
    unexpectedly 1 will work:
218
219
\code
220
    if( *p & 0x80 )
221
    {  // What should be a multibyte encoding.
222
      code = utf8decode(p, end, &len);
223
      if( len<2 ) code = 0xFFFD;  // Turn errors into REPLACEMENT CHARACTER.
224
    }
225
    else
226
    {  // Handle the 1-byte utf8 encoding:
227
      code = *p;
228
      len = 1;
229
    }
230
\endcode
231
232
    Direct testing for the 1-byte case (as shown above) will also
233
    speed up the scanning of strings where the majority of characters
234
    are ASCII.
235
*/
236
static unsigned utf8decode(const char *p, const char *end, int *len) {
237
    unsigned char c = *reinterpret_cast<const unsigned char *>(p);
238
    if (c < 0x80) {
239
        *len = 1;
240
        return c;
241
#if ERRORS_TO_CP1252
242
    } else if (c < 0xa0) {
243
        *len = 1;
244
        return cp1252[c - 0x80];
245
#endif
246
    } else if (c < 0xc2) {
247
        goto FAIL;
248
    }
249
    if (p + 1 >= end || (p[1] & 0xc0) != 0x80)
250
        goto FAIL;
251
    if (c < 0xe0) {
252
        *len = 2;
253
        return ((p[0] & 0x1f) << 6) + ((p[1] & 0x3f));
254
    } else if (c == 0xe0) {
255
        if ((reinterpret_cast<const unsigned char *>(p))[1] < 0xa0)
256
            goto FAIL;
257
        goto UTF8_3;
258
#if STRICT_RFC3629
259
    } else if (c == 0xed) {
260
        // RFC 3629 says surrogate chars are illegal.
261
        if ((reinterpret_cast<const unsigned char *>(p))[1] >= 0xa0)
262
            goto FAIL;
263
        goto UTF8_3;
264
    } else if (c == 0xef) {
265
        // 0xfffe and 0xffff are also illegal characters.
266
        if ((reinterpret_cast<const unsigned char *>(p))[1] == 0xbf &&
267
            (reinterpret_cast<const unsigned char *>(p))[2] >= 0xbe)
268
            goto FAIL;
269
        goto UTF8_3;
270
#endif
271
    } else if (c < 0xf0) {
272
    UTF8_3:
273
        if (p + 2 >= end || (p[2] & 0xc0) != 0x80)
274
            goto FAIL;
275
        *len = 3;
276
        return ((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) + ((p[2] & 0x3f));
277
    } else if (c == 0xf0) {
278
        if ((reinterpret_cast<const unsigned char *>(p))[1] < 0x90)
279
            goto FAIL;
280
        goto UTF8_4;
281
    } else if (c < 0xf4) {
282
    UTF8_4:
283
        if (p + 3 >= end || (p[2] & 0xc0) != 0x80 || (p[3] & 0xc0) != 0x80)
284
            goto FAIL;
285
        *len = 4;
286
#if STRICT_RFC3629
287
        // RFC 3629 says all codes ending in fffe or ffff are illegal:
288
        if ((p[1] & 0xf) == 0xf &&
289
            (reinterpret_cast<const unsigned char *>(p))[2] == 0xbf &&
290
            (reinterpret_cast<const unsigned char *>(p))[3] >= 0xbe)
291
            goto FAIL;
292
#endif
293
        return ((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) +
294
               ((p[2] & 0x3f) << 6) + ((p[3] & 0x3f));
295
    } else if (c == 0xf4) {
296
        if ((reinterpret_cast<const unsigned char *>(p))[1] > 0x8f)
297
            goto FAIL; // After 0x10ffff.
298
        goto UTF8_4;
299
    } else {
300
    FAIL:
301
        *len = 1;
302
#if ERRORS_TO_ISO8859_1
303
        return c;
304
#else
305
        return 0xfffd; // Unicode REPLACEMENT CHARACTER
306
#endif
307
    }
308
}
309
310
/************************************************************************/
311
/*                              utf8towc()                              */
312
/************************************************************************/
313
314
/*  Convert a UTF-8 sequence into an array of wchar_t. These
315
    are used by some system calls, especially on Windows.
316
317
    \a src points at the UTF-8, and \a srclen is the number of bytes to
318
    convert.
319
320
    \a dst points at an array to write, and \a dstlen is the number of
321
    locations in this array. At most \a dstlen-1 words will be
322
    written there, plus a 0 terminating word. Thus this function
323
    will never overwrite the buffer and will always return a
324
    zero-terminated string. If \a dstlen is zero then \a dst can be
325
    null and no data is written, but the length is returned.
326
327
    The return value is the number of words that \e would be written
328
    to \a dst if it were long enough, not counting the terminating
329
    zero. If the return value is greater or equal to \a dstlen it
330
    indicates truncation, you can then allocate a new array of size
331
    return+1 and call this again.
332
333
    Errors in the UTF-8 are converted as though each byte in the
334
    erroneous string is in the Microsoft CP1252 encoding. This allows
335
    ISO-8859-1 text mistakenly identified as UTF-8 to be printed
336
    correctly.
337
338
    Notice that sizeof(wchar_t) is 2 on Windows and is 4 on Linux
339
    and most other systems. Where wchar_t is 16 bits, Unicode
340
    characters in the range 0x10000 to 0x10ffff are converted to
341
    "surrogate pairs" which take two words each (this is called UTF-16
342
    encoding). If wchar_t is 32 bits this rather nasty problem is
343
    avoided.
344
*/
345
static unsigned utf8towc(const char *src, unsigned srclen, wchar_t *dst,
346
                         unsigned dstlen) {
347
    const char *p = src;
348
    const char *e = src + srclen;
349
    unsigned count = 0;
350
    if (dstlen)
351
        while (true) {
352
            if (p >= e) {
353
                dst[count] = 0;
354
                return count;
355
            }
356
            if (!(*p & 0x80)) {
357
                // ASCII
358
                dst[count] = *p++;
359
            } else {
360
                int len = 0;
361
                unsigned ucs = utf8decode(p, e, &len);
362
                p += len;
363
#ifdef _WIN32
364
                if (ucs < 0x10000) {
365
                    dst[count] = static_cast<wchar_t>(ucs);
366
                } else {
367
                    // Make a surrogate pair:
368
                    if (count + 2 >= dstlen) {
369
                        dst[count] = 0;
370
                        count += 2;
371
                        break;
372
                    }
373
                    dst[count] = static_cast<wchar_t>(
374
                        (((ucs - 0x10000u) >> 10) & 0x3ff) | 0xd800);
375
                    dst[++count] = static_cast<wchar_t>((ucs & 0x3ff) | 0xdc00);
376
                }
377
#else
378
                dst[count] = static_cast<wchar_t>(ucs);
379
#endif
380
            }
381
            if (++count == dstlen) {
382
                dst[count - 1] = 0;
383
                break;
384
            }
385
        }
386
    // We filled dst, measure the rest:
387
    while (p < e) {
388
        if (!(*p & 0x80)) {
389
            p++;
390
        } else {
391
            int len = 0;
392
#ifdef _WIN32
393
            const unsigned ucs = utf8decode(p, e, &len);
394
            p += len;
395
            if (ucs >= 0x10000)
396
                ++count;
397
#else
398
            utf8decode(p, e, &len);
399
            p += len;
400
#endif
401
        }
402
        ++count;
403
    }
404
405
    return count;
406
}
407
408
// ---------------------------------------------------------------------------
409
410
struct NonValidUTF8Exception : public std::exception {};
411
412
// May throw exceptions
413
static std::wstring UTF8ToWString(const std::string &str) {
414
    std::wstring wstr;
415
    wstr.resize(str.size());
416
    wstr.resize(utf8towc(str.data(), static_cast<unsigned>(str.size()),
417
                         &wstr[0], static_cast<unsigned>(wstr.size()) + 1));
418
    for (const auto ch : wstr) {
419
        if (ch == 0xfffd) {
420
            throw NonValidUTF8Exception();
421
        }
422
    }
423
    return wstr;
424
}
425
426
// ---------------------------------------------------------------------------
427
428
/************************************************************************/
429
/*                             utf8fromwc()                             */
430
/************************************************************************/
431
/* Turn "wide characters" as returned by some system calls
432
    (especially on Windows) into UTF-8.
433
434
    Up to \a dstlen bytes are written to \a dst, including a null
435
    terminator. The return value is the number of bytes that would be
436
    written, not counting the null terminator. If greater or equal to
437
    \a dstlen then if you malloc a new array of size n+1 you will have
438
    the space needed for the entire string. If \a dstlen is zero then
439
    nothing is written and this call just measures the storage space
440
    needed.
441
442
    \a srclen is the number of words in \a src to convert. On Windows
443
    this is not necessarily the number of characters, due to there
444
    possibly being "surrogate pairs" in the UTF-16 encoding used.
445
    On Unix wchar_t is 32 bits and each location is a character.
446
447
    On Unix if a src word is greater than 0x10ffff then this is an
448
    illegal character according to RFC 3629. These are converted as
449
    though they are 0xFFFD (REPLACEMENT CHARACTER). Characters in the
450
    range 0xd800 to 0xdfff, or ending with 0xfffe or 0xffff are also
451
    illegal according to RFC 3629. However I encode these as though
452
    they are legal, so that utf8towc will return the original data.
453
454
    On Windows "surrogate pairs" are converted to a single character
455
    and UTF-8 encoded (as 4 bytes). Mismatched halves of surrogate
456
    pairs are converted as though they are individual characters.
457
*/
458
static unsigned int utf8fromwc(char *dst, unsigned dstlen, const wchar_t *src,
459
                               unsigned srclen) {
460
    unsigned int i = 0;
461
    unsigned int count = 0;
462
    if (dstlen)
463
        while (true) {
464
            if (i >= srclen) {
465
                dst[count] = 0;
466
                return count;
467
            }
468
            unsigned int ucs = src[i++];
469
            if (ucs < 0x80U) {
470
                dst[count++] = static_cast<char>(ucs);
471
                if (count >= dstlen) {
472
                    dst[count - 1] = 0;
473
                    break;
474
                }
475
            } else if (ucs < 0x800U) {
476
                // 2 bytes.
477
                if (count + 2 >= dstlen) {
478
                    dst[count] = 0;
479
                    count += 2;
480
                    break;
481
                }
482
                dst[count++] = 0xc0 | static_cast<char>(ucs >> 6);
483
                dst[count++] = 0x80 | static_cast<char>(ucs & 0x3F);
484
#ifdef _WIN32
485
            } else if (ucs >= 0xd800 && ucs <= 0xdbff && i < srclen &&
486
                       src[i] >= 0xdc00 && src[i] <= 0xdfff) {
487
                // Surrogate pair.
488
                unsigned int ucs2 = src[i++];
489
                ucs = 0x10000U + ((ucs & 0x3ff) << 10) + (ucs2 & 0x3ff);
490
// All surrogate pairs turn into 4-byte utf8.
491
#else
492
            } else if (ucs >= 0x10000) {
493
                if (ucs > 0x10ffff) {
494
                    ucs = 0xfffd;
495
                    goto J1;
496
                }
497
#endif
498
                if (count + 4 >= dstlen) {
499
                    dst[count] = 0;
500
                    count += 4;
501
                    break;
502
                }
503
                dst[count++] = 0xf0 | static_cast<char>(ucs >> 18);
504
                dst[count++] = 0x80 | static_cast<char>((ucs >> 12) & 0x3F);
505
                dst[count++] = 0x80 | static_cast<char>((ucs >> 6) & 0x3F);
506
                dst[count++] = 0x80 | static_cast<char>(ucs & 0x3F);
507
            } else {
508
#ifndef _WIN32
509
            J1:
510
#endif
511
                // All others are 3 bytes:
512
                if (count + 3 >= dstlen) {
513
                    dst[count] = 0;
514
                    count += 3;
515
                    break;
516
                }
517
                dst[count++] = 0xe0 | static_cast<char>(ucs >> 12);
518
                dst[count++] = 0x80 | static_cast<char>((ucs >> 6) & 0x3F);
519
                dst[count++] = 0x80 | static_cast<char>(ucs & 0x3F);
520
            }
521
        }
522
523
    // We filled dst, measure the rest:
524
    while (i < srclen) {
525
        unsigned int ucs = src[i++];
526
        if (ucs < 0x80U) {
527
            count++;
528
        } else if (ucs < 0x800U) {
529
            // 2 bytes.
530
            count += 2;
531
#ifdef _WIN32
532
        } else if (ucs >= 0xd800 && ucs <= 0xdbff && i < srclen - 1 &&
533
                   src[i + 1] >= 0xdc00 && src[i + 1] <= 0xdfff) {
534
            // Surrogate pair.
535
            ++i;
536
#else
537
        } else if (ucs >= 0x10000 && ucs <= 0x10ffff) {
538
#endif
539
            count += 4;
540
        } else {
541
            count += 3;
542
        }
543
    }
544
    return count;
545
}
546
547
// ---------------------------------------------------------------------------
548
549
static std::string WStringToUTF8(const std::wstring &wstr) {
550
    std::string str;
551
    str.resize(wstr.size());
552
    str.resize(utf8fromwc(&str[0], static_cast<unsigned>(str.size() + 1),
553
                          wstr.data(), static_cast<unsigned>(wstr.size())));
554
    return str;
555
}
556
557
// ---------------------------------------------------------------------------
558
559
static std::string Win32Recode(const char *src, unsigned src_code_page,
560
                               unsigned dst_code_page) {
561
    // Convert from source code page to Unicode.
562
563
    // Compute the length in wide characters.
564
    int wlen = MultiByteToWideChar(src_code_page, MB_ERR_INVALID_CHARS, src, -1,
565
                                   nullptr, 0);
566
    if (wlen == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION) {
567
        return std::string();
568
    }
569
570
    // Do the actual conversion.
571
    std::wstring wbuf;
572
    wbuf.resize(wlen);
573
    MultiByteToWideChar(src_code_page, 0, src, -1, &wbuf[0], wlen);
574
575
    // Convert from Unicode to destination code page.
576
577
    // Compute the length in chars.
578
    int len = WideCharToMultiByte(dst_code_page, 0, &wbuf[0], -1, nullptr, 0,
579
                                  nullptr, nullptr);
580
581
    // Do the actual conversion.
582
    std::string out;
583
    out.resize(len);
584
    WideCharToMultiByte(dst_code_page, 0, &wbuf[0], -1, &out[0], len, nullptr,
585
                        nullptr);
586
    out.resize(strlen(out.c_str()));
587
588
    return out;
589
}
590
591
#endif // _defined(_WIN32)
592
593
#if !(EMBED_RESOURCE_FILES && USE_ONLY_EMBEDDED_RESOURCE_FILES)
594
595
#ifdef _WIN32
596
597
// ---------------------------------------------------------------------------
598
599
class FileWin32 : public File {
600
    PJ_CONTEXT *m_ctx;
601
    HANDLE m_handle;
602
603
    FileWin32(const FileWin32 &) = delete;
604
    FileWin32 &operator=(const FileWin32 &) = delete;
605
606
  protected:
607
    FileWin32(const std::string &name, PJ_CONTEXT *ctx, HANDLE handle)
608
        : File(name), m_ctx(ctx), m_handle(handle) {}
609
610
  public:
611
    ~FileWin32() override;
612
613
    size_t read(void *buffer, size_t sizeBytes) override;
614
    size_t write(const void *buffer, size_t sizeBytes) override;
615
    bool seek(unsigned long long offset, int whence = SEEK_SET) override;
616
    unsigned long long tell() override;
617
    void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }
618
619
    // We may lie, but the real use case is only for network files
620
    bool hasChanged() const override { return false; }
621
622
    static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename,
623
                                      FileAccess access);
624
};
625
626
// ---------------------------------------------------------------------------
627
628
FileWin32::~FileWin32() { CloseHandle(m_handle); }
629
630
// ---------------------------------------------------------------------------
631
632
size_t FileWin32::read(void *buffer, size_t sizeBytes) {
633
    DWORD dwSizeRead = 0;
634
    size_t nResult = 0;
635
636
    if (!ReadFile(m_handle, buffer, static_cast<DWORD>(sizeBytes), &dwSizeRead,
637
                  nullptr))
638
        nResult = 0;
639
    else
640
        nResult = dwSizeRead;
641
642
    return nResult;
643
}
644
645
// ---------------------------------------------------------------------------
646
647
size_t FileWin32::write(const void *buffer, size_t sizeBytes) {
648
    DWORD dwSizeWritten = 0;
649
    size_t nResult = 0;
650
651
    if (!WriteFile(m_handle, buffer, static_cast<DWORD>(sizeBytes),
652
                   &dwSizeWritten, nullptr))
653
        nResult = 0;
654
    else
655
        nResult = dwSizeWritten;
656
657
    return nResult;
658
}
659
660
// ---------------------------------------------------------------------------
661
662
bool FileWin32::seek(unsigned long long offset, int whence) {
663
    LONG dwMoveMethod, dwMoveHigh;
664
    uint32_t nMoveLow;
665
    LARGE_INTEGER li;
666
667
    switch (whence) {
668
    case SEEK_CUR:
669
        dwMoveMethod = FILE_CURRENT;
670
        break;
671
    case SEEK_END:
672
        dwMoveMethod = FILE_END;
673
        break;
674
    case SEEK_SET:
675
    default:
676
        dwMoveMethod = FILE_BEGIN;
677
        break;
678
    }
679
680
    li.QuadPart = offset;
681
    nMoveLow = li.LowPart;
682
    dwMoveHigh = li.HighPart;
683
684
    SetLastError(0);
685
    SetFilePointer(m_handle, nMoveLow, &dwMoveHigh, dwMoveMethod);
686
687
    return GetLastError() == NO_ERROR;
688
}
689
690
// ---------------------------------------------------------------------------
691
692
unsigned long long FileWin32::tell() {
693
    LARGE_INTEGER li;
694
695
    li.HighPart = 0;
696
    li.LowPart = SetFilePointer(m_handle, 0, &(li.HighPart), FILE_CURRENT);
697
698
    return static_cast<unsigned long long>(li.QuadPart);
699
}
700
// ---------------------------------------------------------------------------
701
702
std::unique_ptr<File> FileWin32::open(PJ_CONTEXT *ctx, const char *filename,
703
                                      FileAccess access) {
704
    DWORD dwDesiredAccess = access == FileAccess::READ_ONLY
705
                                ? GENERIC_READ
706
                                : GENERIC_READ | GENERIC_WRITE;
707
    DWORD dwCreationDisposition =
708
        access == FileAccess::CREATE ? CREATE_ALWAYS : OPEN_EXISTING;
709
    DWORD dwFlagsAndAttributes = (dwDesiredAccess == GENERIC_READ)
710
                                     ? FILE_ATTRIBUTE_READONLY
711
                                     : FILE_ATTRIBUTE_NORMAL;
712
    try {
713
#if UWP
714
        CREATEFILE2_EXTENDED_PARAMETERS extendedParameters;
715
        ZeroMemory(&extendedParameters, sizeof(extendedParameters));
716
        extendedParameters.dwSize = sizeof(extendedParameters);
717
        extendedParameters.dwFileAttributes = dwFlagsAndAttributes;
718
        HANDLE hFile = CreateFile2(
719
            UTF8ToWString(std::string(filename)).c_str(), dwDesiredAccess,
720
            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
721
            dwCreationDisposition, &extendedParameters);
722
#else  // UWP
723
        HANDLE hFile = CreateFileW(
724
            UTF8ToWString(std::string(filename)).c_str(), dwDesiredAccess,
725
            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
726
            dwCreationDisposition, dwFlagsAndAttributes, nullptr);
727
#endif // UWP
728
        return std::unique_ptr<File>(hFile != INVALID_HANDLE_VALUE
729
                                         ? new FileWin32(filename, ctx, hFile)
730
                                         : nullptr);
731
    } catch (const std::exception &e) {
732
        pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
733
        return nullptr;
734
    }
735
}
736
737
#else // if !defined(_WIN32)
738
739
// ---------------------------------------------------------------------------
740
741
class FileStdio : public File {
742
    PJ_CONTEXT *m_ctx;
743
    FILE *m_fp;
744
745
    FileStdio(const FileStdio &) = delete;
746
    FileStdio &operator=(const FileStdio &) = delete;
747
748
  protected:
749
    FileStdio(const std::string &filename, PJ_CONTEXT *ctx, FILE *fp)
750
6.18k
        : File(filename), m_ctx(ctx), m_fp(fp) {}
751
752
  public:
753
    ~FileStdio() override;
754
755
    size_t read(void *buffer, size_t sizeBytes) override;
756
    size_t write(const void *buffer, size_t sizeBytes) override;
757
    bool seek(unsigned long long offset, int whence = SEEK_SET) override;
758
    unsigned long long tell() override;
759
0
    void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }
760
761
    // We may lie, but the real use case is only for network files
762
0
    bool hasChanged() const override { return false; }
763
764
    static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename,
765
                                      FileAccess access);
766
};
767
768
// ---------------------------------------------------------------------------
769
770
6.18k
FileStdio::~FileStdio() { fclose(m_fp); }
771
772
// ---------------------------------------------------------------------------
773
774
6.18k
size_t FileStdio::read(void *buffer, size_t sizeBytes) {
775
6.18k
    return fread(buffer, 1, sizeBytes, m_fp);
776
6.18k
}
777
778
// ---------------------------------------------------------------------------
779
780
0
size_t FileStdio::write(const void *buffer, size_t sizeBytes) {
781
0
    return fwrite(buffer, 1, sizeBytes, m_fp);
782
0
}
783
784
// ---------------------------------------------------------------------------
785
786
1.28k
bool FileStdio::seek(unsigned long long offset, int whence) {
787
    // TODO one day: use 64-bit offset compatible API
788
1.28k
    if (offset != static_cast<unsigned long long>(static_cast<long>(offset))) {
789
0
        pj_log(m_ctx, PJ_LOG_ERROR,
790
0
               "Attempt at seeking to a 64 bit offset. Not supported yet");
791
0
        return false;
792
0
    }
793
1.28k
    return fseek(m_fp, static_cast<long>(offset), whence) == 0;
794
1.28k
}
795
796
// ---------------------------------------------------------------------------
797
798
0
unsigned long long FileStdio::tell() {
799
    // TODO one day: use 64-bit offset compatible API
800
0
    return ftell(m_fp);
801
0
}
802
803
// ---------------------------------------------------------------------------
804
805
std::unique_ptr<File> FileStdio::open(PJ_CONTEXT *ctx, const char *filename,
806
3.47M
                                      FileAccess access) {
807
3.47M
    auto fp = fopen(filename, access == FileAccess::READ_ONLY ? "rb"
808
3.47M
                              : access == FileAccess::READ_UPDATE ? "r+b"
809
0
                                                                  : "w+b");
810
3.47M
    return std::unique_ptr<File>(fp ? new FileStdio(filename, ctx, fp)
811
3.47M
                                    : nullptr);
812
3.47M
}
813
814
#endif // _WIN32
815
816
#endif // !(EMBED_RESOURCE_FILES && USE_ONLY_EMBEDDED_RESOURCE_FILES)
817
818
// ---------------------------------------------------------------------------
819
820
class FileApiAdapter : public File {
821
    PJ_CONTEXT *m_ctx;
822
    PROJ_FILE_HANDLE *m_fp;
823
824
    FileApiAdapter(const FileApiAdapter &) = delete;
825
    FileApiAdapter &operator=(const FileApiAdapter &) = delete;
826
827
  protected:
828
    FileApiAdapter(const std::string &filename, PJ_CONTEXT *ctx,
829
                   PROJ_FILE_HANDLE *fp)
830
0
        : File(filename), m_ctx(ctx), m_fp(fp) {}
831
832
  public:
833
    ~FileApiAdapter() override;
834
835
    size_t read(void *buffer, size_t sizeBytes) override;
836
    size_t write(const void *, size_t) override;
837
    bool seek(unsigned long long offset, int whence = SEEK_SET) override;
838
    unsigned long long tell() override;
839
0
    void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }
840
841
    // We may lie, but the real use case is only for network files
842
0
    bool hasChanged() const override { return false; }
843
844
    static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename,
845
                                      FileAccess access);
846
};
847
848
// ---------------------------------------------------------------------------
849
850
0
FileApiAdapter::~FileApiAdapter() {
851
0
    m_ctx->fileApi.close_cbk(m_ctx, m_fp, m_ctx->fileApi.user_data);
852
0
}
853
854
// ---------------------------------------------------------------------------
855
856
0
size_t FileApiAdapter::read(void *buffer, size_t sizeBytes) {
857
0
    return m_ctx->fileApi.read_cbk(m_ctx, m_fp, buffer, sizeBytes,
858
0
                                   m_ctx->fileApi.user_data);
859
0
}
860
861
// ---------------------------------------------------------------------------
862
863
0
size_t FileApiAdapter::write(const void *buffer, size_t sizeBytes) {
864
0
    return m_ctx->fileApi.write_cbk(m_ctx, m_fp, buffer, sizeBytes,
865
0
                                    m_ctx->fileApi.user_data);
866
0
}
867
868
// ---------------------------------------------------------------------------
869
870
0
bool FileApiAdapter::seek(unsigned long long offset, int whence) {
871
0
    return m_ctx->fileApi.seek_cbk(m_ctx, m_fp, static_cast<long long>(offset),
872
0
                                   whence, m_ctx->fileApi.user_data) != 0;
873
0
}
874
875
// ---------------------------------------------------------------------------
876
877
0
unsigned long long FileApiAdapter::tell() {
878
0
    return m_ctx->fileApi.tell_cbk(m_ctx, m_fp, m_ctx->fileApi.user_data);
879
0
}
880
881
// ---------------------------------------------------------------------------
882
883
std::unique_ptr<File> FileApiAdapter::open(PJ_CONTEXT *ctx,
884
                                           const char *filename,
885
0
                                           FileAccess eAccess) {
886
0
    PROJ_OPEN_ACCESS eCAccess = PROJ_OPEN_ACCESS_READ_ONLY;
887
0
    switch (eAccess) {
888
0
    case FileAccess::READ_ONLY:
889
        // Initialized above
890
0
        break;
891
0
    case FileAccess::READ_UPDATE:
892
0
        eCAccess = PROJ_OPEN_ACCESS_READ_UPDATE;
893
0
        break;
894
0
    case FileAccess::CREATE:
895
0
        eCAccess = PROJ_OPEN_ACCESS_CREATE;
896
0
        break;
897
0
    }
898
0
    auto fp =
899
0
        ctx->fileApi.open_cbk(ctx, filename, eCAccess, ctx->fileApi.user_data);
900
0
    return std::unique_ptr<File>(fp ? new FileApiAdapter(filename, ctx, fp)
901
0
                                    : nullptr);
902
0
}
903
904
// ---------------------------------------------------------------------------
905
906
#if EMBED_RESOURCE_FILES
907
908
class FileMemory : public File {
909
    PJ_CONTEXT *m_ctx;
910
    const unsigned char *const m_data;
911
    const size_t m_size;
912
    size_t m_pos = 0;
913
914
    FileMemory(const FileMemory &) = delete;
915
    FileMemory &operator=(const FileMemory &) = delete;
916
917
  protected:
918
    FileMemory(const std::string &filename, PJ_CONTEXT *ctx,
919
               const unsigned char *data, size_t size)
920
4.26k
        : File(filename), m_ctx(ctx), m_data(data), m_size(size) {}
921
922
  public:
923
    size_t read(void *buffer, size_t sizeBytes) override;
924
    size_t write(const void *, size_t) override;
925
    bool seek(unsigned long long offset, int whence = SEEK_SET) override;
926
4.21k
    unsigned long long tell() override { return m_pos; }
927
0
    void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }
928
929
0
    bool hasChanged() const override { return false; }
930
931
    static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename,
932
                                      FileAccess access,
933
4.26k
                                      const unsigned char *data, size_t size) {
934
4.26k
        if (access != FileAccess::READ_ONLY)
935
0
            return nullptr;
936
4.26k
        return std::unique_ptr<File>(new FileMemory(filename, ctx, data, size));
937
4.26k
    }
938
};
939
940
4.47k
size_t FileMemory::read(void *buffer, size_t sizeBytes) {
941
4.47k
    if (m_pos >= m_size)
942
0
        return 0;
943
4.47k
    if (sizeBytes >= m_size - m_pos) {
944
4.23k
        const size_t bytesToCopy = m_size - m_pos;
945
4.23k
        memcpy(buffer, m_data + m_pos, bytesToCopy);
946
4.23k
        m_pos = m_size;
947
4.23k
        return bytesToCopy;
948
4.23k
    }
949
235
    memcpy(buffer, m_data + m_pos, sizeBytes);
950
235
    m_pos += sizeBytes;
951
235
    return sizeBytes;
952
4.47k
}
953
954
0
size_t FileMemory::write(const void *, size_t) {
955
    // shouldn't happen given we have bailed out in open() in non read-only
956
    // modes
957
0
    return 0;
958
0
}
959
960
8.45k
bool FileMemory::seek(unsigned long long offset, int whence) {
961
8.45k
    if (whence == SEEK_SET) {
962
4.24k
        m_pos = static_cast<size_t>(offset);
963
4.24k
        return m_pos == offset;
964
4.24k
    } else if (whence == SEEK_CUR) {
965
0
        const unsigned long long newPos = m_pos + offset;
966
0
        m_pos = static_cast<size_t>(newPos);
967
0
        return m_pos == newPos;
968
4.21k
    } else {
969
4.21k
        if (offset != 0)
970
0
            return false;
971
4.21k
        m_pos = m_size;
972
4.21k
        return true;
973
4.21k
    }
974
8.45k
}
975
976
#endif
977
978
// ---------------------------------------------------------------------------
979
980
std::unique_ptr<File> FileManager::open(PJ_CONTEXT *ctx, const char *filename,
981
3.47M
                                        FileAccess access) {
982
3.47M
    if (starts_with(filename, "http://") || starts_with(filename, "https://")) {
983
5.28k
        if (!proj_context_is_network_enabled(ctx)) {
984
5.28k
            pj_log(
985
5.28k
                ctx, PJ_LOG_ERROR,
986
5.28k
                "Attempt at accessing remote resource not authorized. Either "
987
5.28k
                "set PROJ_NETWORK=ON or "
988
5.28k
                "proj_context_set_enable_network(ctx, TRUE)");
989
5.28k
            return nullptr;
990
5.28k
        }
991
0
        return pj_network_file_open(ctx, filename);
992
5.28k
    }
993
3.47M
    if (ctx->fileApi.open_cbk != nullptr) {
994
0
        return FileApiAdapter::open(ctx, filename, access);
995
0
    }
996
997
3.47M
    std::unique_ptr<File> ret;
998
3.47M
#if !(EMBED_RESOURCE_FILES && USE_ONLY_EMBEDDED_RESOURCE_FILES)
999
#ifdef _WIN32
1000
    ret = FileWin32::open(ctx, filename, access);
1001
#else
1002
3.47M
    ret = FileStdio::open(ctx, filename, access);
1003
3.47M
#endif
1004
3.47M
#endif
1005
1006
3.47M
#if EMBED_RESOURCE_FILES
1007
#if USE_ONLY_EMBEDDED_RESOURCE_FILES
1008
    if (!ret)
1009
#endif
1010
3.47M
    {
1011
3.47M
        unsigned int size = 0;
1012
3.47M
        const unsigned char *in_memory_data =
1013
3.47M
            pj_get_embedded_resource(filename, &size);
1014
3.47M
        if (in_memory_data) {
1015
4.26k
            ret = FileMemory::open(ctx, filename, access, in_memory_data, size);
1016
4.26k
        }
1017
3.47M
    }
1018
3.47M
#endif
1019
1020
3.47M
    return ret;
1021
3.47M
}
1022
1023
// ---------------------------------------------------------------------------
1024
1025
1
bool FileManager::exists(PJ_CONTEXT *ctx, const char *filename) {
1026
1
    if (ctx->fileApi.exists_cbk) {
1027
0
        return ctx->fileApi.exists_cbk(ctx, filename, ctx->fileApi.user_data) !=
1028
0
               0;
1029
0
    }
1030
1031
#ifdef _WIN32
1032
    struct __stat64 buf;
1033
    try {
1034
        return _wstat64(UTF8ToWString(filename).c_str(), &buf) == 0;
1035
    } catch (const std::exception &e) {
1036
        pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
1037
        return false;
1038
    }
1039
#else
1040
1
    (void)ctx;
1041
1
    struct stat sStat;
1042
1
    return stat(filename, &sStat) == 0;
1043
1
#endif
1044
1
}
1045
1046
// ---------------------------------------------------------------------------
1047
1048
0
bool FileManager::mkdir(PJ_CONTEXT *ctx, const char *filename) {
1049
0
    if (ctx->fileApi.mkdir_cbk) {
1050
0
        return ctx->fileApi.mkdir_cbk(ctx, filename, ctx->fileApi.user_data) !=
1051
0
               0;
1052
0
    }
1053
1054
#ifdef _WIN32
1055
    try {
1056
        return _wmkdir(UTF8ToWString(filename).c_str()) == 0;
1057
    } catch (const std::exception &e) {
1058
        pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
1059
        return false;
1060
    }
1061
#else
1062
0
    (void)ctx;
1063
0
    return ::mkdir(filename, 0755) == 0;
1064
0
#endif
1065
0
}
1066
1067
// ---------------------------------------------------------------------------
1068
1069
0
bool FileManager::unlink(PJ_CONTEXT *ctx, const char *filename) {
1070
0
    if (ctx->fileApi.unlink_cbk) {
1071
0
        return ctx->fileApi.unlink_cbk(ctx, filename, ctx->fileApi.user_data) !=
1072
0
               0;
1073
0
    }
1074
1075
#ifdef _WIN32
1076
    try {
1077
        return _wunlink(UTF8ToWString(filename).c_str()) == 0;
1078
    } catch (const std::exception &e) {
1079
        pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
1080
        return false;
1081
    }
1082
#else
1083
0
    (void)ctx;
1084
0
    return ::unlink(filename) == 0;
1085
0
#endif
1086
0
}
1087
1088
// ---------------------------------------------------------------------------
1089
1090
bool FileManager::rename(PJ_CONTEXT *ctx, const char *oldPath,
1091
0
                         const char *newPath) {
1092
0
    if (ctx->fileApi.rename_cbk) {
1093
0
        return ctx->fileApi.rename_cbk(ctx, oldPath, newPath,
1094
0
                                       ctx->fileApi.user_data) != 0;
1095
0
    }
1096
1097
#ifdef _WIN32
1098
    try {
1099
        return _wrename(UTF8ToWString(oldPath).c_str(),
1100
                        UTF8ToWString(newPath).c_str()) == 0;
1101
    } catch (const std::exception &e) {
1102
        pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
1103
        return false;
1104
    }
1105
#else
1106
0
    (void)ctx;
1107
0
    return ::rename(oldPath, newPath) == 0;
1108
0
#endif
1109
0
}
1110
1111
// ---------------------------------------------------------------------------
1112
1113
17.6k
std::string FileManager::getProjDataEnvVar(PJ_CONTEXT *ctx) {
1114
17.6k
    if (!ctx->env_var_proj_data.empty()) {
1115
0
        return ctx->env_var_proj_data;
1116
0
    }
1117
17.6k
    (void)ctx;
1118
17.6k
    std::string str;
1119
17.6k
    const char *envvar = getenv("PROJ_DATA");
1120
17.6k
    if (!envvar) {
1121
17.6k
        envvar = getenv("PROJ_LIB"); // Legacy name. We should probably keep it
1122
                                     // for a long time for nostalgic people :-)
1123
17.6k
        if (envvar) {
1124
0
            pj_log(ctx, PJ_LOG_DEBUG,
1125
0
                   "PROJ_LIB environment variable is deprecated, and will be "
1126
0
                   "removed in a future release. You are encouraged to set "
1127
0
                   "PROJ_DATA instead");
1128
0
        }
1129
17.6k
    }
1130
17.6k
    if (!envvar)
1131
17.6k
        return str;
1132
0
    str = envvar;
1133
#ifdef _WIN32
1134
    // Assume this is UTF-8. If not try to convert from ANSI page
1135
    bool looksLikeUTF8 = false;
1136
    try {
1137
        UTF8ToWString(envvar);
1138
        looksLikeUTF8 = true;
1139
    } catch (const std::exception &) {
1140
    }
1141
    if (!looksLikeUTF8 || !exists(ctx, envvar)) {
1142
        str = Win32Recode(envvar, CP_ACP, CP_UTF8);
1143
        if (str.empty() || !exists(ctx, str.c_str()))
1144
            str = envvar;
1145
    }
1146
#endif
1147
0
    ctx->env_var_proj_data = str;
1148
0
    return str;
1149
17.6k
}
1150
1151
NS_PROJ_END
1152
1153
// ---------------------------------------------------------------------------
1154
1155
static void CreateDirectoryRecursively(PJ_CONTEXT *ctx,
1156
0
                                       const std::string &path) {
1157
0
    if (NS_PROJ::FileManager::exists(ctx, path.c_str()))
1158
0
        return;
1159
0
    auto pos = path.find_last_of("/\\");
1160
0
    if (pos == 0 || pos == std::string::npos)
1161
0
        return;
1162
0
    CreateDirectoryRecursively(ctx, path.substr(0, pos));
1163
0
    NS_PROJ::FileManager::mkdir(ctx, path.c_str());
1164
0
}
1165
1166
//! @endcond
1167
1168
// ---------------------------------------------------------------------------
1169
1170
/** Set a file API
1171
 *
1172
 * All callbacks should be provided (non NULL pointers). If read-only usage
1173
 * is intended, then the callbacks might have a dummy implementation.
1174
 *
1175
 * \note Those callbacks will not be used for SQLite3 database access. If
1176
 * custom I/O is desired for that, then proj_context_set_sqlite3_vfs_name()
1177
 * should be used.
1178
 *
1179
 * @param ctx PROJ context, or NULL
1180
 * @param fileapi Pointer to file API structure (content will be copied).
1181
 * @param user_data Arbitrary pointer provided by the user, and passed to the
1182
 * above callbacks. May be NULL.
1183
 * @return TRUE in case of success.
1184
 * @since 7.0
1185
 */
1186
int proj_context_set_fileapi(PJ_CONTEXT *ctx, const PROJ_FILE_API *fileapi,
1187
0
                             void *user_data) {
1188
0
    if (ctx == nullptr) {
1189
0
        ctx = pj_get_default_ctx();
1190
0
    }
1191
0
    if (!fileapi) {
1192
0
        return false;
1193
0
    }
1194
0
    if (fileapi->version != 1) {
1195
0
        return false;
1196
0
    }
1197
0
    if (!fileapi->open_cbk || !fileapi->close_cbk || !fileapi->read_cbk ||
1198
0
        !fileapi->write_cbk || !fileapi->seek_cbk || !fileapi->tell_cbk ||
1199
0
        !fileapi->exists_cbk || !fileapi->mkdir_cbk || !fileapi->unlink_cbk ||
1200
0
        !fileapi->rename_cbk) {
1201
0
        return false;
1202
0
    }
1203
0
    ctx->fileApi.open_cbk = fileapi->open_cbk;
1204
0
    ctx->fileApi.close_cbk = fileapi->close_cbk;
1205
0
    ctx->fileApi.read_cbk = fileapi->read_cbk;
1206
0
    ctx->fileApi.write_cbk = fileapi->write_cbk;
1207
0
    ctx->fileApi.seek_cbk = fileapi->seek_cbk;
1208
0
    ctx->fileApi.tell_cbk = fileapi->tell_cbk;
1209
0
    ctx->fileApi.exists_cbk = fileapi->exists_cbk;
1210
0
    ctx->fileApi.mkdir_cbk = fileapi->mkdir_cbk;
1211
0
    ctx->fileApi.unlink_cbk = fileapi->unlink_cbk;
1212
0
    ctx->fileApi.rename_cbk = fileapi->rename_cbk;
1213
0
    ctx->fileApi.user_data = user_data;
1214
0
    return true;
1215
0
}
1216
1217
// ---------------------------------------------------------------------------
1218
1219
/** Set the name of a custom SQLite3 VFS.
1220
 *
1221
 * This should be a valid SQLite3 VFS name, such as the one passed to the
1222
 * sqlite3_vfs_register(). See https://www.sqlite.org/vfs.html
1223
 *
1224
 * It will be used to read proj.db or create&access the cache.db file in the
1225
 * PROJ user writable directory.
1226
 *
1227
 * @param ctx PROJ context, or NULL
1228
 * @param name SQLite3 VFS name. If NULL is passed, default implementation by
1229
 * SQLite will be used.
1230
 * @since 7.0
1231
 */
1232
0
void proj_context_set_sqlite3_vfs_name(PJ_CONTEXT *ctx, const char *name) {
1233
0
    if (ctx == nullptr) {
1234
0
        ctx = pj_get_default_ctx();
1235
0
    }
1236
0
    ctx->custom_sqlite3_vfs_name = name ? name : std::string();
1237
0
}
1238
1239
// ---------------------------------------------------------------------------
1240
1241
/** Get the PROJ user writable directory for downloadable resource files, such
1242
 * as datum shift grids.
1243
 *
1244
 * @param ctx PROJ context, or NULL
1245
 * @param create If set to TRUE, create the directory if it does not exist
1246
 * already.
1247
 * @return The path to the PROJ user writable directory.
1248
 * @since 7.1
1249
 * @see proj_context_set_user_writable_directory()
1250
 */
1251
1252
const char *proj_context_get_user_writable_directory(PJ_CONTEXT *ctx,
1253
17.6k
                                                     int create) {
1254
17.6k
    if (!ctx)
1255
0
        ctx = pj_get_default_ctx();
1256
17.6k
    if (ctx->user_writable_directory.empty()) {
1257
        // For testing purposes only
1258
4.21k
        const char *env_var_PROJ_USER_WRITABLE_DIRECTORY =
1259
4.21k
            getenv("PROJ_USER_WRITABLE_DIRECTORY");
1260
4.21k
        if (env_var_PROJ_USER_WRITABLE_DIRECTORY &&
1261
4.21k
            env_var_PROJ_USER_WRITABLE_DIRECTORY[0] != '\0') {
1262
0
            ctx->user_writable_directory = env_var_PROJ_USER_WRITABLE_DIRECTORY;
1263
0
        }
1264
4.21k
    }
1265
17.6k
    if (ctx->user_writable_directory.empty()) {
1266
4.21k
        std::string path;
1267
#ifdef _WIN32
1268
#ifdef __MINGW32__
1269
        std::wstring wPath;
1270
        wPath.resize(MAX_PATH);
1271
        if (SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0,
1272
                             &wPath[0]) == S_OK) {
1273
            wPath.resize(wcslen(wPath.data()));
1274
            path = NS_PROJ::WStringToUTF8(wPath);
1275
#else
1276
#if UWP
1277
        if (false) {
1278
#else  // UWP
1279
        wchar_t *wPath;
1280
        if (SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &wPath) ==
1281
            S_OK) {
1282
            std::wstring ws(wPath);
1283
            std::string str = NS_PROJ::WStringToUTF8(ws);
1284
            path = str;
1285
            CoTaskMemFree(wPath);
1286
#endif // UWP
1287
#endif
1288
        } else {
1289
            const char *local_app_data = getenv("LOCALAPPDATA");
1290
            if (!local_app_data) {
1291
                local_app_data = getenv("TEMP");
1292
                if (!local_app_data) {
1293
                    local_app_data = "c:/users";
1294
                }
1295
            }
1296
            path = local_app_data;
1297
        }
1298
#else
1299
4.21k
        const char *xdg_data_home = getenv("XDG_DATA_HOME");
1300
4.21k
        if (xdg_data_home != nullptr) {
1301
0
            path = xdg_data_home;
1302
4.21k
        } else {
1303
4.21k
            const char *home = getenv("HOME");
1304
4.21k
            if (home && access(home, W_OK) == 0) {
1305
#if defined(__MACH__) && defined(__APPLE__)
1306
                path = std::string(home) + "/Library/Application Support";
1307
#else
1308
4.21k
                path = std::string(home) + "/.local/share";
1309
4.21k
#endif
1310
4.21k
            } else {
1311
0
                path = "/tmp";
1312
0
            }
1313
4.21k
        }
1314
4.21k
#endif
1315
4.21k
        path += "/proj";
1316
4.21k
        ctx->user_writable_directory = std::move(path);
1317
4.21k
    }
1318
17.6k
    if (create != FALSE) {
1319
0
        CreateDirectoryRecursively(ctx, ctx->user_writable_directory);
1320
0
    }
1321
17.6k
    return ctx->user_writable_directory.c_str();
1322
17.6k
}
1323
1324
// ---------------------------------------------------------------------------
1325
1326
/** Set the PROJ user writable directory for downloadable resource files, such
1327
 * as datum shift grids.
1328
 *
1329
 * If not explicitly set, the following locations are used:
1330
 * <ul>
1331
 * <li>on Windows, ${LOCALAPPDATA}/proj</li>
1332
 * <li>on macOS, ${HOME}/Library/Application Support/proj</li>
1333
 * <li>on other platforms (Linux), ${XDG_DATA_HOME}/proj if XDG_DATA_HOME is
1334
 * defined. Else ${HOME}/.local/share/proj</li>
1335
 * </ul>
1336
 *
1337
 * @param ctx PROJ context, or NULL
1338
 * @param path Path to the PROJ user writable directory. If set to NULL, the
1339
 *             default location will be used.
1340
 * @param create If set to TRUE, create the directory if it does not exist
1341
 * already.
1342
 * @since 9.5
1343
 * @see proj_context_get_user_writable_directory()
1344
 */
1345
1346
void proj_context_set_user_writable_directory(PJ_CONTEXT *ctx, const char *path,
1347
0
                                              int create) {
1348
0
    if (!ctx)
1349
0
        ctx = pj_get_default_ctx();
1350
0
    ctx->user_writable_directory = path ? path : "";
1351
0
    if (!path || create) {
1352
0
        proj_context_get_user_writable_directory(ctx, create);
1353
0
    }
1354
0
}
1355
1356
// ---------------------------------------------------------------------------
1357
1358
/** Get the URL endpoint to query for remote grids.
1359
 *
1360
 * @param ctx PROJ context, or NULL
1361
 * @return Endpoint URL. The returned pointer would be invalidated
1362
 * by a later call to proj_context_set_url_endpoint()
1363
 * @since 7.1
1364
 */
1365
0
const char *proj_context_get_url_endpoint(PJ_CONTEXT *ctx) {
1366
0
    if (ctx == nullptr) {
1367
0
        ctx = pj_get_default_ctx();
1368
0
    }
1369
0
    if (!ctx->endpoint.empty()) {
1370
0
        return ctx->endpoint.c_str();
1371
0
    }
1372
0
    pj_load_ini(ctx);
1373
0
    return ctx->endpoint.c_str();
1374
0
}
1375
1376
// ---------------------------------------------------------------------------
1377
1378
//! @cond Doxygen_Suppress
1379
1380
// ---------------------------------------------------------------------------
1381
1382
#ifdef WIN32
1383
static const char dir_chars[] = "/\\";
1384
#else
1385
static const char dir_chars[] = "/";
1386
#endif
1387
1388
321k
static bool is_tilde_slash(const char *name) {
1389
321k
    return *name == '~' && strchr(dir_chars, name[1]);
1390
321k
}
1391
1392
321k
static bool is_rel_or_absolute_filename(const char *name) {
1393
321k
    return strchr(dir_chars, *name) ||
1394
321k
           (*name == '.' && strchr(dir_chars, name[1])) ||
1395
321k
           (!strncmp(name, "..", 2) && strchr(dir_chars, name[2])) ||
1396
321k
           (name[0] != '\0' && name[1] == ':' && strchr(dir_chars, name[2]));
1397
321k
}
1398
1399
// ---------------------------------------------------------------------------
1400
1401
1
static std::string pj_get_relative_share_proj_internal_no_check() {
1402
1
#if defined(_WIN32) || defined(HAVE_LIBDL)
1403
#ifdef _WIN32
1404
    HMODULE hm = NULL;
1405
#if !UWP
1406
    if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
1407
                              GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
1408
                          (LPCSTR)&pj_get_relative_share_proj, &hm) == 0) {
1409
        return std::string();
1410
    }
1411
#endif // UWP
1412
1413
    DWORD path_size = 1024;
1414
1415
    std::wstring wout;
1416
    for (;;) {
1417
        wout.clear();
1418
        wout.resize(path_size);
1419
        DWORD result = GetModuleFileNameW(hm, &wout[0], path_size - 1);
1420
        DWORD last_error = GetLastError();
1421
1422
        if (result == 0) {
1423
            return std::string();
1424
        } else if (result == path_size - 1) {
1425
            if (ERROR_INSUFFICIENT_BUFFER != last_error) {
1426
                return std::string();
1427
            }
1428
            path_size = path_size * 2;
1429
        } else {
1430
            break;
1431
        }
1432
    }
1433
    wout.resize(wcslen(wout.c_str()));
1434
    std::string out = NS_PROJ::WStringToUTF8(wout);
1435
    constexpr char dir_sep = '\\';
1436
#else
1437
1
    Dl_info info;
1438
1
    if (!dladdr((void *)pj_get_relative_share_proj, &info)) {
1439
0
        return std::string();
1440
0
    }
1441
1
    std::string out(info.dli_fname);
1442
1
    constexpr char dir_sep = '/';
1443
    // "optimization" for cmake builds where RUNPATH is set to ${prefix}/lib
1444
1
    out = replaceAll(out, "/bin/../", "/");
1445
1
#ifdef __linux
1446
    // If we get a filename without any path, this is most likely a static
1447
    // binary. Resolve the executable name
1448
1
    if (out.find(dir_sep) == std::string::npos) {
1449
0
        constexpr size_t BUFFER_SIZE = 1024;
1450
0
        std::vector<char> path(BUFFER_SIZE + 1);
1451
0
        ssize_t nResultLen = readlink("/proc/self/exe", &path[0], BUFFER_SIZE);
1452
0
        if (nResultLen >= 0 && static_cast<size_t>(nResultLen) < BUFFER_SIZE) {
1453
0
            out.assign(path.data(), static_cast<size_t>(nResultLen));
1454
0
        }
1455
0
    }
1456
1
#endif
1457
1
    if (starts_with(out, "./"))
1458
0
        out = out.substr(2);
1459
1
#endif
1460
1
    auto pos = out.find_last_of(dir_sep);
1461
1
    if (pos == std::string::npos) {
1462
        // The initial path was something like libproj.so"
1463
0
        out = "../share/proj";
1464
0
        return out;
1465
0
    }
1466
1
    out.resize(pos);
1467
1
    pos = out.find_last_of(dir_sep);
1468
1
    if (pos == std::string::npos) {
1469
        // The initial path was something like bin/libproj.so"
1470
0
        out = "share/proj";
1471
0
        return out;
1472
0
    }
1473
1
    out.resize(pos);
1474
    // The initial path was something like foo/bin/libproj.so"
1475
1
    out += "/share/proj";
1476
1
    return out;
1477
#else
1478
    return std::string();
1479
#endif
1480
1
}
1481
1482
static std::string
1483
1
pj_get_relative_share_proj_internal_check_exists(PJ_CONTEXT *ctx) {
1484
1
    if (ctx == nullptr) {
1485
0
        ctx = pj_get_default_ctx();
1486
0
    }
1487
1
    std::string path(pj_get_relative_share_proj_internal_no_check());
1488
1
    if (!path.empty() && NS_PROJ::FileManager::exists(ctx, path.c_str())) {
1489
0
        return path;
1490
0
    }
1491
1
    return std::string();
1492
1
}
1493
1494
17.6k
std::string pj_get_relative_share_proj(PJ_CONTEXT *ctx) {
1495
17.6k
    static std::string path(
1496
17.6k
        pj_get_relative_share_proj_internal_check_exists(ctx));
1497
17.6k
    return path;
1498
17.6k
}
1499
1500
// ---------------------------------------------------------------------------
1501
1502
static bool get_path_from_relative_share_proj(PJ_CONTEXT *ctx, const char *name,
1503
17.6k
                                              std::string &out) {
1504
17.6k
    out = pj_get_relative_share_proj(ctx);
1505
17.6k
    if (out.empty()) {
1506
17.6k
        return false;
1507
17.6k
    }
1508
0
    out += '/';
1509
0
    out += name;
1510
1511
0
    return NS_PROJ::FileManager::exists(ctx, out.c_str());
1512
17.6k
}
1513
1514
/************************************************************************/
1515
/*                      pj_open_lib_internal()                          */
1516
/************************************************************************/
1517
1518
#ifdef WIN32
1519
static const char dirSeparator = ';';
1520
#else
1521
static const char dirSeparator = ':';
1522
#endif
1523
1524
static const char *proj_data_name =
1525
#ifdef PROJ_DATA
1526
    PROJ_DATA;
1527
#else
1528
    nullptr;
1529
#endif
1530
1531
#ifdef PROJ_DATA_ENV_VAR_TRIED_LAST
1532
static bool gbPROJ_DATA_ENV_VAR_TRIED_LAST = true;
1533
#else
1534
static bool gbPROJ_DATA_ENV_VAR_TRIED_LAST = false;
1535
#endif
1536
1537
17.6k
static bool dontReadUserWritableDirectory() {
1538
    // Env var mostly for testing purposes and being independent from
1539
    // an existing installation
1540
17.6k
    const char *envVar = getenv("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY");
1541
17.6k
    return envVar != nullptr && envVar[0] != '\0';
1542
17.6k
}
1543
1544
static void *pj_open_lib_internal(
1545
    PJ_CONTEXT *ctx, const char *name, const char *mode,
1546
    void *(*open_file)(PJ_CONTEXT *, const char *, const char *),
1547
101k
    char *out_full_filename, size_t out_full_filename_size) {
1548
101k
    try {
1549
101k
        std::string fname;
1550
101k
        void *fid = nullptr;
1551
101k
        const char *tmpname = nullptr;
1552
101k
        std::string projLib;
1553
1554
101k
        if (ctx == nullptr) {
1555
0
            ctx = pj_get_default_ctx();
1556
0
        }
1557
1558
101k
        if (out_full_filename != nullptr && out_full_filename_size > 0)
1559
4.32k
            out_full_filename[0] = '\0';
1560
1561
101k
        auto open_lib_from_paths = [&ctx, open_file, &name, &fname,
1562
101k
                                    &mode](const std::string &projLibPaths) {
1563
0
            void *lib_fid = nullptr;
1564
0
            auto paths = NS_PROJ::internal::split(projLibPaths, dirSeparator);
1565
0
            for (const auto &path : paths) {
1566
0
                fname = NS_PROJ::internal::stripQuotes(path);
1567
0
                fname += DIR_CHAR;
1568
0
                fname += name;
1569
0
                lib_fid = open_file(ctx, fname.c_str(), mode);
1570
0
                if (lib_fid)
1571
0
                    break;
1572
0
            }
1573
0
            return lib_fid;
1574
0
        };
1575
1576
        /* check if ~/name */
1577
101k
        if (is_tilde_slash(name))
1578
60
            if (const char *home = getenv("HOME")) {
1579
60
                fname = home;
1580
60
                fname += DIR_CHAR;
1581
60
                fname += name;
1582
60
            } else
1583
0
                return nullptr;
1584
1585
        /* or fixed path: /name, ./name or ../name  */
1586
101k
        else if (is_rel_or_absolute_filename(name)) {
1587
31.7k
            fname = name;
1588
#ifdef _WIN32
1589
            try {
1590
                NS_PROJ::UTF8ToWString(name);
1591
            } catch (const std::exception &) {
1592
                fname = NS_PROJ::Win32Recode(name, CP_ACP, CP_UTF8);
1593
            }
1594
#endif
1595
31.7k
        }
1596
1597
70.0k
        else if (starts_with(name, "http://") || starts_with(name, "https://"))
1598
5.28k
            fname = name;
1599
1600
        /* or try to use application provided file finder */
1601
64.7k
        else if (ctx->file_finder != nullptr &&
1602
64.7k
                 (tmpname = ctx->file_finder(
1603
0
                      ctx, name, ctx->file_finder_user_data)) != nullptr) {
1604
0
            fname = tmpname;
1605
0
        }
1606
1607
        /* The user has search paths set */
1608
64.7k
        else if (!ctx->search_paths.empty()) {
1609
3.30M
            for (const auto &path : ctx->search_paths) {
1610
3.30M
                try {
1611
3.30M
                    fname = path;
1612
3.30M
                    fname += DIR_CHAR;
1613
3.30M
                    fname += name;
1614
3.30M
                    fid = open_file(ctx, fname.c_str(), mode);
1615
3.30M
                } catch (const std::exception &) {
1616
0
                }
1617
3.30M
                if (fid)
1618
0
                    break;
1619
3.30M
            }
1620
47.1k
        }
1621
1622
17.6k
        else if (!dontReadUserWritableDirectory() &&
1623
17.6k
                 (fid = open_file(
1624
17.6k
                      ctx,
1625
17.6k
                      (std::string(proj_context_get_user_writable_directory(
1626
17.6k
                           ctx, false)) +
1627
17.6k
                       DIR_CHAR + name)
1628
17.6k
                          .c_str(),
1629
17.6k
                      mode)) != nullptr) {
1630
0
            fname = proj_context_get_user_writable_directory(ctx, false);
1631
0
            fname += DIR_CHAR;
1632
0
            fname += name;
1633
0
        }
1634
1635
        /* if the environment PROJ_DATA defined, and *not* tried as last
1636
           possibility */
1637
17.6k
        else if (!gbPROJ_DATA_ENV_VAR_TRIED_LAST &&
1638
17.6k
                 !(projLib = NS_PROJ::FileManager::getProjDataEnvVar(ctx))
1639
17.6k
                      .empty()) {
1640
0
            fid = open_lib_from_paths(projLib);
1641
0
        }
1642
1643
17.6k
        else if (get_path_from_relative_share_proj(ctx, name, fname)) {
1644
            /* check if it lives in a ../share/proj dir of the proj dll */
1645
17.6k
        } else if (proj_data_name != nullptr &&
1646
17.6k
                   (fid = open_file(
1647
17.6k
                        ctx,
1648
17.6k
                        (std::string(proj_data_name) + DIR_CHAR + name).c_str(),
1649
17.6k
                        mode)) != nullptr) {
1650
1651
            /* or hardcoded path */
1652
0
            fname = proj_data_name;
1653
0
            fname += DIR_CHAR;
1654
0
            fname += name;
1655
0
        }
1656
1657
        /* if the environment PROJ_DATA defined, and tried as last possibility
1658
         */
1659
17.6k
        else if (gbPROJ_DATA_ENV_VAR_TRIED_LAST &&
1660
17.6k
                 !(projLib = NS_PROJ::FileManager::getProjDataEnvVar(ctx))
1661
0
                      .empty()) {
1662
0
            fid = open_lib_from_paths(projLib);
1663
0
        }
1664
1665
17.6k
        else {
1666
            /* just try it bare bones */
1667
17.6k
            fname = name;
1668
17.6k
        }
1669
1670
101k
        if (fid != nullptr ||
1671
101k
            (fid = open_file(ctx, fname.c_str(), mode)) != nullptr) {
1672
10.3k
            if (out_full_filename != nullptr && out_full_filename_size > 0) {
1673
                // cppcheck-suppress nullPointer
1674
0
                strncpy(out_full_filename, fname.c_str(),
1675
0
                        out_full_filename_size);
1676
0
                out_full_filename[out_full_filename_size - 1] = '\0';
1677
0
            }
1678
10.3k
            errno = 0;
1679
10.3k
        }
1680
1681
101k
#if EMBED_RESOURCE_FILES
1682
101k
        if (!fid && fname != name && name[0] != '.' && name[0] != '/' &&
1683
101k
            name[0] != '~' && !starts_with(name, "http://") &&
1684
101k
            !starts_with(name, "https://")) {
1685
38.6k
            fid = open_file(ctx, name, mode);
1686
38.6k
            if (fid) {
1687
75
                if (out_full_filename != nullptr &&
1688
75
                    out_full_filename_size > 0) {
1689
                    // cppcheck-suppress nullPointer
1690
0
                    strncpy(out_full_filename, name, out_full_filename_size);
1691
0
                    out_full_filename[out_full_filename_size - 1] = '\0';
1692
0
                }
1693
75
                fname = name;
1694
75
                errno = 0;
1695
75
            }
1696
38.6k
        }
1697
101k
#endif
1698
1699
101k
        if (ctx->last_errno == 0 && errno != 0)
1700
89.2k
            proj_context_errno_set(ctx, errno);
1701
1702
101k
        pj_log(ctx, PJ_LOG_DEBUG, "pj_open_lib(%s): call fopen(%s) - %s", name,
1703
101k
               fname.c_str(), fid == nullptr ? "failed" : "succeeded");
1704
1705
101k
        return (fid);
1706
101k
    } catch (const std::exception &) {
1707
1708
0
        pj_log(ctx, PJ_LOG_DEBUG, "pj_open_lib(%s): out of memory", name);
1709
1710
0
        return nullptr;
1711
0
    }
1712
101k
}
1713
1714
/************************************************************************/
1715
/*                  pj_get_default_searchpaths()                        */
1716
/************************************************************************/
1717
1718
0
std::vector<std::string> pj_get_default_searchpaths(PJ_CONTEXT *ctx) {
1719
0
    std::vector<std::string> ret;
1720
1721
    // Env var mostly for testing purposes and being independent from
1722
    // an existing installation
1723
0
    const char *ignoreUserWritableDirectory =
1724
0
        getenv("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY");
1725
0
    if (ignoreUserWritableDirectory == nullptr ||
1726
0
        ignoreUserWritableDirectory[0] == '\0') {
1727
0
        ret.push_back(proj_context_get_user_writable_directory(ctx, false));
1728
0
    }
1729
1730
0
    std::string envPROJ_DATA = NS_PROJ::FileManager::getProjDataEnvVar(ctx);
1731
0
    std::string relativeSharedProj = pj_get_relative_share_proj(ctx);
1732
1733
0
    if (gbPROJ_DATA_ENV_VAR_TRIED_LAST) {
1734
/* Situation where PROJ_DATA environment variable is tried in last */
1735
0
#ifdef PROJ_DATA
1736
0
        ret.push_back(PROJ_DATA);
1737
0
#endif
1738
0
        if (!relativeSharedProj.empty()) {
1739
0
            ret.push_back(std::move(relativeSharedProj));
1740
0
        }
1741
0
        if (!envPROJ_DATA.empty()) {
1742
0
            ret.push_back(std::move(envPROJ_DATA));
1743
0
        }
1744
0
    } else {
1745
        /* Situation where PROJ_DATA environment variable is used if defined */
1746
0
        if (!envPROJ_DATA.empty()) {
1747
0
            ret.push_back(std::move(envPROJ_DATA));
1748
0
        } else {
1749
0
            if (!relativeSharedProj.empty()) {
1750
0
                ret.push_back(std::move(relativeSharedProj));
1751
0
            }
1752
0
#ifdef PROJ_DATA
1753
0
            ret.push_back(PROJ_DATA);
1754
0
#endif
1755
0
        }
1756
0
    }
1757
1758
0
    return ret;
1759
0
}
1760
1761
/************************************************************************/
1762
/*                  pj_open_file_with_manager()                         */
1763
/************************************************************************/
1764
1765
static void *pj_open_file_with_manager(PJ_CONTEXT *ctx, const char *name,
1766
3.47M
                                       const char * /* mode */) {
1767
3.47M
    return NS_PROJ::FileManager::open(ctx, name, NS_PROJ::FileAccess::READ_ONLY)
1768
3.47M
        .release();
1769
3.47M
}
1770
1771
// ---------------------------------------------------------------------------
1772
1773
58.3k
static NS_PROJ::io::DatabaseContextPtr getDBcontext(PJ_CONTEXT *ctx) {
1774
58.3k
    try {
1775
58.3k
        return ctx->get_cpp_context()->getDatabaseContext().as_nullable();
1776
58.3k
    } catch (const std::exception &e) {
1777
0
        pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
1778
0
        return nullptr;
1779
0
    }
1780
58.3k
}
1781
1782
/************************************************************************/
1783
/*                 FileManager::open_resource_file()                    */
1784
/************************************************************************/
1785
1786
std::unique_ptr<NS_PROJ::File>
1787
NS_PROJ::FileManager::open_resource_file(PJ_CONTEXT *ctx, const char *name,
1788
                                         char *out_full_filename,
1789
97.6k
                                         size_t out_full_filename_size) {
1790
1791
97.6k
    if (ctx == nullptr) {
1792
0
        ctx = pj_get_default_ctx();
1793
0
    }
1794
1795
97.6k
    auto file =
1796
97.6k
        std::unique_ptr<NS_PROJ::File>(reinterpret_cast<NS_PROJ::File *>(
1797
97.6k
            pj_open_lib_internal(ctx, name, "rb", pj_open_file_with_manager,
1798
97.6k
                                 out_full_filename, out_full_filename_size)));
1799
1800
    // Retry with the new proj grid name if the file name doesn't end with .tif
1801
97.6k
    std::string tmpString; // keep it in this upper scope !
1802
97.6k
    if (file == nullptr && !is_tilde_slash(name) &&
1803
97.6k
        !is_rel_or_absolute_filename(name) && !starts_with(name, "http://") &&
1804
97.6k
        !starts_with(name, "https://") && strcmp(name, "proj.db") != 0 &&
1805
97.6k
        strstr(name, ".tif") == nullptr) {
1806
1807
54.0k
        auto dbContext = getDBcontext(ctx);
1808
54.0k
        if (dbContext) {
1809
54.0k
            try {
1810
54.0k
                auto filename = dbContext->getProjGridName(name);
1811
54.0k
                if (!filename.empty()) {
1812
5
                    file.reset(reinterpret_cast<NS_PROJ::File *>(
1813
5
                        pj_open_lib_internal(ctx, filename.c_str(), "rb",
1814
5
                                             pj_open_file_with_manager,
1815
5
                                             out_full_filename,
1816
5
                                             out_full_filename_size)));
1817
5
                    if (file) {
1818
0
                        proj_context_errno_set(ctx, 0);
1819
5
                    } else {
1820
                        // For final network access attempt, use the new
1821
                        // name.
1822
5
                        tmpString = std::move(filename);
1823
5
                        name = tmpString.c_str();
1824
5
                    }
1825
5
                }
1826
54.0k
            } catch (const std::exception &e) {
1827
0
                pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
1828
0
                return nullptr;
1829
0
            }
1830
54.0k
        }
1831
54.0k
    }
1832
    // Retry with the old proj grid name if the file name ends with .tif
1833
43.5k
    else if (file == nullptr && !is_tilde_slash(name) &&
1834
43.5k
             !is_rel_or_absolute_filename(name) &&
1835
43.5k
             !starts_with(name, "http://") && !starts_with(name, "https://") &&
1836
43.5k
             strstr(name, ".tif") != nullptr) {
1837
1838
4.27k
        auto dbContext = getDBcontext(ctx);
1839
4.27k
        if (dbContext) {
1840
4.27k
            try {
1841
4.27k
                const auto filename = dbContext->getOldProjGridName(name);
1842
4.27k
                if (!filename.empty()) {
1843
65
                    file.reset(reinterpret_cast<NS_PROJ::File *>(
1844
65
                        pj_open_lib_internal(ctx, filename.c_str(), "rb",
1845
65
                                             pj_open_file_with_manager,
1846
65
                                             out_full_filename,
1847
65
                                             out_full_filename_size)));
1848
65
                    if (file) {
1849
0
                        proj_context_errno_set(ctx, 0);
1850
0
                    }
1851
65
                }
1852
4.27k
            } catch (const std::exception &e) {
1853
0
                pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
1854
0
                return nullptr;
1855
0
            }
1856
4.27k
        }
1857
4.27k
    }
1858
1859
97.6k
    if (file == nullptr && !is_tilde_slash(name) &&
1860
97.6k
        !is_rel_or_absolute_filename(name) && !starts_with(name, "http://") &&
1861
97.6k
        !starts_with(name, "https://") &&
1862
97.6k
        proj_context_is_network_enabled(ctx)) {
1863
1864
0
        std::string remote_file;
1865
0
        auto dbContext = getDBcontext(ctx);
1866
0
        if (dbContext) {
1867
0
            try {
1868
0
                std::string fullFilename, packageName, url;
1869
0
                bool directDownload = false;
1870
0
                bool openLicense = false;
1871
0
                bool gridAvailable = false;
1872
0
                proj_context_set_enable_network(ctx,
1873
0
                                                false); // prevent recursion
1874
0
                const bool found = dbContext->lookForGridInfo(
1875
0
                    name, /* considerKnownGridsAsAvailable = */ true,
1876
0
                    fullFilename, packageName, url, directDownload, openLicense,
1877
0
                    gridAvailable);
1878
0
                proj_context_set_enable_network(ctx, true);
1879
0
                if (found && !url.empty() && directDownload) {
1880
0
                    remote_file = url;
1881
0
                    if (starts_with(url, "https://cdn.proj.org/")) {
1882
0
                        std::string endpoint =
1883
0
                            proj_context_get_url_endpoint(ctx);
1884
0
                        if (!endpoint.empty()) {
1885
0
                            remote_file = std::move(endpoint);
1886
0
                            if (remote_file.back() != '/') {
1887
0
                                remote_file += '/';
1888
0
                            }
1889
0
                            remote_file += name;
1890
0
                        }
1891
0
                    }
1892
0
                }
1893
0
            } catch (const std::exception &e) {
1894
0
                proj_context_set_enable_network(ctx, true);
1895
0
                pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
1896
0
                return nullptr;
1897
0
            }
1898
0
        }
1899
0
        if (remote_file.empty()) {
1900
0
            remote_file = proj_context_get_url_endpoint(ctx);
1901
0
            if (!remote_file.empty()) {
1902
0
                if (remote_file.back() != '/') {
1903
0
                    remote_file += '/';
1904
0
                }
1905
0
                remote_file += name;
1906
0
            }
1907
0
        }
1908
0
        if (!remote_file.empty()) {
1909
0
            file =
1910
0
                open(ctx, remote_file.c_str(), NS_PROJ::FileAccess::READ_ONLY);
1911
0
            if (file) {
1912
0
                if (out_full_filename) {
1913
0
                    strncpy(out_full_filename, remote_file.c_str(),
1914
0
                            out_full_filename_size);
1915
0
                    out_full_filename[out_full_filename_size - 1] = '\0';
1916
0
                }
1917
0
                pj_log(ctx, PJ_LOG_DEBUG, "Using %s", remote_file.c_str());
1918
0
                proj_context_errno_set(ctx, 0);
1919
0
            }
1920
0
        }
1921
0
    }
1922
97.6k
    return file;
1923
97.6k
}
1924
1925
/************************************************************************/
1926
/*                           pj_find_file()                             */
1927
/************************************************************************/
1928
1929
/** Returns the full filename corresponding to a proj resource file specified
1930
 *  as a short filename.
1931
 *
1932
 * @param ctx context.
1933
 * @param short_filename short filename (e.g. us_nga_egm96_15.tif).
1934
 *                       Must not be NULL.
1935
 * @param out_full_filename output buffer, of size out_full_filename_size, that
1936
 *                          will receive the full filename on success.
1937
 *                          Will be zero-terminated.
1938
 * @param out_full_filename_size size of out_full_filename.
1939
 * @return 1 if the file was found, 0 otherwise.
1940
 */
1941
int pj_find_file(PJ_CONTEXT *ctx, const char *short_filename,
1942
22.0k
                 char *out_full_filename, size_t out_full_filename_size) {
1943
22.0k
    const auto iter = ctx->lookupedFiles.find(short_filename);
1944
22.0k
    if (iter != ctx->lookupedFiles.end()) {
1945
17.7k
        if (iter->second.empty()) {
1946
17.7k
            out_full_filename[0] = 0;
1947
17.7k
            return 0;
1948
17.7k
        }
1949
0
        snprintf(out_full_filename, out_full_filename_size, "%s",
1950
0
                 iter->second.c_str());
1951
0
        return 1;
1952
17.7k
    }
1953
4.32k
    const bool old_network_enabled =
1954
4.32k
        proj_context_is_network_enabled(ctx) != FALSE;
1955
4.32k
    if (old_network_enabled)
1956
0
        proj_context_set_enable_network(ctx, false);
1957
4.32k
    auto file = NS_PROJ::FileManager::open_resource_file(
1958
4.32k
        ctx, short_filename, out_full_filename, out_full_filename_size);
1959
4.32k
    if (old_network_enabled)
1960
0
        proj_context_set_enable_network(ctx, true);
1961
4.32k
    if (file) {
1962
0
        ctx->lookupedFiles[short_filename] = out_full_filename;
1963
4.32k
    } else {
1964
4.32k
        ctx->lookupedFiles[short_filename] = std::string();
1965
4.32k
    }
1966
4.32k
    return file != nullptr;
1967
22.0k
}
1968
1969
/************************************************************************/
1970
/*                              trim()                                  */
1971
/************************************************************************/
1972
1973
117k
static std::string trim(const std::string &s) {
1974
117k
    const auto first = s.find_first_not_of(' ');
1975
117k
    const auto last = s.find_last_not_of(' ');
1976
117k
    if (first == std::string::npos || last == std::string::npos) {
1977
0
        return std::string();
1978
0
    }
1979
117k
    return s.substr(first, last - first + 1);
1980
117k
}
1981
1982
/************************************************************************/
1983
/*                            pj_load_ini()                             */
1984
/************************************************************************/
1985
1986
90.8k
void pj_load_ini(PJ_CONTEXT *ctx) {
1987
90.8k
    if (ctx->iniFileLoaded)
1988
86.6k
        return;
1989
1990
    // Start reading environment variables that have priority over the
1991
    // .ini file
1992
4.21k
    const char *proj_network = getenv("PROJ_NETWORK");
1993
4.21k
    if (proj_network && proj_network[0] != '\0') {
1994
0
        ctx->networking.enabled = ci_equal(proj_network, "ON") ||
1995
0
                                  ci_equal(proj_network, "YES") ||
1996
0
                                  ci_equal(proj_network, "TRUE");
1997
4.21k
    } else {
1998
4.21k
        proj_network = nullptr;
1999
4.21k
    }
2000
2001
4.21k
    const char *endpoint_from_env = getenv("PROJ_NETWORK_ENDPOINT");
2002
4.21k
    if (endpoint_from_env && endpoint_from_env[0] != '\0') {
2003
0
        ctx->endpoint = endpoint_from_env;
2004
0
    }
2005
2006
    // Custom path to SSL certificates.
2007
4.21k
    const char *ca_bundle_path = getenv("PROJ_CURL_CA_BUNDLE");
2008
4.21k
    if (ca_bundle_path == nullptr) {
2009
        // Name of environment variable used by the curl binary
2010
4.21k
        ca_bundle_path = getenv("CURL_CA_BUNDLE");
2011
4.21k
    }
2012
4.21k
    if (ca_bundle_path == nullptr) {
2013
        // Name of environment variable used by the curl binary (tested
2014
        // after CURL_CA_BUNDLE
2015
4.21k
        ca_bundle_path = getenv("SSL_CERT_FILE");
2016
4.21k
    }
2017
4.21k
    if (ca_bundle_path != nullptr) {
2018
0
        ctx->ca_bundle_path = ca_bundle_path;
2019
0
    }
2020
2021
    // Load default value for errorIfBestTransformationNotAvailableDefault
2022
    // from environment first
2023
4.21k
    const char *proj_only_best_default = getenv("PROJ_ONLY_BEST_DEFAULT");
2024
4.21k
    if (proj_only_best_default && proj_only_best_default[0] != '\0') {
2025
0
        ctx->warnIfBestTransformationNotAvailableDefault = false;
2026
0
        ctx->errorIfBestTransformationNotAvailableDefault =
2027
0
            ci_equal(proj_only_best_default, "ON") ||
2028
0
            ci_equal(proj_only_best_default, "YES") ||
2029
0
            ci_equal(proj_only_best_default, "TRUE");
2030
0
    }
2031
2032
4.21k
    const char *native_ca = getenv("PROJ_NATIVE_CA");
2033
4.21k
    if (native_ca && native_ca[0] != '\0') {
2034
0
        ctx->native_ca = ci_equal(native_ca, "ON") ||
2035
0
                         ci_equal(native_ca, "YES") ||
2036
0
                         ci_equal(native_ca, "TRUE");
2037
4.21k
    } else {
2038
4.21k
        native_ca = nullptr;
2039
4.21k
    }
2040
2041
4.21k
    ctx->iniFileLoaded = true;
2042
4.21k
    std::string content;
2043
4.21k
    auto file = std::unique_ptr<NS_PROJ::File>(
2044
4.21k
        reinterpret_cast<NS_PROJ::File *>(pj_open_lib_internal(
2045
4.21k
            ctx, "proj.ini", "rb", pj_open_file_with_manager, nullptr, 0)));
2046
4.21k
    if (file) {
2047
4.21k
        file->seek(0, SEEK_END);
2048
4.21k
        const auto filesize = file->tell();
2049
4.21k
        if (filesize == 0 || filesize > 100 * 1024U)
2050
0
            return;
2051
4.21k
        file->seek(0, SEEK_SET);
2052
4.21k
        content.resize(static_cast<size_t>(filesize));
2053
4.21k
        const auto nread = file->read(&content[0], content.size());
2054
4.21k
        if (nread != content.size())
2055
0
            return;
2056
4.21k
    }
2057
4.21k
    content += '\n';
2058
4.21k
    size_t pos = 0;
2059
210k
    while (pos != std::string::npos) {
2060
206k
        const auto eol = content.find_first_of("\r\n", pos);
2061
206k
        if (eol == std::string::npos) {
2062
0
            break;
2063
0
        }
2064
2065
206k
        const auto equal = content.find('=', pos);
2066
206k
        if (equal < eol) {
2067
58.9k
            const auto key = trim(content.substr(pos, equal - pos));
2068
58.9k
            auto value = trim(content.substr(equal + 1, eol - (equal + 1)));
2069
58.9k
            if (ctx->endpoint.empty() && key == "cdn_endpoint") {
2070
4.21k
                ctx->endpoint = std::move(value);
2071
54.7k
            } else if (proj_network == nullptr && key == "network") {
2072
4.21k
                ctx->networking.enabled = ci_equal(value, "ON") ||
2073
4.21k
                                          ci_equal(value, "YES") ||
2074
4.21k
                                          ci_equal(value, "TRUE");
2075
50.5k
            } else if (key == "cache_enabled") {
2076
4.21k
                ctx->gridChunkCache.enabled = ci_equal(value, "ON") ||
2077
4.21k
                                              ci_equal(value, "YES") ||
2078
4.21k
                                              ci_equal(value, "TRUE");
2079
46.3k
            } else if (key == "cache_size_MB") {
2080
4.21k
                const int val = atoi(value.c_str());
2081
4.21k
                ctx->gridChunkCache.max_size =
2082
4.21k
                    val > 0 ? static_cast<long long>(val) * 1024 * 1024 : -1;
2083
42.1k
            } else if (key == "cache_ttl_sec") {
2084
4.21k
                ctx->gridChunkCache.ttl = atoi(value.c_str());
2085
37.9k
            } else if (key == "tmerc_default_algo") {
2086
4.21k
                if (value == "auto") {
2087
0
                    ctx->defaultTmercAlgo = TMercAlgo::AUTO;
2088
4.21k
                } else if (value == "evenden_snyder") {
2089
0
                    ctx->defaultTmercAlgo = TMercAlgo::EVENDEN_SNYDER;
2090
4.21k
                } else if (value == "poder_engsager") {
2091
4.21k
                    ctx->defaultTmercAlgo = TMercAlgo::PODER_ENGSAGER;
2092
4.21k
                } else {
2093
0
                    pj_log(
2094
0
                        ctx, PJ_LOG_ERROR,
2095
0
                        "pj_load_ini(): Invalid value for tmerc_default_algo");
2096
0
                }
2097
33.7k
            } else if (ca_bundle_path == nullptr && key == "ca_bundle_path") {
2098
0
                ctx->ca_bundle_path = std::move(value);
2099
33.7k
            } else if (proj_only_best_default == nullptr &&
2100
33.7k
                       key == "only_best_default") {
2101
4.21k
                ctx->warnIfBestTransformationNotAvailableDefault = false;
2102
4.21k
                ctx->errorIfBestTransformationNotAvailableDefault =
2103
4.21k
                    ci_equal(value, "ON") || ci_equal(value, "YES") ||
2104
4.21k
                    ci_equal(value, "TRUE");
2105
29.4k
            } else if (native_ca == nullptr && key == "native_ca") {
2106
0
                ctx->native_ca = ci_equal(value, "ON") ||
2107
0
                                 ci_equal(value, "YES") ||
2108
0
                                 ci_equal(value, "TRUE");
2109
0
            }
2110
58.9k
        }
2111
2112
206k
        pos = content.find_first_not_of("\r\n", eol);
2113
206k
    }
2114
4.21k
}
2115
2116
//! @endcond
2117
2118
/************************************************************************/
2119
/*                   proj_context_set_file_finder()                     */
2120
/************************************************************************/
2121
2122
/** \brief Assign a file finder callback to a context.
2123
 *
2124
 * This callback will be used whenever PROJ must open one of its resource files
2125
 * (proj.db database, grids, etc...)
2126
 *
2127
 * The callback will be called with the context currently in use at the moment
2128
 * where it is used (not necessarily the one provided during this call), and
2129
 * with the provided user_data (which may be NULL).
2130
 * The user_data must remain valid during the whole lifetime of the context.
2131
 *
2132
 * A finder set on the default context will be inherited by contexts created
2133
 * later.
2134
 *
2135
 * @param ctx PROJ context, or NULL for the default context.
2136
 * @param finder Finder callback. May be NULL
2137
 * @param user_data User data provided to the finder callback. May be NULL.
2138
 *
2139
 * @since PROJ 6.0
2140
 */
2141
void proj_context_set_file_finder(PJ_CONTEXT *ctx, proj_file_finder finder,
2142
0
                                  void *user_data) {
2143
0
    if (!ctx)
2144
0
        ctx = pj_get_default_ctx();
2145
0
    if (!ctx)
2146
0
        return;
2147
0
    ctx->file_finder = finder;
2148
0
    ctx->file_finder_user_data = user_data;
2149
0
}
2150
2151
/************************************************************************/
2152
/*                  proj_context_set_search_paths()                     */
2153
/************************************************************************/
2154
2155
/** \brief Sets search paths.
2156
 *
2157
 * Those search paths will be used whenever PROJ must open one of its resource
2158
 * files
2159
 * (proj.db database, grids, etc...)
2160
 *
2161
 * If set on the default context, they will be inherited by contexts created
2162
 * later.
2163
 *
2164
 * Starting with PROJ 7.0, the path(s) should be encoded in UTF-8.
2165
 *
2166
 * @param ctx PROJ context, or NULL for the default context.
2167
 * @param count_paths Number of paths. 0 if paths == NULL.
2168
 * @param paths Paths. May be NULL.
2169
 *
2170
 * @since PROJ 6.0
2171
 */
2172
void proj_context_set_search_paths(PJ_CONTEXT *ctx, int count_paths,
2173
394
                                   const char *const *paths) {
2174
394
    if (!ctx)
2175
0
        ctx = pj_get_default_ctx();
2176
394
    if (!ctx)
2177
0
        return;
2178
394
    try {
2179
394
        std::vector<std::string> vector_of_paths;
2180
6.40k
        for (int i = 0; i < count_paths; i++) {
2181
6.01k
            vector_of_paths.emplace_back(paths[i]);
2182
6.01k
        }
2183
394
        ctx->set_search_paths(vector_of_paths);
2184
394
    } catch (const std::exception &) {
2185
0
    }
2186
394
}
2187
2188
/************************************************************************/
2189
/*                  proj_context_set_ca_bundle_path()                   */
2190
/************************************************************************/
2191
2192
/** \brief Sets CA Bundle path.
2193
 *
2194
 * Those CA Bundle path will be used by PROJ when curl and PROJ_NETWORK
2195
 * are enabled.
2196
 *
2197
 * If set on the default context, they will be inherited by contexts created
2198
 * later.
2199
 *
2200
 * The path should be encoded in UTF-8.
2201
 *
2202
 * @param ctx PROJ context, or NULL for the default context.
2203
 * @param path Path. May be NULL.
2204
 *
2205
 * @since PROJ 7.2
2206
 */
2207
0
void proj_context_set_ca_bundle_path(PJ_CONTEXT *ctx, const char *path) {
2208
0
    if (!ctx)
2209
0
        ctx = pj_get_default_ctx();
2210
0
    if (!ctx)
2211
0
        return;
2212
0
    pj_load_ini(ctx);
2213
0
    try {
2214
0
        ctx->set_ca_bundle_path(path != nullptr ? path : "");
2215
0
    } catch (const std::exception &) {
2216
0
    }
2217
0
}
2218
2219
// ---------------------------------------------------------------------------
2220
2221
0
void pj_stderr_proj_lib_deprecation_warning() {
2222
0
    if (getenv("PROJ_LIB") != nullptr && getenv("PROJ_DATA") == nullptr) {
2223
0
        fprintf(stderr, "DeprecationWarning: PROJ_LIB environment variable is "
2224
0
                        "deprecated, and will be removed in a future release. "
2225
0
                        "You are encouraged to set PROJ_DATA instead.\n");
2226
0
    }
2227
0
}