aboutsummaryrefslogtreecommitdiff
path: root/src/filemanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/filemanager.cpp')
-rw-r--r--src/filemanager.cpp161
1 files changed, 161 insertions, 0 deletions
diff --git a/src/filemanager.cpp b/src/filemanager.cpp
index 97f6a4dc..3a2acee4 100644
--- a/src/filemanager.cpp
+++ b/src/filemanager.cpp
@@ -29,11 +29,18 @@
#define FROM_PROJ_CPP
#endif
+#include <algorithm>
+
#include "filemanager.hpp"
#include "proj.h"
#include "proj/internal/internal.hpp"
#include "proj_internal.h"
+#ifdef CURL_ENABLED
+#include <curl/curl.h>
+#include <sqlite3.h> // for sqlite3_snprintf
+#endif
+
//! @cond Doxygen_Suppress
NS_PROJ_START
@@ -258,6 +265,152 @@ std::unique_ptr<File> FileManager::open(PJ_CONTEXT *ctx, const char *filename) {
// ---------------------------------------------------------------------------
+#ifdef CURL_ENABLED
+
+struct CurlFileHandle {
+ CURL *m_handle = nullptr;
+ std::string m_headers;
+
+ CurlFileHandle(const CurlFileHandle &) = delete;
+ CurlFileHandle &operator=(const CurlFileHandle &) = delete;
+
+ explicit CurlFileHandle(CURL *handle, std::string &&headers)
+ : m_handle(handle), m_headers(std::move(headers)) {}
+ ~CurlFileHandle();
+};
+
+// ---------------------------------------------------------------------------
+
+CurlFileHandle::~CurlFileHandle() { curl_easy_cleanup(m_handle); }
+
+// ---------------------------------------------------------------------------
+
+static size_t pj_curl_write_func(void *buffer, size_t count, size_t nmemb,
+ void *req) {
+ const size_t nSize = count * nmemb;
+ auto pStr = static_cast<std::string *>(req);
+ if (pStr->size() + nSize > pStr->capacity()) {
+ // to avoid servers not honouring Range to cause excessive memory
+ // allocation
+ return 0;
+ }
+ pStr->append(static_cast<const char *>(buffer), nSize);
+ return nmemb;
+}
+
+// ---------------------------------------------------------------------------
+
+static PROJ_NETWORK_HANDLE *pj_curl_open(PJ_CONTEXT *, const char *url,
+ size_t size_to_read, void *buffer,
+ size_t *out_size_read, void *) {
+ CURL *hCurlHandle = curl_easy_init();
+ if (!hCurlHandle)
+ return nullptr;
+ curl_easy_setopt(hCurlHandle, CURLOPT_URL, url);
+
+ if (getenv("PROJ_CURL_VERBOSE"))
+ curl_easy_setopt(hCurlHandle, CURLOPT_VERBOSE, 1);
+
+// CURLOPT_SUPPRESS_CONNECT_HEADERS is defined in curl 7.54.0 or newer.
+#if LIBCURL_VERSION_NUM >= 0x073600
+ curl_easy_setopt(hCurlHandle, CURLOPT_SUPPRESS_CONNECT_HEADERS, 1L);
+#endif
+
+ // Enable following redirections. Requires libcurl 7.10.1 at least.
+ curl_easy_setopt(hCurlHandle, CURLOPT_FOLLOWLOCATION, 1);
+ curl_easy_setopt(hCurlHandle, CURLOPT_MAXREDIRS, 10);
+
+ if (getenv("PROJ_UNSAFE_SSL")) {
+ curl_easy_setopt(hCurlHandle, CURLOPT_SSL_VERIFYPEER, 0L);
+ curl_easy_setopt(hCurlHandle, CURLOPT_SSL_VERIFYHOST, 0L);
+ }
+
+ char szBuffer[128];
+ sqlite3_snprintf(sizeof(szBuffer), szBuffer, "0-%llu", size_to_read - 1);
+ curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, szBuffer);
+
+ std::string headers;
+ headers.reserve(16 * 1024);
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, &headers);
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, pj_curl_write_func);
+
+ std::string body;
+ body.reserve(size_to_read);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &body);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, pj_curl_write_func);
+
+ curl_easy_perform(hCurlHandle);
+
+ long response_code = 0;
+ curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
+
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, nullptr);
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, nullptr);
+
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, nullptr);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, nullptr);
+
+ if (response_code == 0 || response_code >= 300) {
+ curl_easy_cleanup(hCurlHandle);
+ return nullptr;
+ }
+
+ if (!body.empty()) {
+ memcpy(buffer, body.data(), std::min(size_to_read, body.size()));
+ }
+ *out_size_read = std::min(size_to_read, body.size());
+
+ return reinterpret_cast<PROJ_NETWORK_HANDLE *>(
+ new CurlFileHandle(hCurlHandle, std::move(headers)));
+}
+
+// ---------------------------------------------------------------------------
+
+static void pj_curl_close(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *handle,
+ void * /*user_data*/) {
+ delete reinterpret_cast<CurlFileHandle *>(handle);
+}
+
+// ---------------------------------------------------------------------------
+
+static size_t pj_curl_read_range(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *raw_handle,
+ unsigned long long offset, size_t size_to_read,
+ void *buffer, void *) {
+ auto handle = reinterpret_cast<CurlFileHandle *>(raw_handle);
+ auto hCurlHandle = handle->m_handle;
+
+ char szBuffer[128];
+ sqlite3_snprintf(sizeof(szBuffer), szBuffer, "%llu-%llu", offset,
+ offset + size_to_read - 1);
+ curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, szBuffer);
+
+ std::string body;
+ body.reserve(size_to_read);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &body);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, pj_curl_write_func);
+
+ curl_easy_perform(hCurlHandle);
+
+ long response_code = 0;
+ curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
+
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, nullptr);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, nullptr);
+
+ if (response_code == 0 || response_code >= 300) {
+ return 0;
+ }
+
+ if (!body.empty()) {
+ memcpy(buffer, body.data(), std::min(size_to_read, body.size()));
+ }
+ return std::min(size_to_read, body.size());
+}
+
+#else
+
+// ---------------------------------------------------------------------------
+
static PROJ_NETWORK_HANDLE *
no_op_network_open(PJ_CONTEXT *ctx, const char * /* url */,
size_t, /* size to read */
@@ -281,12 +434,20 @@ static const char *no_op_network_get_last_error(PJ_CONTEXT *,
return "Network functionality not available";
}
+#endif
+
// ---------------------------------------------------------------------------
void FileManager::fillDefaultNetworkInterface(PJ_CONTEXT *ctx) {
+#ifdef CURL_ENABLED
+ ctx->networking.open = pj_curl_open;
+ ctx->networking.close = pj_curl_close;
+ ctx->networking.read_range = pj_curl_read_range;
+#else
ctx->networking.open = no_op_network_open;
ctx->networking.close = no_op_network_close;
ctx->networking.get_last_error = no_op_network_get_last_error;
+#endif
}
// ---------------------------------------------------------------------------