diff options
| author | Even Rouault <even.rouault@spatialys.com> | 2020-01-10 21:54:34 +0100 |
|---|---|---|
| committer | Even Rouault <even.rouault@spatialys.com> | 2020-01-11 00:27:49 +0100 |
| commit | f6c424165264eec06af2504800ebfdd5d701efae (patch) | |
| tree | 5512b667a9bd7c751c96990ec32082b8564c916b /src | |
| parent | 90b6685a990b8c4931aafb508853401a89163e78 (diff) | |
| download | PROJ-f6c424165264eec06af2504800ebfdd5d701efae.tar.gz PROJ-f6c424165264eec06af2504800ebfdd5d701efae.zip | |
Use Win32 Unicode APIs and expect all strings to be UTF-8 (fixes #1765)
For backward compatibility, if PROJ_LIB content is found to be not UTF-8 or
pointing to a non existing directory, then an attempt at interpretating it
in the ANSI page encoding is done.
proj_context_set_search_paths() now assumes strings to be in UTF-8, and
functions returning paths will also return values in UTF-8.
Diffstat (limited to 'src')
| -rw-r--r-- | src/4D_api.cpp | 7 | ||||
| -rw-r--r-- | src/Makefile.am | 2 | ||||
| -rw-r--r-- | src/filemanager.cpp | 1317 | ||||
| -rw-r--r-- | src/filemanager.hpp | 18 | ||||
| -rw-r--r-- | src/lib_proj.cmake | 1 | ||||
| -rw-r--r-- | src/open_lib.cpp | 576 | ||||
| -rw-r--r-- | src/proj_internal.h | 1 |
7 files changed, 1265 insertions, 657 deletions
diff --git a/src/4D_api.cpp b/src/4D_api.cpp index 9107723d..6b53363a 100644 --- a/src/4D_api.cpp +++ b/src/4D_api.cpp @@ -1461,10 +1461,10 @@ PJ_INFO proj_info (void) { pj_context_get_user_writable_directory(ctx, false).c_str(), &buf_size); } - const char *envPROJ_LIB = getenv("PROJ_LIB"); - buf = path_append(buf, envPROJ_LIB, &buf_size); + const std::string envPROJ_LIB = NS_PROJ::FileManager::getProjLibEnvVar(ctx); + buf = path_append(buf, envPROJ_LIB.empty() ? nullptr : envPROJ_LIB.c_str(), &buf_size); #ifdef PROJ_LIB - if (envPROJ_LIB == nullptr) { + if (envPROJ_LIB.empty()) { buf = path_append(buf, PROJ_LIB, &buf_size); } #endif @@ -1770,3 +1770,4 @@ PJ_FACTORS proj_factors(PJ *P, PJ_COORD lp) { return factors; } + diff --git a/src/Makefile.am b/src/Makefile.am index 39667509..5e97cb4a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -190,7 +190,7 @@ libproj_la_SOURCES = \ deriv.cpp ell_set.cpp ellps.cpp errno.cpp \ factors.cpp fwd.cpp init.cpp inv.cpp \ list.cpp malloc.cpp mlfn.cpp msfn.cpp proj_mdist.cpp \ - open_lib.cpp param.cpp phi2.cpp pr_list.cpp \ + param.cpp phi2.cpp pr_list.cpp \ qsfn.cpp strerrno.cpp \ tsfn.cpp units.cpp ctx.cpp log.cpp zpoly1.cpp rtodms.cpp \ release.cpp gauss.cpp \ diff --git a/src/filemanager.cpp b/src/filemanager.cpp index 9acea83e..277578d1 100644 --- a/src/filemanager.cpp +++ b/src/filemanager.cpp @@ -33,10 +33,7 @@ #include <stdlib.h> #include <algorithm> -#include <codecvt> -#include <functional> #include <limits> -#include <locale> #include <string> #include "filemanager.hpp" @@ -114,6 +111,580 @@ File::File(const std::string &name) : name_(name) {} File::~File() = default; +#ifdef _WIN32 + +/* The bulk of utf8towc()/utf8fromwc() is derived from the utf.c module from + * FLTK. It was originally downloaded from: + * http://svn.easysw.com/public/fltk/fltk/trunk/src/utf.c + * And already used by GDAL + */ +/************************************************************************/ +/* ==================================================================== */ +/* UTF.C code from FLTK with some modifications. */ +/* ==================================================================== */ +/************************************************************************/ + +/* Set to 1 to turn bad UTF8 bytes into ISO-8859-1. If this is to zero + they are instead turned into the Unicode REPLACEMENT CHARACTER, of + value 0xfffd. + If this is on utf8decode will correctly map most (perhaps all) + human-readable text that is in ISO-8859-1. This may allow you + to completely ignore character sets in your code because virtually + everything is either ISO-8859-1 or UTF-8. +*/ +#define ERRORS_TO_ISO8859_1 1 + +/* Set to 1 to turn bad UTF8 bytes in the 0x80-0x9f range into the + Unicode index for Microsoft's CP1252 character set. You should + also set ERRORS_TO_ISO8859_1. With this a huge amount of more + available text (such as all web pages) are correctly converted + to Unicode. +*/ +#define ERRORS_TO_CP1252 1 + +/* A number of Unicode code points are in fact illegal and should not + be produced by a UTF-8 converter. Turn this on will replace the + bytes in those encodings with errors. If you do this then converting + arbitrary 16-bit data to UTF-8 and then back is not an identity, + which will probably break a lot of software. +*/ +#define STRICT_RFC3629 0 + +#if ERRORS_TO_CP1252 +// Codes 0x80..0x9f from the Microsoft CP1252 character set, translated +// to Unicode: +constexpr unsigned short cp1252[32] = { + 0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f, + 0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178}; +#endif + +/************************************************************************/ +/* utf8decode() */ +/************************************************************************/ + +/* + Decode a single UTF-8 encoded character starting at \e p. The + resulting Unicode value (in the range 0-0x10ffff) is returned, + and \e len is set the number of bytes in the UTF-8 encoding + (adding \e len to \e p will point at the next character). + + If \a p points at an illegal UTF-8 encoding, including one that + would go past \e end, or where a code is uses more bytes than + necessary, then *reinterpret_cast<const unsigned char*>(p) is translated as +though it is + in the Microsoft CP1252 character set and \e len is set to 1. + Treating errors this way allows this to decode almost any + ISO-8859-1 or CP1252 text that has been mistakenly placed where + UTF-8 is expected, and has proven very useful. + + If you want errors to be converted to error characters (as the + standards recommend), adding a test to see if the length is + unexpectedly 1 will work: + +\code + if( *p & 0x80 ) + { // What should be a multibyte encoding. + code = utf8decode(p, end, &len); + if( len<2 ) code = 0xFFFD; // Turn errors into REPLACEMENT CHARACTER. + } + else + { // Handle the 1-byte utf8 encoding: + code = *p; + len = 1; + } +\endcode + + Direct testing for the 1-byte case (as shown above) will also + speed up the scanning of strings where the majority of characters + are ASCII. +*/ +static unsigned utf8decode(const char *p, const char *end, int *len) { + unsigned char c = *reinterpret_cast<const unsigned char *>(p); + if (c < 0x80) { + *len = 1; + return c; +#if ERRORS_TO_CP1252 + } else if (c < 0xa0) { + *len = 1; + return cp1252[c - 0x80]; +#endif + } else if (c < 0xc2) { + goto FAIL; + } + if (p + 1 >= end || (p[1] & 0xc0) != 0x80) + goto FAIL; + if (c < 0xe0) { + *len = 2; + return ((p[0] & 0x1f) << 6) + ((p[1] & 0x3f)); + } else if (c == 0xe0) { + if ((reinterpret_cast<const unsigned char *>(p))[1] < 0xa0) + goto FAIL; + goto UTF8_3; +#if STRICT_RFC3629 + } else if (c == 0xed) { + // RFC 3629 says surrogate chars are illegal. + if ((reinterpret_cast<const unsigned char *>(p))[1] >= 0xa0) + goto FAIL; + goto UTF8_3; + } else if (c == 0xef) { + // 0xfffe and 0xffff are also illegal characters. + if ((reinterpret_cast<const unsigned char *>(p))[1] == 0xbf && + (reinterpret_cast<const unsigned char *>(p))[2] >= 0xbe) + goto FAIL; + goto UTF8_3; +#endif + } else if (c < 0xf0) { + UTF8_3: + if (p + 2 >= end || (p[2] & 0xc0) != 0x80) + goto FAIL; + *len = 3; + return ((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) + ((p[2] & 0x3f)); + } else if (c == 0xf0) { + if ((reinterpret_cast<const unsigned char *>(p))[1] < 0x90) + goto FAIL; + goto UTF8_4; + } else if (c < 0xf4) { + UTF8_4: + if (p + 3 >= end || (p[2] & 0xc0) != 0x80 || (p[3] & 0xc0) != 0x80) + goto FAIL; + *len = 4; +#if STRICT_RFC3629 + // RFC 3629 says all codes ending in fffe or ffff are illegal: + if ((p[1] & 0xf) == 0xf && + (reinterpret_cast<const unsigned char *>(p))[2] == 0xbf && + (reinterpret_cast<const unsigned char *>(p))[3] >= 0xbe) + goto FAIL; +#endif + return ((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) + + ((p[2] & 0x3f) << 6) + ((p[3] & 0x3f)); + } else if (c == 0xf4) { + if ((reinterpret_cast<const unsigned char *>(p))[1] > 0x8f) + goto FAIL; // After 0x10ffff. + goto UTF8_4; + } else { + FAIL: + *len = 1; +#if ERRORS_TO_ISO8859_1 + return c; +#else + return 0xfffd; // Unicode REPLACEMENT CHARACTER +#endif + } +} + +/************************************************************************/ +/* utf8towc() */ +/************************************************************************/ + +/* Convert a UTF-8 sequence into an array of wchar_t. These + are used by some system calls, especially on Windows. + + \a src points at the UTF-8, and \a srclen is the number of bytes to + convert. + + \a dst points at an array to write, and \a dstlen is the number of + locations in this array. At most \a dstlen-1 words will be + written there, plus a 0 terminating word. Thus this function + will never overwrite the buffer and will always return a + zero-terminated string. If \a dstlen is zero then \a dst can be + null and no data is written, but the length is returned. + + The return value is the number of words that \e would be written + to \a dst if it were long enough, not counting the terminating + zero. If the return value is greater or equal to \a dstlen it + indicates truncation, you can then allocate a new array of size + return+1 and call this again. + + Errors in the UTF-8 are converted as though each byte in the + erroneous string is in the Microsoft CP1252 encoding. This allows + ISO-8859-1 text mistakenly identified as UTF-8 to be printed + correctly. + + Notice that sizeof(wchar_t) is 2 on Windows and is 4 on Linux + and most other systems. Where wchar_t is 16 bits, Unicode + characters in the range 0x10000 to 0x10ffff are converted to + "surrogate pairs" which take two words each (this is called UTF-16 + encoding). If wchar_t is 32 bits this rather nasty problem is + avoided. +*/ +static unsigned utf8towc(const char *src, unsigned srclen, wchar_t *dst, + unsigned dstlen) { + const char *p = src; + const char *e = src + srclen; + unsigned count = 0; + if (dstlen) + while (true) { + if (p >= e) { + dst[count] = 0; + return count; + } + if (!(*p & 0x80)) { + // ASCII + dst[count] = *p++; + } else { + int len = 0; + unsigned ucs = utf8decode(p, e, &len); + p += len; +#ifdef _WIN32 + if (ucs < 0x10000) { + dst[count] = static_cast<wchar_t>(ucs); + } else { + // Make a surrogate pair: + if (count + 2 >= dstlen) { + dst[count] = 0; + count += 2; + break; + } + dst[count] = static_cast<wchar_t>( + (((ucs - 0x10000u) >> 10) & 0x3ff) | 0xd800); + dst[++count] = static_cast<wchar_t>((ucs & 0x3ff) | 0xdc00); + } +#else + dst[count] = static_cast<wchar_t>(ucs); +#endif + } + if (++count == dstlen) { + dst[count - 1] = 0; + break; + } + } + // We filled dst, measure the rest: + while (p < e) { + if (!(*p & 0x80)) { + p++; + } else { + int len = 0; +#ifdef _WIN32 + const unsigned ucs = utf8decode(p, e, &len); + p += len; + if (ucs >= 0x10000) + ++count; +#else + utf8decode(p, e, &len); + p += len; +#endif + } + ++count; + } + + return count; +} + +// --------------------------------------------------------------------------- + +struct NonValidUTF8Exception : public std::exception {}; + +// May throw exceptions +static std::wstring UTF8ToWString(const std::string &str) { + std::wstring wstr; + wstr.resize(str.size()); + wstr.resize(utf8towc(str.data(), static_cast<unsigned>(str.size()), + &wstr[0], static_cast<unsigned>(wstr.size()) + 1)); + for (const auto ch : wstr) { + if (ch == 0xfffd) { + throw NonValidUTF8Exception(); + } + } + return wstr; +} + +// --------------------------------------------------------------------------- + +/************************************************************************/ +/* utf8fromwc() */ +/************************************************************************/ +/* Turn "wide characters" as returned by some system calls + (especially on Windows) into UTF-8. + + Up to \a dstlen bytes are written to \a dst, including a null + terminator. The return value is the number of bytes that would be + written, not counting the null terminator. If greater or equal to + \a dstlen then if you malloc a new array of size n+1 you will have + the space needed for the entire string. If \a dstlen is zero then + nothing is written and this call just measures the storage space + needed. + + \a srclen is the number of words in \a src to convert. On Windows + this is not necessarily the number of characters, due to there + possibly being "surrogate pairs" in the UTF-16 encoding used. + On Unix wchar_t is 32 bits and each location is a character. + + On Unix if a src word is greater than 0x10ffff then this is an + illegal character according to RFC 3629. These are converted as + though they are 0xFFFD (REPLACEMENT CHARACTER). Characters in the + range 0xd800 to 0xdfff, or ending with 0xfffe or 0xffff are also + illegal according to RFC 3629. However I encode these as though + they are legal, so that utf8towc will return the original data. + + On Windows "surrogate pairs" are converted to a single character + and UTF-8 encoded (as 4 bytes). Mismatched halves of surrogate + pairs are converted as though they are individual characters. +*/ +static unsigned int utf8fromwc(char *dst, unsigned dstlen, const wchar_t *src, + unsigned srclen) { + unsigned int i = 0; + unsigned int count = 0; + if (dstlen) + while (true) { + if (i >= srclen) { + dst[count] = 0; + return count; + } + unsigned int ucs = src[i++]; + if (ucs < 0x80U) { + dst[count++] = static_cast<char>(ucs); + if (count >= dstlen) { + dst[count - 1] = 0; + break; + } + } else if (ucs < 0x800U) { + // 2 bytes. + if (count + 2 >= dstlen) { + dst[count] = 0; + count += 2; + break; + } + dst[count++] = 0xc0 | static_cast<char>(ucs >> 6); + dst[count++] = 0x80 | static_cast<char>(ucs & 0x3F); +#ifdef _WIN32 + } else if (ucs >= 0xd800 && ucs <= 0xdbff && i < srclen && + src[i] >= 0xdc00 && src[i] <= 0xdfff) { + // Surrogate pair. + unsigned int ucs2 = src[i++]; + ucs = 0x10000U + ((ucs & 0x3ff) << 10) + (ucs2 & 0x3ff); +// All surrogate pairs turn into 4-byte utf8. +#else + } else if (ucs >= 0x10000) { + if (ucs > 0x10ffff) { + ucs = 0xfffd; + goto J1; + } +#endif + if (count + 4 >= dstlen) { + dst[count] = 0; + count += 4; + break; + } + dst[count++] = 0xf0 | static_cast<char>(ucs >> 18); + dst[count++] = 0x80 | static_cast<char>((ucs >> 12) & 0x3F); + dst[count++] = 0x80 | static_cast<char>((ucs >> 6) & 0x3F); + dst[count++] = 0x80 | static_cast<char>(ucs & 0x3F); + } else { +#ifndef _WIN32 + J1: +#endif + // All others are 3 bytes: + if (count + 3 >= dstlen) { + dst[count] = 0; + count += 3; + break; + } + dst[count++] = 0xe0 | static_cast<char>(ucs >> 12); + dst[count++] = 0x80 | static_cast<char>((ucs >> 6) & 0x3F); + dst[count++] = 0x80 | static_cast<char>(ucs & 0x3F); + } + } + + // We filled dst, measure the rest: + while (i < srclen) { + unsigned int ucs = src[i++]; + if (ucs < 0x80U) { + count++; + } else if (ucs < 0x800U) { + // 2 bytes. + count += 2; +#ifdef _WIN32 + } else if (ucs >= 0xd800 && ucs <= 0xdbff && i < srclen - 1 && + src[i + 1] >= 0xdc00 && src[i + 1] <= 0xdfff) { + // Surrogate pair. + ++i; +#else + } else if (ucs >= 0x10000 && ucs <= 0x10ffff) { +#endif + count += 4; + } else { + count += 3; + } + } + return count; +} + +// --------------------------------------------------------------------------- + +static std::string WStringToUTF8(const std::wstring &wstr) { + std::string str; + str.resize(wstr.size()); + str.resize(utf8fromwc(&str[0], static_cast<unsigned>(str.size() + 1), + wstr.data(), static_cast<unsigned>(wstr.size()))); + return str; +} + +// --------------------------------------------------------------------------- + +static std::string Win32Recode(const char *src, unsigned src_code_page, + unsigned dst_code_page) { + // Convert from source code page to Unicode. + + // Compute the length in wide characters. + int wlen = MultiByteToWideChar(src_code_page, MB_ERR_INVALID_CHARS, src, -1, + nullptr, 0); + if (wlen == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION) { + return std::string(); + } + + // Do the actual conversion. + std::wstring wbuf; + wbuf.resize(wlen); + MultiByteToWideChar(src_code_page, 0, src, -1, &wbuf[0], wlen); + + // Convert from Unicode to destination code page. + + // Compute the length in chars. + int len = WideCharToMultiByte(dst_code_page, 0, &wbuf[0], -1, nullptr, 0, + nullptr, nullptr); + + // Do the actual conversion. + std::string out; + out.resize(len); + WideCharToMultiByte(dst_code_page, 0, &wbuf[0], -1, &out[0], len, nullptr, + nullptr); + out.resize(strlen(out.c_str())); + + return out; +} + +// --------------------------------------------------------------------------- + +class FileWin32 : public File { + PJ_CONTEXT *m_ctx; + HANDLE m_handle; + + FileWin32(const FileWin32 &) = delete; + FileWin32 &operator=(const FileWin32 &) = delete; + + protected: + FileWin32(const std::string &name, PJ_CONTEXT *ctx, HANDLE handle) + : File(name), m_ctx(ctx), m_handle(handle) {} + + public: + ~FileWin32() override; + + size_t read(void *buffer, size_t sizeBytes) override; + size_t write(const void *buffer, size_t sizeBytes) override; + bool seek(unsigned long long offset, int whence = SEEK_SET) override; + unsigned long long tell() override; + void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; } + + // We may lie, but the real use case is only for network files + bool hasChanged() const override { return false; } + + static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename, + FileAccess access); +}; + +// --------------------------------------------------------------------------- + +FileWin32::~FileWin32() { CloseHandle(m_handle); } + +// --------------------------------------------------------------------------- + +size_t FileWin32::read(void *buffer, size_t sizeBytes) { + DWORD dwSizeRead = 0; + size_t nResult = 0; + + if (!ReadFile(m_handle, buffer, static_cast<DWORD>(sizeBytes), &dwSizeRead, + nullptr)) + nResult = 0; + else + nResult = dwSizeRead; + + return nResult; +} + +// --------------------------------------------------------------------------- + +size_t FileWin32::write(const void *buffer, size_t sizeBytes) { + DWORD dwSizeWritten = 0; + size_t nResult = 0; + + if (!WriteFile(m_handle, buffer, static_cast<DWORD>(sizeBytes), + &dwSizeWritten, nullptr)) + nResult = 0; + else + nResult = dwSizeWritten; + + return nResult; +} + +// --------------------------------------------------------------------------- + +bool FileWin32::seek(unsigned long long offset, int whence) { + LONG dwMoveMethod, dwMoveHigh; + uint32_t nMoveLow; + LARGE_INTEGER li; + + switch (whence) { + case SEEK_CUR: + dwMoveMethod = FILE_CURRENT; + break; + case SEEK_END: + dwMoveMethod = FILE_END; + break; + case SEEK_SET: + default: + dwMoveMethod = FILE_BEGIN; + break; + } + + li.QuadPart = offset; + nMoveLow = li.LowPart; + dwMoveHigh = li.HighPart; + + SetLastError(0); + SetFilePointer(m_handle, nMoveLow, &dwMoveHigh, dwMoveMethod); + + return GetLastError() == NO_ERROR; +} + +// --------------------------------------------------------------------------- + +unsigned long long FileWin32::tell() { + LARGE_INTEGER li; + + li.HighPart = 0; + li.LowPart = SetFilePointer(m_handle, 0, &(li.HighPart), FILE_CURRENT); + + return static_cast<unsigned long long>(li.QuadPart); +} +// --------------------------------------------------------------------------- + +std::unique_ptr<File> FileWin32::open(PJ_CONTEXT *ctx, const char *filename, + FileAccess access) { + DWORD dwDesiredAccess = access == FileAccess::READ_ONLY + ? GENERIC_READ + : GENERIC_READ | GENERIC_WRITE; + DWORD dwCreationDisposition = + access == FileAccess::CREATE ? CREATE_ALWAYS : OPEN_EXISTING; + DWORD dwFlagsAndAttributes = (dwDesiredAccess == GENERIC_READ) + ? FILE_ATTRIBUTE_READONLY + : FILE_ATTRIBUTE_NORMAL; + try { + HANDLE hFile = CreateFileW( + UTF8ToWString(std::string(filename)).c_str(), dwDesiredAccess, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, + dwCreationDisposition, dwFlagsAndAttributes, nullptr); + return std::unique_ptr<File>(hFile != INVALID_HANDLE_VALUE + ? new FileWin32(filename, ctx, hFile) + : nullptr); + } catch (const std::exception &e) { + pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what()); + return nullptr; + } +} +#else + // --------------------------------------------------------------------------- class FileStdio : public File { @@ -131,6 +702,7 @@ class FileStdio : public File { ~FileStdio() override; size_t read(void *buffer, size_t sizeBytes) override; + size_t write(const void *buffer, size_t sizeBytes) override; bool seek(unsigned long long offset, int whence = SEEK_SET) override; unsigned long long tell() override; void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; } @@ -138,7 +710,8 @@ class FileStdio : public File { // We may lie, but the real use case is only for network files bool hasChanged() const override { return false; } - static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename); + static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename, + FileAccess access); }; // --------------------------------------------------------------------------- @@ -153,6 +726,12 @@ size_t FileStdio::read(void *buffer, size_t sizeBytes) { // --------------------------------------------------------------------------- +size_t FileStdio::write(const void *buffer, size_t sizeBytes) { + return fwrite(buffer, 1, sizeBytes, m_fp); +} + +// --------------------------------------------------------------------------- + bool FileStdio::seek(unsigned long long offset, int whence) { // TODO one day: use 64-bit offset compatible API if (offset != static_cast<unsigned long long>(static_cast<long>(offset))) { @@ -172,12 +751,18 @@ unsigned long long FileStdio::tell() { // --------------------------------------------------------------------------- -std::unique_ptr<File> FileStdio::open(PJ_CONTEXT *ctx, const char *filename) { - auto fp = fopen(filename, "rb"); +std::unique_ptr<File> FileStdio::open(PJ_CONTEXT *ctx, const char *filename, + FileAccess access) { + auto fp = fopen(filename, + access == FileAccess::READ_ONLY + ? "rb" + : access == FileAccess::READ_UPDATE ? "r+b" : "w+b"); return std::unique_ptr<File>(fp ? new FileStdio(filename, ctx, fp) : nullptr); } +#endif // _WIN32 + // --------------------------------------------------------------------------- #ifndef REMOVE_LEGACY_SUPPORT @@ -197,6 +782,7 @@ class FileLegacyAdapter : public File { ~FileLegacyAdapter() override; size_t read(void *buffer, size_t sizeBytes) override; + size_t write(const void *, size_t) override { return 0; } bool seek(unsigned long long offset, int whence = SEEK_SET) override; unsigned long long tell() override; void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; } @@ -204,7 +790,8 @@ class FileLegacyAdapter : public File { // We may lie, but the real use case is only for network files bool hasChanged() const override { return false; } - static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename); + static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename, + FileAccess access); }; // --------------------------------------------------------------------------- @@ -236,8 +823,8 @@ unsigned long long FileLegacyAdapter::tell() { // --------------------------------------------------------------------------- -std::unique_ptr<File> FileLegacyAdapter::open(PJ_CONTEXT *ctx, - const char *filename) { +std::unique_ptr<File> +FileLegacyAdapter::open(PJ_CONTEXT *ctx, const char *filename, FileAccess) { auto fid = pj_ctx_fopen(ctx, filename, "rb"); return std::unique_ptr<File>(fid ? new FileLegacyAdapter(filename, ctx, fid) : nullptr); @@ -1370,6 +1957,7 @@ class NetworkFile : public File { ~NetworkFile() override; size_t read(void *buffer, size_t sizeBytes) override; + size_t write(const void *, size_t) override { return 0; } bool seek(unsigned long long offset, int whence) override; unsigned long long tell() override; void reassign_context(PJ_CONTEXT *ctx) override; @@ -1616,11 +2204,12 @@ void NetworkFile::reassign_context(PJ_CONTEXT *ctx) { // --------------------------------------------------------------------------- -std::unique_ptr<File> FileManager::open(PJ_CONTEXT *ctx, const char *filename) { +std::unique_ptr<File> FileManager::open(PJ_CONTEXT *ctx, const char *filename, + FileAccess access) { #ifndef REMOVE_LEGACY_SUPPORT // If the user has specified a legacy fileapi, use it if (ctx->fileapi_legacy != pj_get_default_fileapi()) { - return FileLegacyAdapter::open(ctx, filename); + return FileLegacyAdapter::open(ctx, filename, access); } #endif if (starts_with(filename, "http://") || starts_with(filename, "https://")) { @@ -1634,7 +2223,109 @@ std::unique_ptr<File> FileManager::open(PJ_CONTEXT *ctx, const char *filename) { } return NetworkFile::open(ctx, filename); } - return FileStdio::open(ctx, filename); +#ifdef _WIN32 + return FileWin32::open(ctx, filename, access); +#else + return FileStdio::open(ctx, filename, access); +#endif +} + +// --------------------------------------------------------------------------- + +bool FileManager::exists(PJ_CONTEXT *ctx, const char *filename) { +#ifdef _WIN32 + struct __stat64 buf; + try { + return _wstat64(UTF8ToWString(filename).c_str(), &buf) == 0; + } catch (const std::exception &e) { + pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what()); + return false; + } +#else + (void)ctx; + struct stat sStat; + return stat(filename, &sStat) == 0; +#endif +} + +// --------------------------------------------------------------------------- + +bool FileManager::mkdir(PJ_CONTEXT *ctx, const char *filename) { +#ifdef _WIN32 + try { + return _wmkdir(UTF8ToWString(filename).c_str()) == 0; + } catch (const std::exception &e) { + pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what()); + return false; + } +#else + (void)ctx; + return ::mkdir(filename, 0755) == 0; +#endif +} + +// --------------------------------------------------------------------------- + +bool FileManager::unlink(PJ_CONTEXT *ctx, const char *filename) { +#ifdef _WIN32 + try { + return _wunlink(UTF8ToWString(filename).c_str()) == 0; + } catch (const std::exception &e) { + pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what()); + return false; + } +#else + (void)ctx; + return ::unlink(filename) == 0; +#endif +} + +// --------------------------------------------------------------------------- + +bool FileManager::rename(PJ_CONTEXT *ctx, const char *oldPath, + const char *newPath) { +#ifdef _WIN32 + try { + return _wrename(UTF8ToWString(oldPath).c_str(), + UTF8ToWString(newPath).c_str()) == 0; + } catch (const std::exception &e) { + pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what()); + return false; + } +#else + (void)ctx; + return ::rename(oldPath, newPath) == 0; +#endif +} + +// --------------------------------------------------------------------------- + +std::string FileManager::getProjLibEnvVar(PJ_CONTEXT *ctx) { + if (!ctx->env_var_proj_lib.empty()) { + return ctx->env_var_proj_lib; + } + (void)ctx; + std::string str; + const char *envvar = getenv("PROJ_LIB"); + if (!envvar) + return str; + str = envvar; +#ifdef _WIN32 + // Assume this is UTF-8. If not try to convert from ANSI page + bool looksLikeUTF8 = false; + try { + UTF8ToWString(envvar); + looksLikeUTF8 = true; + } catch (const std::exception &) { + } + if (!looksLikeUTF8 || !exists(ctx, envvar)) { + str = Win32Recode(envvar, CP_ACP, CP_UTF8); + if (str.empty() || !exists(ctx, str.c_str())) + str = envvar; + } +#endif + ctx->env_var_proj_lib = str; + return str; } // --------------------------------------------------------------------------- @@ -2255,48 +2946,15 @@ bool pj_context_is_network_enabled(PJ_CONTEXT *ctx) { // --------------------------------------------------------------------------- -#ifdef _WIN32 - -static std::wstring UTF8ToWString(const std::string &str) { - using convert_typeX = std::codecvt_utf8<wchar_t>; - std::wstring_convert<convert_typeX, wchar_t> converterX; - - return converterX.from_bytes(str); -} - -// --------------------------------------------------------------------------- - -static std::string WStringToUTF8(const std::wstring &wstr) { - using convert_typeX = std::codecvt_utf8<wchar_t>; - std::wstring_convert<convert_typeX, wchar_t> converterX; - - return converterX.to_bytes(wstr); -} -#endif - -// --------------------------------------------------------------------------- - -static void CreateDirectory(const std::string &path) { -#ifdef _WIN32 - struct __stat64 buf; - const auto wpath = UTF8ToWString(path); - if (_wstat64(wpath.c_str(), &buf) == 0) +static void CreateDirectoryRecursively(PJ_CONTEXT *ctx, + const std::string &path) { + if (NS_PROJ::FileManager::exists(ctx, path.c_str())) return; auto pos = path.find_last_of("/\\"); if (pos == 0 || pos == std::string::npos) return; - CreateDirectory(path.substr(0, pos)); - _wmkdir(wpath.c_str()); -#else - struct stat buf; - if (stat(path.c_str(), &buf) == 0) - return; - auto pos = path.find_last_of("/\\"); - if (pos == 0 || pos == std::string::npos) - return; - CreateDirectory(path.substr(0, pos)); - mkdir(path.c_str(), 0755); -#endif + CreateDirectoryRecursively(ctx, path.substr(0, pos)); + NS_PROJ::FileManager::mkdir(ctx, path.c_str()); } // --------------------------------------------------------------------------- @@ -2320,7 +2978,7 @@ std::string pj_context_get_user_writable_directory(PJ_CONTEXT *ctx, if (SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, &wPath[0]) == S_OK) { wPath.resize(wcslen(wPath.data())); - path = WStringToUTF8(wPath); + path = NS_PROJ::WStringToUTF8(wPath); } else { const char *local_app_data = getenv("LOCALAPPDATA"); if (!local_app_data) { @@ -2352,7 +3010,7 @@ std::string pj_context_get_user_writable_directory(PJ_CONTEXT *ctx, ctx->user_writable_directory = path; } if (create) { - CreateDirectory(ctx->user_writable_directory); + CreateDirectoryRecursively(ctx, ctx->user_writable_directory); } return ctx->user_writable_directory; } @@ -2410,6 +3068,419 @@ static std::string build_url(PJ_CONTEXT *ctx, const char *name) { return name; } +// --------------------------------------------------------------------------- + +#ifdef _WIN32 +static const char *get_path_from_win32_projlib(PJ_CONTEXT *ctx, + const char *name, + std::string &out) { + /* Check if proj.db lieves in a share/proj dir parallel to bin/proj.dll */ + /* Based in + * https://stackoverflow.com/questions/9112893/how-to-get-path-to-executable-in-c-running-on-windows + */ + + DWORD path_size = 1024; + + std::wstring wout; + for (;;) { + wout.clear(); + wout.resize(path_size); + DWORD result = GetModuleFileNameW(nullptr, &wout[0], path_size - 1); + DWORD last_error = GetLastError(); + + if (result == 0) { + return nullptr; + } else if (result == path_size - 1) { + if (ERROR_INSUFFICIENT_BUFFER != last_error) { + return nullptr; + } + path_size = path_size * 2; + } else { + break; + } + } + // Now remove the program's name. It was (example) + // "C:\programs\gmt6\bin\gdal_translate.exe" + wout.resize(wcslen(wout.c_str())); + out = NS_PROJ::WStringToUTF8(wout); + size_t k = out.size(); + while (k > 0 && out[--k] != '\\') { + } + out.resize(k); + + out += "/../share/proj/"; + out += name; + + return NS_PROJ::FileManager::exists(ctx, out.c_str()) ? out.c_str() + : nullptr; +} +#endif + +/************************************************************************/ +/* pj_open_lib_internal() */ +/************************************************************************/ + +#ifdef WIN32 +static const char dirSeparator = ';'; +#else +static const char dirSeparator = ':'; +#endif + +static const char *proj_lib_name = +#ifdef PROJ_LIB + PROJ_LIB; +#else + nullptr; +#endif + +static bool ignoreUserWritableDirectory() { + // Env var mostly for testing purposes and being independent from + // an existing installation + const char *envVarIgnoreUserWritableDirectory = + getenv("PROJ_IGNORE_USER_WRITABLE_DIRECTORY"); + return envVarIgnoreUserWritableDirectory != nullptr && + envVarIgnoreUserWritableDirectory[0] != '\0'; +} + +static void * +pj_open_lib_internal(projCtx ctx, const char *name, const char *mode, + void *(*open_file)(projCtx, const char *, const char *), + char *out_full_filename, size_t out_full_filename_size) { + try { + std::string fname; + const char *sysname = nullptr; + void *fid = nullptr; + std::string projLib; + + if (ctx == nullptr) { + ctx = pj_get_default_ctx(); + } + + if (out_full_filename != nullptr && out_full_filename_size > 0) + out_full_filename[0] = '\0'; + + /* check if ~/name */ + if (is_tilde_slash(name)) + if ((sysname = getenv("HOME")) != nullptr) { + fname = sysname; + fname += DIR_CHAR; + fname += name; + sysname = fname.c_str(); + } else + return nullptr; + + /* or fixed path: /name, ./name or ../name */ + else if (is_rel_or_absolute_filename(name)) { + sysname = name; +#ifdef _WIN32 + try { + NS_PROJ::UTF8ToWString(name); + } catch (const std::exception &) { + fname = NS_PROJ::Win32Recode(name, CP_ACP, CP_UTF8); + sysname = fname.c_str(); + } +#endif + } + + else if (starts_with(name, "http://") || starts_with(name, "https://")) + sysname = name; + + /* or try to use application provided file finder */ + else if (ctx->file_finder != nullptr && + (sysname = ctx->file_finder( + ctx, name, ctx->file_finder_user_data)) != nullptr) + ; + + else if (ctx->file_finder_legacy != nullptr && + (sysname = ctx->file_finder_legacy(name)) != nullptr) + ; + + /* The user has search paths set */ + else if (!ctx->search_paths.empty()) { + for (const auto &path : ctx->search_paths) { + try { + fname = path; + fname += DIR_CHAR; + fname += name; + sysname = fname.c_str(); + fid = open_file(ctx, sysname, mode); + } catch (const std::exception &) { + } + if (fid) + break; + } + } + + else if (!ignoreUserWritableDirectory() && + (fid = open_file( + ctx, (pj_context_get_user_writable_directory(ctx, false) + + DIR_CHAR + name) + .c_str(), + mode)) != nullptr) { + fname = pj_context_get_user_writable_directory(ctx, false); + fname += DIR_CHAR; + fname += name; + sysname = fname.c_str(); + } + + /* if is environment PROJ_LIB defined */ + else if (!(projLib = NS_PROJ::FileManager::getProjLibEnvVar(ctx)) + .empty()) { + auto paths = NS_PROJ::internal::split(projLib, dirSeparator); + for (const auto &path : paths) { + fname = path; + fname += DIR_CHAR; + fname += name; + sysname = fname.c_str(); + fid = open_file(ctx, sysname, mode); + if (fid) + break; + } +#ifdef _WIN32 + /* check if it lives in a ../share/proj dir of the proj dll */ + } else if ((sysname = get_path_from_win32_projlib(ctx, name, fname)) != + nullptr) { +#endif + /* or hardcoded path */ + } else if ((sysname = proj_lib_name) != nullptr) { + fname = sysname; + fname += DIR_CHAR; + fname += name; + sysname = fname.c_str(); + /* just try it bare bones */ + } else { + sysname = name; + } + + assert(sysname); // to make Coverity Scan happy + if (fid != nullptr || + (fid = open_file(ctx, sysname, mode)) != nullptr) { + if (out_full_filename != nullptr && out_full_filename_size > 0) { + // cppcheck-suppress nullPointer + strncpy(out_full_filename, sysname, out_full_filename_size); + out_full_filename[out_full_filename_size - 1] = '\0'; + } + errno = 0; + } + + if (ctx->last_errno == 0 && errno != 0) + pj_ctx_set_errno(ctx, errno); + + pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "pj_open_lib(%s): call fopen(%s) - %s", + name, sysname, fid == nullptr ? "failed" : "succeeded"); + + return (fid); + } catch (const std::exception &) { + + pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "pj_open_lib(%s): out of memory", name); + + return nullptr; + } +} + +/************************************************************************/ +/* pj_open_file_with_manager() */ +/************************************************************************/ + +static void *pj_open_file_with_manager(projCtx ctx, const char *name, + const char * /* mode */) { + return NS_PROJ::FileManager::open(ctx, name, NS_PROJ::FileAccess::READ_ONLY) + .release(); +} + +/************************************************************************/ +/* FileManager::open_resource_file() */ +/************************************************************************/ + +std::unique_ptr<NS_PROJ::File> +NS_PROJ::FileManager::open_resource_file(projCtx ctx, const char *name) { + auto file = std::unique_ptr<NS_PROJ::File>( + reinterpret_cast<NS_PROJ::File *>(pj_open_lib_internal( + ctx, name, "rb", pj_open_file_with_manager, nullptr, 0))); + if (file == nullptr && !is_tilde_slash(name) && + !is_rel_or_absolute_filename(name) && !starts_with(name, "http://") && + !starts_with(name, "https://") && pj_context_is_network_enabled(ctx)) { + std::string remote_file(pj_context_get_url_endpoint(ctx)); + if (!remote_file.empty()) { + if (remote_file.back() != '/') { + remote_file += '/'; + } + remote_file += name; + auto pos = remote_file.rfind('.'); + if (pos + 4 == remote_file.size()) { + remote_file = remote_file.substr(0, pos) + ".tif"; + file = open(ctx, remote_file.c_str(), + NS_PROJ::FileAccess::READ_ONLY); + if (file) { + pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Using %s", + remote_file.c_str()); + pj_ctx_set_errno(ctx, 0); + } + } else { + // For example for resource files like 'alaska' + auto remote_file_tif = remote_file + ".tif"; + file = open(ctx, remote_file_tif.c_str(), + NS_PROJ::FileAccess::READ_ONLY); + if (file) { + pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Using %s", + remote_file_tif.c_str()); + pj_ctx_set_errno(ctx, 0); + } else { + // Init files + file = open(ctx, remote_file.c_str(), + NS_PROJ::FileAccess::READ_ONLY); + if (file) { + pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Using %s", + remote_file.c_str()); + pj_ctx_set_errno(ctx, 0); + } + } + } + } + } + return file; +} + +/************************************************************************/ +/* pj_open_lib() */ +/************************************************************************/ + +#ifndef REMOVE_LEGACY_SUPPORT + +// Used by following legacy function +static void *pj_ctx_fopen_adapter(projCtx ctx, const char *name, + const char *mode) { + return pj_ctx_fopen(ctx, name, mode); +} + +// Legacy function +PAFile pj_open_lib(projCtx ctx, const char *name, const char *mode) { + return (PAFile)pj_open_lib_internal(ctx, name, mode, pj_ctx_fopen_adapter, + nullptr, 0); +} + +#endif // REMOVE_LEGACY_SUPPORT + +/************************************************************************/ +/* pj_find_file() */ +/************************************************************************/ + +/** Returns the full filename corresponding to a proj resource file specified + * as a short filename. + * + * @param ctx context. + * @param short_filename short filename (e.g. egm96_15.gtx). Must not be NULL. + * @param out_full_filename output buffer, of size out_full_filename_size, that + * will receive the full filename on success. + * Will be zero-terminated. + * @param out_full_filename_size size of out_full_filename. + * @return 1 if the file was found, 0 otherwise. + */ +int pj_find_file(projCtx ctx, const char *short_filename, + char *out_full_filename, size_t out_full_filename_size) { + auto f = reinterpret_cast<NS_PROJ::File *>(pj_open_lib_internal( + ctx, short_filename, "rb", pj_open_file_with_manager, out_full_filename, + out_full_filename_size)); + if (f != nullptr) { + delete f; + return 1; + } + return 0; +} + +/************************************************************************/ +/* pj_context_get_url_endpoint() */ +/************************************************************************/ + +std::string pj_context_get_url_endpoint(PJ_CONTEXT *ctx) { + if (!ctx->endpoint.empty()) { + return ctx->endpoint; + } + pj_load_ini(ctx); + return ctx->endpoint; +} + +/************************************************************************/ +/* trim() */ +/************************************************************************/ + +static std::string trim(const std::string &s) { + const auto first = s.find_first_not_of(' '); + const auto last = s.find_last_not_of(' '); + if (first == std::string::npos || last == std::string::npos) { + return std::string(); + } + return s.substr(first, last - first + 1); +} + +/************************************************************************/ +/* pj_load_ini() */ +/************************************************************************/ + +void pj_load_ini(projCtx ctx) { + if (ctx->iniFileLoaded) + return; + + const char *endpoint_from_env = getenv("PROJ_NETWORK_ENDPOINT"); + if (endpoint_from_env && endpoint_from_env[0] != '\0') { + ctx->endpoint = endpoint_from_env; + } + + ctx->iniFileLoaded = true; + auto file = std::unique_ptr<NS_PROJ::File>( + reinterpret_cast<NS_PROJ::File *>(pj_open_lib_internal( + ctx, "proj.ini", "rb", pj_open_file_with_manager, nullptr, 0))); + if (!file) + return; + file->seek(0, SEEK_END); + const auto filesize = file->tell(); + if (filesize == 0 || filesize > 100 * 1024U) + return; + file->seek(0, SEEK_SET); + std::string content; + content.resize(static_cast<size_t>(filesize)); + const auto nread = file->read(&content[0], content.size()); + if (nread != content.size()) + return; + content += '\n'; + size_t pos = 0; + while (pos != std::string::npos) { + const auto eol = content.find_first_of("\r\n", pos); + if (eol == std::string::npos) { + break; + } + + const auto equal = content.find('=', pos); + if (equal < eol) { + const auto key = trim(content.substr(pos, equal - pos)); + const auto value = + trim(content.substr(equal + 1, eol - (equal + 1))); + if (ctx->endpoint.empty() && key == "cdn_endpoint") { + ctx->endpoint = value; + } else if (key == "network") { + const char *enabled = getenv("PROJ_NETWORK"); + if (enabled == nullptr || enabled[0] == '\0') { + ctx->networking.enabled = ci_equal(value, "ON") || + ci_equal(value, "YES") || + ci_equal(value, "TRUE"); + } + } else if (key == "cache_enabled") { + ctx->gridChunkCache.enabled = ci_equal(value, "ON") || + ci_equal(value, "YES") || + ci_equal(value, "TRUE"); + } else if (key == "cache_size_MB") { + const int val = atoi(value.c_str()); + ctx->gridChunkCache.max_size = + val > 0 ? static_cast<long long>(val) * 1024 * 1024 : -1; + } else if (key == "cache_ttl_sec") { + ctx->gridChunkCache.ttl = atoi(value.c_str()); + } + } + + pos = content.find_first_not_of("\r\n", eol); + } +} + //! @endcond // --------------------------------------------------------------------------- @@ -2458,7 +3529,8 @@ int proj_is_download_needed(PJ_CONTEXT *ctx, const char *url_or_filename, const auto localFilename( pj_context_get_user_writable_directory(ctx, false) + filename); - auto f = NS_PROJ::FileManager::open(ctx, localFilename.c_str()); + auto f = NS_PROJ::FileManager::open(ctx, localFilename.c_str(), + NS_PROJ::FileAccess::READ_ONLY); if (!f) { return true; } @@ -2604,7 +3676,8 @@ int proj_download_file(PJ_CONTEXT *ctx, const char *url_or_filename, char szUniqueSuffix[128]; snprintf(szUniqueSuffix, sizeof(szUniqueSuffix), "%d_%p", nPID, &url); const auto localFilenameTmp(localFilename + szUniqueSuffix); - FILE *f = fopen(localFilenameTmp.c_str(), "wb"); + auto f = NS_PROJ::FileManager::open(ctx, localFilenameTmp.c_str(), + NS_PROJ::FileAccess::CREATE); if (!f) { pj_log(ctx, PJ_LOG_ERROR, "Cannot create %s", localFilenameTmp.c_str()); return false; @@ -2629,8 +3702,8 @@ int proj_download_file(PJ_CONTEXT *ctx, const char *url_or_filename, errorBuffer.resize(strlen(errorBuffer.data())); pj_log(ctx, PJ_LOG_ERROR, "Cannot open %s: %s", url.c_str(), errorBuffer.c_str()); - fclose(f); - unlink(localFilenameTmp.c_str()); + f.reset(); + NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str()); return false; } @@ -2639,8 +3712,8 @@ int proj_download_file(PJ_CONTEXT *ctx, const char *url_or_filename, NS_PROJ::FileProperties props; if (!NS_PROJ::NetworkFile::get_props_from_headers(ctx, handle, props)) { ctx->networking.close(ctx, handle, ctx->networking.user_data); - fclose(f); - unlink(localFilenameTmp.c_str()); + f.reset(); + NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str()); return false; } @@ -2648,15 +3721,15 @@ int proj_download_file(PJ_CONTEXT *ctx, const char *url_or_filename, std::min(static_cast<unsigned long long>(buffer.size()), props.size)) { pj_log(ctx, PJ_LOG_ERROR, "Did not get as many bytes as expected"); ctx->networking.close(ctx, handle, ctx->networking.user_data); - fclose(f); - unlink(localFilenameTmp.c_str()); + f.reset(); + NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str()); return false; } - if (fwrite(buffer.data(), size_read, 1, f) != 1) { + if (f->write(buffer.data(), size_read) != size_read) { pj_log(ctx, PJ_LOG_ERROR, "Write error"); ctx->networking.close(ctx, handle, ctx->networking.user_data); - fclose(f); - unlink(localFilenameTmp.c_str()); + f.reset(); + NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str()); return false; } @@ -2673,15 +3746,15 @@ int proj_download_file(PJ_CONTEXT *ctx, const char *url_or_filename, if (size_read < buffer.size()) { pj_log(ctx, PJ_LOG_ERROR, "Did not get as many bytes as expected"); ctx->networking.close(ctx, handle, ctx->networking.user_data); - fclose(f); - unlink(localFilenameTmp.c_str()); + f.reset(); + NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str()); return false; } - if (fwrite(buffer.data(), size_read, 1, f) != 1) { + if (f->write(buffer.data(), size_read) != size_read) { pj_log(ctx, PJ_LOG_ERROR, "Write error"); ctx->networking.close(ctx, handle, ctx->networking.user_data); - fclose(f); - unlink(localFilenameTmp.c_str()); + f.reset(); + NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str()); return false; } @@ -2690,17 +3763,17 @@ int proj_download_file(PJ_CONTEXT *ctx, const char *url_or_filename, !progress_cbk(ctx, double(totalDownloaded) / props.size, user_data)) { ctx->networking.close(ctx, handle, ctx->networking.user_data); - fclose(f); - unlink(localFilenameTmp.c_str()); + f.reset(); + NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str()); return false; } } ctx->networking.close(ctx, handle, ctx->networking.user_data); - fclose(f); - - unlink(localFilename.c_str()); - if (rename(localFilenameTmp.c_str(), localFilename.c_str()) != 0) { + f.reset(); + NS_PROJ::FileManager::unlink(ctx, localFilename.c_str()); + if (!NS_PROJ::FileManager::rename(ctx, localFilenameTmp.c_str(), + localFilename.c_str())) { pj_log(ctx, PJ_LOG_ERROR, "Cannot rename %s to %s", localFilenameTmp.c_str(), localFilename.c_str()); return false; @@ -2766,3 +3839,99 @@ int proj_download_file(PJ_CONTEXT *ctx, const char *url_or_filename, } return true; } + +/************************************************************************/ +/* pj_set_finder() */ +/************************************************************************/ + +void pj_set_finder(const char *(*new_finder)(const char *)) + +{ + auto ctx = pj_get_default_ctx(); + if (ctx) { + ctx->file_finder_legacy = new_finder; + } +} + +/************************************************************************/ +/* proj_context_set_file_finder() */ +/************************************************************************/ + +/** \brief Assign a file finder callback to a context. + * + * This callback will be used whenever PROJ must open one of its resource files + * (proj.db database, grids, etc...) + * + * The callback will be called with the context currently in use at the moment + * where it is used (not necessarily the one provided during this call), and + * with the provided user_data (which may be NULL). + * The user_data must remain valid during the whole lifetime of the context. + * + * A finder set on the default context will be inherited by contexts created + * later. + * + * @param ctx PROJ context, or NULL for the default context. + * @param finder Finder callback. May be NULL + * @param user_data User data provided to the finder callback. May be NULL. + * + * @since PROJ 6.0 + */ +void proj_context_set_file_finder(PJ_CONTEXT *ctx, proj_file_finder finder, + void *user_data) { + if (!ctx) + ctx = pj_get_default_ctx(); + if (!ctx) + return; + ctx->file_finder = finder; + ctx->file_finder_user_data = user_data; +} + +/************************************************************************/ +/* proj_context_set_search_paths() */ +/************************************************************************/ + +/** \brief Sets search paths. + * + * Those search paths will be used whenever PROJ must open one of its resource + * files + * (proj.db database, grids, etc...) + * + * If set on the default context, they will be inherited by contexts created + * later. + * + * Starting with PROJ 7.0, the path(s) should be encoded in UTF-8. + * + * @param ctx PROJ context, or NULL for the default context. + * @param count_paths Number of paths. 0 if paths == NULL. + * @param paths Paths. May be NULL. + * + * @since PROJ 6.0 + */ +void proj_context_set_search_paths(PJ_CONTEXT *ctx, int count_paths, + const char *const *paths) { + if (!ctx) + ctx = pj_get_default_ctx(); + if (!ctx) + return; + try { + std::vector<std::string> vector_of_paths; + for (int i = 0; i < count_paths; i++) { + vector_of_paths.emplace_back(paths[i]); + } + ctx->set_search_paths(vector_of_paths); + } catch (const std::exception &) { + } +} + +/************************************************************************/ +/* pj_set_searchpath() */ +/* */ +/* Path control for callers that can't practically provide */ +/* pj_set_finder() style callbacks. Call with (0,NULL) as args */ +/* to clear the searchpath set. */ +/************************************************************************/ + +void pj_set_searchpath(int count, const char **path) { + proj_context_set_search_paths(nullptr, count, + const_cast<const char *const *>(path)); +} diff --git a/src/filemanager.hpp b/src/filemanager.hpp index 9793267c..bc12a303 100644 --- a/src/filemanager.hpp +++ b/src/filemanager.hpp @@ -39,13 +39,26 @@ NS_PROJ_START class File; +enum class FileAccess { + READ_ONLY, // "rb" + READ_UPDATE, // "r+b" + CREATE, // "w+b" +}; + class FileManager { private: FileManager() = delete; public: // "Low-level" interface. - static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename); + static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename, + FileAccess access); + static bool exists(PJ_CONTEXT *ctx, const char *filename); + static bool mkdir(PJ_CONTEXT *ctx, const char *filename); + static bool unlink(PJ_CONTEXT *ctx, const char *filename); + static bool rename(PJ_CONTEXT *ctx, const char *oldPath, + const char *newPath); + static std::string getProjLibEnvVar(PJ_CONTEXT *ctx); // "High-level" interface, honoring PROJ_LIB and the like. static std::unique_ptr<File> open_resource_file(PJ_CONTEXT *ctx, @@ -66,6 +79,7 @@ class File { public: virtual ~File(); virtual size_t read(void *buffer, size_t sizeBytes) = 0; + virtual size_t write(const void *buffer, size_t sizeBytes) = 0; virtual bool seek(unsigned long long offset, int whence = SEEK_SET) = 0; virtual unsigned long long tell() = 0; virtual void reassign_context(PJ_CONTEXT *ctx) = 0; @@ -78,4 +92,4 @@ NS_PROJ_END //! @endcond Doxygen_Suppress -#endif // FILEMANAGER_HPP_INCLUDED
\ No newline at end of file +#endif // FILEMANAGER_HPP_INCLUDED diff --git a/src/lib_proj.cmake b/src/lib_proj.cmake index fdb59434..14ce04a2 100644 --- a/src/lib_proj.cmake +++ b/src/lib_proj.cmake @@ -247,7 +247,6 @@ set(SRC_LIBPROJ_CORE mlfn.cpp msfn.cpp mutex.cpp - open_lib.cpp param.cpp phi2.cpp pipeline.cpp diff --git a/src/open_lib.cpp b/src/open_lib.cpp deleted file mode 100644 index ae387281..00000000 --- a/src/open_lib.cpp +++ /dev/null @@ -1,576 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Implementation of pj_open_lib(), and pj_set_finder(). These - * provide a standard interface for opening projections support - * data files. - * Author: Gerald Evenden, Frank Warmerdam <warmerdam@pobox.com> - * - ****************************************************************************** - * Copyright (c) 1995, Gerald Evenden - * Copyright (c) 2002, Frank Warmerdam <warmerdam@pobox.com> - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - *****************************************************************************/ - -#define PJ_LIB__ - -#ifndef FROM_PROJ_CPP -#define FROM_PROJ_CPP -#endif - -#include <assert.h> -#include <errno.h> -#include <stddef.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "proj/internal/internal.hpp" - -#include "proj_internal.h" -#include "filemanager.hpp" - -static const char * proj_lib_name = -#ifdef PROJ_LIB -PROJ_LIB; -#else -nullptr; -#endif - -using namespace NS_PROJ::internal; - -/************************************************************************/ -/* pj_set_finder() */ -/************************************************************************/ - -void pj_set_finder( const char *(*new_finder)(const char *) ) - -{ - auto ctx = pj_get_default_ctx(); - if( ctx ) { - ctx->file_finder_legacy = new_finder; - } -} - -/************************************************************************/ -/* proj_context_set_file_finder() */ -/************************************************************************/ - -/** \brief Assign a file finder callback to a context. - * - * This callback will be used whenever PROJ must open one of its resource files - * (proj.db database, grids, etc...) - * - * The callback will be called with the context currently in use at the moment - * where it is used (not necessarily the one provided during this call), and - * with the provided user_data (which may be NULL). - * The user_data must remain valid during the whole lifetime of the context. - * - * A finder set on the default context will be inherited by contexts created - * later. - * - * @param ctx PROJ context, or NULL for the default context. - * @param finder Finder callback. May be NULL - * @param user_data User data provided to the finder callback. May be NULL. - * - * @since PROJ 6.0 - */ -void proj_context_set_file_finder(PJ_CONTEXT *ctx, proj_file_finder finder, - void* user_data) -{ - if( !ctx ) - ctx = pj_get_default_ctx(); - if( !ctx ) - return; - ctx->file_finder = finder; - ctx->file_finder_user_data = user_data; -} - -/************************************************************************/ -/* proj_context_set_search_paths() */ -/************************************************************************/ - - -/** \brief Sets search paths. - * - * Those search paths will be used whenever PROJ must open one of its resource files - * (proj.db database, grids, etc...) - * - * If set on the default context, they will be inherited by contexts created - * later. - * - * @param ctx PROJ context, or NULL for the default context. - * @param count_paths Number of paths. 0 if paths == NULL. - * @param paths Paths. May be NULL. - * - * @since PROJ 6.0 - */ -void proj_context_set_search_paths(PJ_CONTEXT *ctx, - int count_paths, - const char* const* paths) -{ - if( !ctx ) - ctx = pj_get_default_ctx(); - if( !ctx ) - return; - try { - std::vector<std::string> vector_of_paths; - for (int i = 0; i < count_paths; i++) - { - vector_of_paths.emplace_back(paths[i]); - } - ctx->set_search_paths(vector_of_paths); - } catch( const std::exception& ) - { - } -} - -/************************************************************************/ -/* pj_set_searchpath() */ -/* */ -/* Path control for callers that can't practically provide */ -/* pj_set_finder() style callbacks. Call with (0,NULL) as args */ -/* to clear the searchpath set. */ -/************************************************************************/ - -void pj_set_searchpath ( int count, const char **path ) -{ - proj_context_set_search_paths( nullptr, count, const_cast<const char* const*>(path) ); -} - -#ifdef _WIN32 -#include <windows.h> -#include <sys/stat.h> -static const char *get_path_from_win32_projlib(const char *name, std::string& out) { - /* Check if proj.db lieves in a share/proj dir parallel to bin/proj.dll */ - /* Based in https://stackoverflow.com/questions/9112893/how-to-get-path-to-executable-in-c-running-on-windows */ - - DWORD path_size = 1024; - - for (;;) { - out.resize(path_size); - memset(&out[0], 0, path_size); - DWORD result = GetModuleFileNameA(nullptr, &out[0], path_size - 1); - DWORD last_error = GetLastError(); - - if (result == 0) { - return nullptr; - } - else if (result == path_size - 1) { - if (ERROR_INSUFFICIENT_BUFFER != last_error) { - return nullptr; - } - path_size = path_size * 2; - } - else { - break; - } - } - // Now remove the program's name. It was (example) "C:\programs\gmt6\bin\gdal_translate.exe" - size_t k = strlen(out.c_str()); - while (k > 0 && out[--k] != '\\') {} - out.resize(k); - - out += "/../share/proj/"; - out += name; - - struct stat fileInfo; - if (stat(out.c_str(), &fileInfo) == 0) // Check if file exists (probably there are simpler ways) - return out.c_str(); - else { - return nullptr; - } -} -#endif - -/************************************************************************/ -/* pj_open_lib_internal() */ -/************************************************************************/ - -#ifdef WIN32 -static const char dir_chars[] = "/\\"; -static const char dirSeparator = ';'; -#else -static const char dir_chars[] = "/"; -static const char dirSeparator = ':'; -#endif - -static bool is_tilde_slash(const char* name) -{ - return *name == '~' && strchr(dir_chars,name[1]); -} - -static bool is_rel_or_absolute_filename(const char *name) -{ - return strchr(dir_chars,*name) - || (*name == '.' && strchr(dir_chars,name[1])) - || (!strncmp(name, "..", 2) && strchr(dir_chars,name[2])) - || (name[0] != '\0' && name[1] == ':' && strchr(dir_chars,name[2])); -} - -static bool ignoreUserWritableDirectory() -{ - // Env var mostly for testing purposes and being independent from - // an existing installation - const char* envVarIgnoreUserWritableDirectory = - getenv("PROJ_IGNORE_USER_WRITABLE_DIRECTORY"); - return envVarIgnoreUserWritableDirectory != nullptr && - envVarIgnoreUserWritableDirectory[0] != '\0'; -} - -static void* -pj_open_lib_internal(projCtx ctx, const char *name, const char *mode, - void* (*open_file)(projCtx, const char*, const char*), - char* out_full_filename, size_t out_full_filename_size) { - try { - std::string fname; - const char *sysname = nullptr; - void* fid = nullptr; - - if( ctx == nullptr ) { - ctx = pj_get_default_ctx(); - } - - if( out_full_filename != nullptr && out_full_filename_size > 0 ) - out_full_filename[0] = '\0'; - - /* check if ~/name */ - if (is_tilde_slash(name)) - if ((sysname = getenv("HOME")) != nullptr) { - fname = sysname; - fname += DIR_CHAR; - fname += name; - sysname = fname.c_str(); - } else - return nullptr; - - /* or fixed path: /name, ./name or ../name or http[s]:// */ - else if (is_rel_or_absolute_filename(name) - || starts_with(name, "http://") - || starts_with(name, "https://")) - sysname = name; - - /* or try to use application provided file finder */ - else if( ctx->file_finder != nullptr && (sysname = ctx->file_finder( ctx, name, ctx->file_finder_user_data )) != nullptr ) - ; - - else if( ctx->file_finder_legacy != nullptr && (sysname = ctx->file_finder_legacy( name )) != nullptr ) - ; - - /* The user has search paths set */ - else if( !ctx->search_paths.empty() ) { - for( const auto& path: ctx->search_paths ) { - try { - fname = path; - fname += DIR_CHAR; - fname += name; - sysname = fname.c_str(); - fid = open_file(ctx, sysname, mode); - } catch( const std::exception& ) - { - } - if( fid ) - break; - } - } - - else if( !ignoreUserWritableDirectory() && - (fid = open_file(ctx, - (pj_context_get_user_writable_directory(ctx, false) + - DIR_CHAR + name).c_str(), mode)) != nullptr ) { - fname = pj_context_get_user_writable_directory(ctx, false); - fname += DIR_CHAR; - fname += name; - sysname = fname.c_str(); - } - - /* if is environment PROJ_LIB defined */ - else if ((sysname = getenv("PROJ_LIB")) != nullptr) { - auto paths = NS_PROJ::internal::split(std::string(sysname), dirSeparator); - for( const auto& path: paths ) { - fname = path; - fname += DIR_CHAR; - fname += name; - sysname = fname.c_str(); - fid = open_file(ctx, sysname, mode); - if( fid ) - break; - } -#ifdef _WIN32 - /* check if it lives in a ../share/proj dir of the proj dll */ - } else if ((sysname = get_path_from_win32_projlib(name, fname)) != nullptr) { -#endif - /* or hardcoded path */ - } else if ((sysname = proj_lib_name) != nullptr) { - fname = sysname; - fname += DIR_CHAR; - fname += name; - sysname = fname.c_str(); - /* just try it bare bones */ - } else { - sysname = name; - } - - assert(sysname); // to make Coverity Scan happy - if ( fid != nullptr || (fid = open_file(ctx, sysname, mode)) != nullptr) - { - if( out_full_filename != nullptr && out_full_filename_size > 0 ) - { - // cppcheck-suppress nullPointer - strncpy(out_full_filename, sysname, out_full_filename_size); - out_full_filename[out_full_filename_size-1] = '\0'; - } - errno = 0; - } - - if( ctx->last_errno == 0 && errno != 0 ) - pj_ctx_set_errno( ctx, errno ); - - pj_log( ctx, PJ_LOG_DEBUG_MAJOR, - "pj_open_lib(%s): call fopen(%s) - %s", - name, sysname, - fid == nullptr ? "failed" : "succeeded" ); - - return(fid); - } - catch( const std::exception& ) { - - pj_log( ctx, PJ_LOG_DEBUG_MAJOR, - "pj_open_lib(%s): out of memory", - name ); - - return nullptr; - } -} - -/************************************************************************/ -/* pj_open_file_with_manager() */ -/************************************************************************/ - -static void* pj_open_file_with_manager(projCtx ctx, const char *name, - const char * /* mode */) -{ - return NS_PROJ::FileManager::open(ctx, name).release(); -} - -/************************************************************************/ -/* FileManager::open_resource_file() */ -/************************************************************************/ - -std::unique_ptr<NS_PROJ::File> NS_PROJ::FileManager::open_resource_file( - projCtx ctx, const char *name) -{ - auto file = std::unique_ptr<NS_PROJ::File>( - reinterpret_cast<NS_PROJ::File*>( - pj_open_lib_internal(ctx, name, "rb", - pj_open_file_with_manager, - nullptr, 0))); - if( file == nullptr && - !is_tilde_slash(name) && - !is_rel_or_absolute_filename(name) && - !starts_with(name, "http://") && - !starts_with(name, "https://") && - pj_context_is_network_enabled(ctx) ) { - std::string remote_file(pj_context_get_url_endpoint(ctx)); - if( !remote_file.empty() ) { - if( remote_file.back() != '/' ) { - remote_file += '/'; - } - remote_file += name; - auto pos = remote_file.rfind('.'); - if( pos + 4 == remote_file.size() ) { - remote_file = remote_file.substr(0, pos) + ".tif"; - file = open(ctx, remote_file.c_str()); - if( file ) { - pj_log( ctx, PJ_LOG_DEBUG_MAJOR, - "Using %s", remote_file.c_str() ); - pj_ctx_set_errno( ctx, 0 ); - } - } else { - // For example for resource files like 'alaska' - auto remote_file_tif = remote_file + ".tif"; - file = open(ctx, remote_file_tif.c_str()); - if( file ) { - pj_log( ctx, PJ_LOG_DEBUG_MAJOR, - "Using %s", remote_file_tif.c_str() ); - pj_ctx_set_errno( ctx, 0 ); - } else { - // Init files - file = open(ctx, remote_file.c_str()); - if( file ) { - pj_log( ctx, PJ_LOG_DEBUG_MAJOR, - "Using %s", remote_file.c_str() ); - pj_ctx_set_errno( ctx, 0 ); - } - } - } - } - } - return file; -} - -/************************************************************************/ -/* pj_open_lib() */ -/************************************************************************/ - -#ifndef REMOVE_LEGACY_SUPPORT - -// Used by following legacy function -static void* pj_ctx_fopen_adapter(projCtx ctx, const char *name, const char *mode) -{ - return pj_ctx_fopen(ctx, name, mode); -} - -// Legacy function -PAFile -pj_open_lib(projCtx ctx, const char *name, const char *mode) { - return (PAFile)pj_open_lib_internal(ctx, name, mode, pj_ctx_fopen_adapter, nullptr, 0); -} - -#endif // REMOVE_LEGACY_SUPPORT - -/************************************************************************/ -/* pj_find_file() */ -/************************************************************************/ - - -/** Returns the full filename corresponding to a proj resource file specified - * as a short filename. - * - * @param ctx context. - * @param short_filename short filename (e.g. egm96_15.gtx). Must not be NULL. - * @param out_full_filename output buffer, of size out_full_filename_size, that - * will receive the full filename on success. - * Will be zero-terminated. - * @param out_full_filename_size size of out_full_filename. - * @return 1 if the file was found, 0 otherwise. - */ -int pj_find_file(projCtx ctx, const char *short_filename, - char* out_full_filename, size_t out_full_filename_size) -{ - auto f = reinterpret_cast<NS_PROJ::File*>( - pj_open_lib_internal(ctx, short_filename, "rb", - pj_open_file_with_manager, - out_full_filename, - out_full_filename_size)); - if( f != nullptr ) - { - delete f; - return 1; - } - return 0; -} - -/************************************************************************/ -/* pj_context_get_url_endpoint() */ -/************************************************************************/ - -std::string pj_context_get_url_endpoint(PJ_CONTEXT* ctx) -{ - if( !ctx->endpoint.empty() ) { - return ctx->endpoint; - } - pj_load_ini(ctx); - return ctx->endpoint; -} - -/************************************************************************/ -/* trim() */ -/************************************************************************/ - -static std::string trim(const std::string& s) { - const auto first = s.find_first_not_of(' '); - const auto last = s.find_last_not_of(' '); - if( first == std::string::npos || last == std::string::npos ) { - return std::string(); - } - return s.substr(first, last - first + 1); -} - -/************************************************************************/ -/* pj_load_ini() */ -/************************************************************************/ - -void pj_load_ini(projCtx ctx) -{ - if( ctx->iniFileLoaded ) - return; - - const char* endpoint_from_env = getenv("PROJ_NETWORK_ENDPOINT"); - if( endpoint_from_env && endpoint_from_env[0] != '\0' ) { - ctx->endpoint = endpoint_from_env; - } - - ctx->iniFileLoaded = true; - auto file = std::unique_ptr<NS_PROJ::File>( - reinterpret_cast<NS_PROJ::File*>( - pj_open_lib_internal(ctx, "proj.ini", "rb", - pj_open_file_with_manager, - nullptr, 0))); - if( !file ) - return; - file->seek(0, SEEK_END); - const auto filesize = file->tell(); - if( filesize == 0 || filesize > 100 * 1024U ) - return; - file->seek(0, SEEK_SET); - std::string content; - content.resize(static_cast<size_t>(filesize)); - const auto nread = file->read(&content[0], content.size()); - if( nread != content.size() ) - return; - content += '\n'; - size_t pos = 0; - while( pos != std::string::npos ) { - const auto eol = content.find_first_of("\r\n", pos); - if( eol == std::string::npos ) { - break; - } - - const auto equal = content.find('=', pos); - if( equal < eol ) - { - const auto key = trim(content.substr(pos, equal-pos)); - const auto value = trim(content.substr(equal + 1, - eol - (equal+1))); - if( ctx->endpoint.empty() && key == "cdn_endpoint" ) { - ctx->endpoint = value; - } else if ( key == "network" ) { - const char *enabled = getenv("PROJ_NETWORK"); - if (enabled == nullptr || enabled[0] == '\0') { - ctx->networking.enabled = ci_equal(value, "ON") || - ci_equal(value, "YES") || - ci_equal(value, "TRUE"); - } - } else if ( key == "cache_enabled" ) { - ctx->gridChunkCache.enabled = ci_equal(value, "ON") || - ci_equal(value, "YES") || - ci_equal(value, "TRUE"); - } else if ( key == "cache_size_MB" ) { - const int val = atoi(value.c_str()); - ctx->gridChunkCache.max_size = val > 0 ? - static_cast<long long>(val) * 1024 * 1024 : -1; - } else if ( key == "cache_ttl_sec" ) { - ctx->gridChunkCache.ttl = atoi(value.c_str()); - } - } - - pos = content.find_first_not_of("\r\n", eol); - } -} diff --git a/src/proj_internal.h b/src/proj_internal.h index ce7b9d74..fb8f294c 100644 --- a/src/proj_internal.h +++ b/src/proj_internal.h @@ -696,6 +696,7 @@ struct projCtx_t { int use_proj4_init_rules = -1; /* -1 = unknown, 0 = no, 1 = yes */ int epsg_file_exists = -1; /* -1 = unknown, 0 = no, 1 = yes */ + std::string env_var_proj_lib{}; // content of PROJ_LIB environment variable. Use Filemanager::getProjLibEnvVar() to access std::vector<std::string> search_paths{}; const char **c_compat_paths = nullptr; // same, but for projinfo usage |
