From 34ae5dd64bd0a38d00f2d5dbbadf98e775d8c5df Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Wed, 9 Sep 2020 05:01:24 +0200 Subject: [PATCH] util/curl: Add C++ wrapper for CURL --- CMakeLists.txt | 44 +++++++- source/util/util-curl.cpp | 215 ++++++++++++++++++++++++++++++++++++++ source/util/util-curl.hpp | 132 +++++++++++++++++++++++ 3 files changed, 389 insertions(+), 2 deletions(-) create mode 100644 source/util/util-curl.cpp create mode 100644 source/util/util-curl.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d8285073..67062727 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -295,6 +295,7 @@ configure_file( "${PROJECT_BINARY_DIR}/generated/config.hpp" ) +# libOBS if(NOT TARGET libobs) # Packaging if("${CMAKE_PACKAGE_SUFFIX_OVERRIDE}" STREQUAL "") @@ -442,6 +443,30 @@ if(HAVE_OBS_FRONTEND) find_package(Qt5 COMPONENTS Core Widgets REQUIRED) endif() +# CURL +if(REQUIRE_CURL) + find_package(CURL QUIET) + if(NOT CURL_FOUND) + # CURL built by OBS Project is not compatible with find modules. + if(${PropertyPrefix}OBS_DOWNLOAD) + set(CURL_LIBRARIES "${obsdeps_SOURCE_DIR}/win${BITS}/bin/libcurl.lib") + set(CURL_INCLUDE_DIRS "${obsdeps_SOURCE_DIR}/win${BITS}/include") + endif() + if(${PropertyPrefix}OBS_NATIVE) # Already defined by OBS + set(CURL_LIBRARIES "${CURL_LIB}") + set(CURL_INCLUDE_DIRS "${CURL_INCLUDE_DIR}") + endif() + if((${PropertyPrefix}OBS_REFERENCE) OR (${PropertyPrefix}OBS_PACKAGE)) + set(CURL_LIBRARIES "${OBS_DEPENDENCIES_DIR}/bin/libcurl.lib") + set(CURL_INCLUDE_DIRS "${OBS_DEPENDENCIES_DIR}/include") + endif() + CacheSet(CURL_LIBRARY_DEBUG ${CURL_LIBRARIES}) + CacheSet(CURL_LIBRARY_RELEASE ${CURL_LIBRARIES}) + CacheSet(CURL_INCLUDE_DIR ${CURL_INCLUDE_DIRS}) + set(CURL_FOUND ON) + endif() +endif() + ################################################################################ # Code ################################################################################ @@ -456,9 +481,10 @@ set(PROJECT_PUBLIC ) set(PROJECT_PRIVATE_GENERATED ) set(PROJECT_PRIVATE_SOURCE ) set(PROJECT_UI ) -set(PROJECT_DEFINES ) +set(PROJECT_DEFINITIONS ) -## OBS Studio +# Dependencies +## Dependency: libOBS if(${PropertyPrefix}OBS_NATIVE) list(APPEND PROJECT_LIBRARIES libobs @@ -483,6 +509,20 @@ elseif(${PropertyPrefix}OBS_DOWNLOAD) ) endif() +## Dependency: CURL +if(REQUIRE_CURL) + list(APPEND PROJECT_PRIVATE_SOURCE + "source/util/util-curl.hpp" + "source/util/util-curl.cpp" + ) + list(APPEND PROJECT_LIBRARIES + ${CURL_LIBRARY_RELEASE} + ) + list(APPEND PROJECT_INCLUDE_DIRS + ${CURL_INCLUDE_DIR} + ) +endif() + ## Data & Source list(APPEND PROJECT_DATA "data/locale/en-US.ini" diff --git a/source/util/util-curl.cpp b/source/util/util-curl.cpp new file mode 100644 index 00000000..8a645983 --- /dev/null +++ b/source/util/util-curl.cpp @@ -0,0 +1,215 @@ +// Copyright (c) 2020 Michael Fabian Dirks +// +// 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. + +#include "util-curl.hpp" +#include + +int32_t util::curl::debug_helper(CURL* handle, curl_infotype type, char* data, size_t size, util::curl* self) +{ + if (self->_debug_callback) { + self->_debug_callback(handle, type, data, size); + } else { +#ifdef _DEBUG_CURL + std::stringstream hd; + for (size_t n = 0; n < size; n++) { + hd << std::uppercase << std::setfill('0') << std::setw(2) << std::hex << static_cast(data[n]) + << " "; + if (n % 16 == 15) { + hd << "\n "; + } + } + std::string hds = hd.str(); + + switch (type) { + case CURLINFO_TEXT: + DLOG_DEBUG(" %.*s", size - 1, data); + break; + case CURLINFO_HEADER_IN: + DLOG_DEBUG(" << Header: %.*s", size - 1, data); + break; + case CURLINFO_HEADER_OUT: + DLOG_DEBUG(" >> Header: %.*s", size - 1, data); + break; + case CURLINFO_DATA_IN: + DLOG_DEBUG(" << %lld bytes of data:\n %s", size, hds.c_str()); + break; + case CURLINFO_DATA_OUT: + DLOG_DEBUG(" >> %lld bytes of data:\n %s", size, hds.c_str()); + break; + case CURLINFO_SSL_DATA_IN: + DLOG_DEBUG(" << %lld bytes of SSL data:\n %s", size, hds.c_str()); + break; + case CURLINFO_SSL_DATA_OUT: + DLOG_DEBUG(" >> %lld bytes of SSL data:\n %s", size, hds.c_str()); + break; + } +#endif + } + return 0; +} + +size_t util::curl::read_helper(void* ptr, size_t size, size_t count, util::curl* self) +{ + if (self->_read_callback) { + return self->_read_callback(ptr, size, count); + } else { + return size * count; + } +} + +size_t util::curl::write_helper(void* ptr, size_t size, size_t count, util::curl* self) +{ + if (self->_write_callback) { + return self->_write_callback(ptr, size, count); + } else { + return size * count; + } +} + +int32_t util::curl::xferinfo_callback(util::curl* self, curl_off_t dlt, curl_off_t dln, curl_off_t ult, curl_off_t uln) +{ + if (self->_xferinfo_callback) { + return self->_xferinfo_callback(static_cast(dlt), static_cast(dln), + static_cast(ult), static_cast(uln)); + } else { + return 0; + } +} + +util::curl::curl() : _curl(), _read_callback(), _write_callback(), _headers() +{ + _curl = curl_easy_init(); + set_read_callback(nullptr); + set_write_callback(nullptr); + set_xferinfo_callback(nullptr); + set_debug_callback(nullptr); + + // Default settings. + set_option(CURLOPT_NOPROGRESS, false); + set_option(CURLOPT_PATH_AS_IS, false); + set_option(CURLOPT_CRLF, false); +#ifdef _DEBUG + set_option(CURLOPT_VERBOSE, true); +#else + set_option(CURLOPT_VERBOSE, false); +#endif +} + +util::curl::~curl() +{ + curl_easy_cleanup(_curl); +} + +void util::curl::clear_headers() +{ + _headers.clear(); +} + +void util::curl::clear_header(std::string header) +{ + _headers.erase(header); +} + +void util::curl::set_header(std::string header, std::string value) +{ + _headers.insert_or_assign(header, value); +} + +size_t perform_get_kv_size(std::string a, std::string b) +{ + return a.size() + 2 + b.size() + 1; +}; + +CURLcode util::curl::perform() +{ + std::vector buffer; + struct curl_slist* headers = nullptr; + + if (_headers.size() > 0) { + // Calculate full buffer size. + { + size_t buffer_size = 0; + for (auto kv : _headers) { + buffer_size += perform_get_kv_size(kv.first, kv.second); + } + buffer.resize(buffer_size * 2); + } + // Create HTTP Headers. + { + size_t buffer_offset = 0; + for (auto kv : _headers) { + size_t size = perform_get_kv_size(kv.first, kv.second); + + snprintf(&buffer.at(buffer_offset), size, "%s: %s", kv.first.c_str(), kv.second.c_str()); + + headers = curl_slist_append(headers, &buffer.at(buffer_offset)); + + buffer_offset += size; + } + } + set_option(CURLOPT_HTTPHEADER, headers); + } + + CURLcode res = curl_easy_perform(_curl); + + if (headers) { + set_option(CURLOPT_HTTPHEADER, nullptr); + curl_slist_free_all(headers); + } + + return res; +} + +void util::curl::reset() +{ + curl_easy_reset(_curl); +} + +CURLcode util::curl::set_read_callback(curl_io_callback_t cb) +{ + _read_callback = cb; + if (CURLcode res = curl_easy_setopt(_curl, CURLOPT_READDATA, this); res != CURLE_OK) + return res; + return curl_easy_setopt(_curl, CURLOPT_READFUNCTION, &read_helper); +} + +CURLcode util::curl::set_write_callback(curl_io_callback_t cb) +{ + _write_callback = cb; + if (CURLcode res = curl_easy_setopt(_curl, CURLOPT_WRITEDATA, this); res != CURLE_OK) + return res; + return curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, &write_helper); +} + +CURLcode util::curl::set_xferinfo_callback(curl_xferinfo_callback_t cb) +{ + _xferinfo_callback = cb; + if (CURLcode res = curl_easy_setopt(_curl, CURLOPT_XFERINFODATA, this); res != CURLE_OK) + return res; + return curl_easy_setopt(_curl, CURLOPT_XFERINFOFUNCTION, &xferinfo_callback); +} + +CURLcode util::curl::set_debug_callback(curl_debug_callback_t cb) +{ + _debug_callback = cb; + if (CURLcode res = curl_easy_setopt(_curl, CURLOPT_DEBUGDATA, this); res != CURLE_OK) + return res; + return curl_easy_setopt(_curl, CURLOPT_DEBUGFUNCTION, &debug_helper); +} diff --git a/source/util/util-curl.hpp b/source/util/util-curl.hpp new file mode 100644 index 00000000..a46db59b --- /dev/null +++ b/source/util/util-curl.hpp @@ -0,0 +1,132 @@ +// Copyright (c) 2020 Michael Fabian Dirks +// +// 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. + +#pragma once +#include +#include +#include +#include + +extern "C" { +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +} + +namespace util { + typedef std::function curl_io_callback_t; + typedef std::function curl_xferinfo_callback_t; + typedef std::function curl_debug_callback_t; + + class curl { + CURL* _curl; + curl_io_callback_t _read_callback; + curl_io_callback_t _write_callback; + curl_xferinfo_callback_t _xferinfo_callback; + curl_debug_callback_t _debug_callback; + std::map _headers; + + static int32_t debug_helper(CURL* handle, curl_infotype type, char* data, size_t size, util::curl* userptr); + static size_t read_helper(void*, size_t, size_t, util::curl*); + static size_t write_helper(void*, size_t, size_t, util::curl*); + static int32_t xferinfo_callback(util::curl*, curl_off_t, curl_off_t, curl_off_t, curl_off_t); + + public: + curl(); + ~curl(); + + template + CURLcode set_option(CURLoption opt, _Ty1 value) + { + return curl_easy_setopt(_curl, opt, value); + }; + + template<> + CURLcode set_option(CURLoption opt, const bool value) + { + // CURL does not seem to accept boolean, so we err on the side of safety here. + return curl_easy_setopt(_curl, opt, value ? 1 : 0); + }; + + template<> + CURLcode set_option(CURLoption opt, const std::string value) + { + return curl_easy_setopt(_curl, opt, value.c_str()); + }; + + template<> + CURLcode set_option(CURLoption opt, const std::string_view value) + { + return curl_easy_setopt(_curl, opt, value.data()); + }; + + template + CURLcode get_info(CURLINFO info, _Ty1& value) + { + return curl_easy_getinfo(_curl, info, &value); + }; + + template<> + CURLcode get_info(CURLINFO info, std::vector& value) + { + char* buffer; + if (CURLcode res = curl_easy_getinfo(_curl, info, &buffer); res != CURLE_OK) { + return res; + } + + size_t buffer_len = strnlen(buffer, size_t(0xFFFF)); + value.resize(buffer_len); + + memcpy(value.data(), buffer, value.size()); + return CURLE_OK; + }; + + template<> + CURLcode get_info(CURLINFO info, std::string& value) + { + std::vector buffer; + if (CURLcode res = get_info(info, buffer); res != CURLE_OK) { + return res; + } + value = std::string(buffer.data(), buffer.data() + strlen(buffer.data())); + return CURLE_OK; + }; + + void clear_headers(); + + void clear_header(std::string header); + + void set_header(std::string header, std::string value); + + CURLcode perform(); + + void reset(); + + public /* Helpers */: + CURLcode set_read_callback(curl_io_callback_t cb); + + CURLcode set_write_callback(curl_io_callback_t cb); + + CURLcode set_xferinfo_callback(curl_xferinfo_callback_t cb); + + CURLcode set_debug_callback(curl_debug_callback_t cb); + }; +} // namespace util