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