diff --git a/src/4D_api.cpp b/src/4D_api.cpp index 9107723d53..6b53363a29 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 3966750943..5e97cb4ac3 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 9acea83e85..b426161679 100644 --- a/src/filemanager.cpp +++ b/src/filemanager.cpp @@ -114,6 +114,189 @@ File::File(const std::string &name) : name_(name) {} File::~File() = default; +#ifdef _WIN32 + +// May throw exceptions +static std::wstring UTF8ToWString(const std::string &str) { + using convert_typeX = std::codecvt_utf8; + std::wstring_convert converterX; + + return converterX.from_bytes(str); +} + +// --------------------------------------------------------------------------- + +static std::string WStringToUTF8(const std::wstring &wstr) { + using convert_typeX = std::codecvt_utf8; + std::wstring_convert converterX; + + return converterX.to_bytes(wstr); +} + +// --------------------------------------------------------------------------- + +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 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(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(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(li.QuadPart); +} +// --------------------------------------------------------------------------- + +std::unique_ptr 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(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 +314,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 +322,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 open(PJ_CONTEXT *ctx, const char *filename); + static std::unique_ptr open(PJ_CONTEXT *ctx, const char *filename, + FileAccess access); }; // --------------------------------------------------------------------------- @@ -153,6 +338,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(static_cast(offset))) { @@ -172,12 +363,18 @@ unsigned long long FileStdio::tell() { // --------------------------------------------------------------------------- -std::unique_ptr FileStdio::open(PJ_CONTEXT *ctx, const char *filename) { - auto fp = fopen(filename, "rb"); +std::unique_ptr 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(fp ? new FileStdio(filename, ctx, fp) : nullptr); } +#endif // _WIN32 + // --------------------------------------------------------------------------- #ifndef REMOVE_LEGACY_SUPPORT @@ -197,6 +394,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 +402,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 open(PJ_CONTEXT *ctx, const char *filename); + static std::unique_ptr open(PJ_CONTEXT *ctx, const char *filename, + FileAccess access); }; // --------------------------------------------------------------------------- @@ -236,8 +435,8 @@ unsigned long long FileLegacyAdapter::tell() { // --------------------------------------------------------------------------- -std::unique_ptr FileLegacyAdapter::open(PJ_CONTEXT *ctx, - const char *filename) { +std::unique_ptr +FileLegacyAdapter::open(PJ_CONTEXT *ctx, const char *filename, FileAccess) { auto fid = pj_ctx_fopen(ctx, filename, "rb"); return std::unique_ptr(fid ? new FileLegacyAdapter(filename, ctx, fid) : nullptr); @@ -1370,6 +1569,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 +1816,12 @@ void NetworkFile::reassign_context(PJ_CONTEXT *ctx) { // --------------------------------------------------------------------------- -std::unique_ptr FileManager::open(PJ_CONTEXT *ctx, const char *filename) { +std::unique_ptr 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 +1835,109 @@ std::unique_ptr 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 +2558,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; - std::wstring_convert converterX; - - return converterX.from_bytes(str); -} - -// --------------------------------------------------------------------------- - -static std::string WStringToUTF8(const std::wstring &wstr) { - using convert_typeX = std::codecvt_utf8; - std::wstring_convert 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 +2590,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 +2622,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; } @@ -2458,7 +2728,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 +2875,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 +2901,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 +2911,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 +2920,15 @@ int proj_download_file(PJ_CONTEXT *ctx, const char *url_or_filename, std::min(static_cast(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 +2945,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 +2962,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 +3038,512 @@ 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 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(path)); +} + +// --------------------------------------------------------------------------- + +#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::FileManager::open_resource_file(projCtx ctx, const char *name) { + auto file = std::unique_ptr( + reinterpret_cast(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(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( + reinterpret_cast(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(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(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/filemanager.hpp b/src/filemanager.hpp index 9793267c80..bc12a303be 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 open(PJ_CONTEXT *ctx, const char *filename); + static std::unique_ptr 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 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 fdb5943474..14ce04a214 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 ae387281a0..0000000000 --- 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 - * - ****************************************************************************** - * Copyright (c) 1995, Gerald Evenden - * Copyright (c) 2002, Frank Warmerdam - * - * 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 -#include -#include -#include -#include -#include - -#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 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(path) ); -} - -#ifdef _WIN32 -#include -#include -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::FileManager::open_resource_file( - projCtx ctx, const char *name) -{ - auto file = std::unique_ptr( - reinterpret_cast( - 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( - 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( - reinterpret_cast( - 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(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(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 ce7b9d74af..fb8f294cbe 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 search_paths{}; const char **c_compat_paths = nullptr; // same, but for projinfo usage