/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 | } |