Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 24 additions & 72 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -447,23 +447,40 @@ if(DESKTOP)
set(CURL_DISABLE_INSTALL ON)

if(WIN32)
# Use Windows native Schannel/SSPI for TLS instead of OpenSSL/BoringSSL.
# Use Windows native Schannel/SSPI (Crypt32) for TLS instead of
# OpenSSL/BoringSSL. Schannel verifies server certificates against the
# Windows Certificate Store and enforces native OCSP/CRL revocation
# checking (SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT) as well as
# Microsoft Certificate Trust List (CTL) / disallowed-CA updates.
set(CURL_USE_SCHANNEL ON)
elseif(APPLE)
# Enable Apple Security.framework (SecTrust) native OS certificate
# verification alongside OpenSSL/BoringSSL, so macOS builds verify peers
# against the system Keychain rather than requiring a CA bundle file path.
# against the macOS System Keychain (SecTrustEvaluateWithError) and enforce
# Apple's native OCSP/CRL policies and Valid/trustd revocation updates
# rather than relying on a static CA bundle file path.
set(USE_APPLE_SECTRUST ON)
# When curl detects static BoringSSL with a Clang toolchain, it appends
# ${_curl_stdlib_cxx} to its link interface. Override curl's default of
# "stdc++" (removed from modern Xcode SDKs) to Apple's "c++" (libc++).
set(_curl_stdlib_cxx "c++")
else()
# On Linux, libcurl uses OpenSSL/BoringSSL and verifies server certificates
# against the OS trust store managed by the distribution's ca-certificates
# package (which removes or distrusts compromised root and intermediate CAs).
# Note: TransportCurl and WebSocketClientImpl resolve CURLOPT_CAINFO and
# CURLOPT_CAPATH at runtime across standard Linux distribution paths so
# prebuilt binaries do not depend solely on the build machine's detected
# CURL_CA_BUNDLE path.
endif()

# Disable optional external libraries that curl 8.23.0 enables by default:
# - libssh2: SSH/SCP/SFTP protocol library (unused by Firebase REST).
# - libpsl: Public Suffix List cookie-domain library (unused).
# - nghttp2: HTTP/2 framing library (Firebase REST pins to HTTP/1.1).
# - nghttp2: optional external HTTP/2 library. Not bundled, so the shipped
# desktop libraries speak HTTP/1.1 only; the REST transport opts into
# HTTP/2 (CURL_HTTP_VERSION_2TLS) automatically if a user-supplied libcurl
# is built with nghttp2.
# - libidn2: Internationalized Domain Names library (unused).
set(CURL_USE_LIBSSH2 OFF)
set(CURL_USE_LIBPSL OFF)
Expand All @@ -481,7 +498,8 @@ if(DESKTOP)
set(CURL_ZSTD OFF CACHE STRING "Build curl with zstd support" FORCE)

# curl 8.23.0 removed the legacy umbrella HTTP_ONLY option. Explicitly disable
# every non-HTTP(S) protocol so only HTTP and HTTPS are compiled into libcurl.
# every unused protocol so only HTTP, HTTPS, WS, and WSS (used by the REST
# layer and Realtime Database WebSockets) are compiled into libcurl.
set(CURL_DISABLE_DICT ON)
set(CURL_DISABLE_FILE ON)
set(CURL_DISABLE_FTP ON)
Expand All @@ -496,7 +514,7 @@ if(DESKTOP)
set(CURL_DISABLE_SMTP ON)
set(CURL_DISABLE_TELNET ON)
set(CURL_DISABLE_TFTP ON)
set(CURL_DISABLE_WEBSOCKETS ON)
set(CURL_DISABLE_WEBSOCKETS OFF)

# Skip building curl's test suite and example binaries.
set(BUILD_TESTING OFF)
Expand All @@ -505,77 +523,11 @@ if(DESKTOP)
add_external_library(curl)

# Clean up generic normal variables set specifically for curl so they do not
# leak into subsequent add_subdirectory() calls (libuv, zlib, firestore, etc.).
# leak into subsequent add_subdirectory() calls (zlib, firestore, etc.).
unset(BUILD_STATIC_LIBS)
unset(BUILD_EXAMPLES)

add_external_library(libuv)

add_external_library(zlib)

add_external_library(uWebSockets)

# Binutils on Mac doesn't support thread-local storage (required by
# websockets), but because we only use websockets via the scheduler, we don't
# need it. Deactivate this by blanking out the __thread keyword.
set(websockets_additional_defines "-D__thread=")

# uWebSockets does not come with a CMakeLists file, so define the target.
# Note that since it depends on OpenSSL, only do so if that was found.
if (OPENSSL_FOUND)
if (MSVC OR APPLE)
set(uWebSockets_extra_src)
else()
# Epoll is only used on Linux, otherwise LibUV is used.
set(uWebSockets_extra_src
${UWEBSOCKETS_SOURCE_DIR}/src/Epoll.cpp)
endif()
add_library(libuWS STATIC
${uWebSockets_extra_src}
${UWEBSOCKETS_SOURCE_DIR}/src/Extensions.cpp
${UWEBSOCKETS_SOURCE_DIR}/src/Group.cpp
${UWEBSOCKETS_SOURCE_DIR}/src/HTTPSocket.cpp
${UWEBSOCKETS_SOURCE_DIR}/src/Hub.cpp
${UWEBSOCKETS_SOURCE_DIR}/src/Networking.cpp
${UWEBSOCKETS_SOURCE_DIR}/src/Node.cpp
${UWEBSOCKETS_SOURCE_DIR}/src/Socket.cpp
${UWEBSOCKETS_SOURCE_DIR}/src/WebSocket.cpp)
if(MSVC)
set(websockets_additional_defines
${websockets_additional_defines}
-DWIN32_LEAN_AND_MEAN # Ensure that windows doesn't include winsock.h by
# default, as it can cause issues when libraries try
# to include winsock2.h later on in the process.
-DUSE_LIBUV=1
)
elseif(APPLE)
set(websockets_additional_defines
${websockets_additional_defines}
-DUSE_LIBUV=1
)
endif()

target_compile_definitions(libuWS
PRIVATE
${websockets_additional_defines}
)
target_include_directories(libuWS
PUBLIC
${LIBUV_SOURCE_DIR}/include
${UWEBSOCKETS_SOURCE_DIR}
${ZLIB_SOURCE_DIR}
${ZLIB_BINARY_DIR}
PRIVATE
${OPENSSL_INCLUDE_DIR}
)
target_link_libraries(libuWS
PRIVATE
OpenSSL::SSL
OpenSSL::Crypto
uv_a
zlibstatic
)
endif()
endif()

if(DESKTOP AND NOT CMAKE_CROSSCOMPILING)
Expand Down
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,9 @@ Currently, the third party libraries that can be provided this way are:

| Library |
| ------- |
| CURL |
| CURL (8.14.0 or newer, built with WebSocket support) |
| FLATBUFFERS |
| LIBUV |
| NANOPB |
| UWEBSOCKETS |
| ZLIB |

#### Building with CMake for iOS
Expand Down
8 changes: 6 additions & 2 deletions app/rest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ enable_language(CXX)
set(rest_SRCS
controller_curl.cc
controller_interface.cc
curl/curl_handles.cc
curl/curl_handles.h
curl/curl_io_loop.cc
curl/curl_io_loop.h
curl/tls_policy.cc
curl/tls_policy.h
gzipheader.cc
request.cc
request_binary_gzip.cc
Expand Down Expand Up @@ -99,8 +105,6 @@ add_definitions(
-DHAVE_LIBZ
# Ensure curl includes ZLIB headers.
-DHAVE_ZLIB_H
# Enforce stricter curl typechecking.
-DCURL_STRICTER
# Disable unused protocols/options to reduce vulnerability surface.
-DCURL_DISABLE_FTP
-DCURL_DISABLE_NTLM
Expand Down
141 changes: 51 additions & 90 deletions app/rest/controller_curl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,103 +16,77 @@

#include "app/rest/controller_curl.h"

#include <atomic>
#include <cstdint>
#include <memory>
#include <utility>

#include "app/rest/transport_curl.h"
#include "app/src/include/firebase/internal/common.h"
#include "app/rest/curl/curl_io_loop.h"
#include "app/src/include/firebase/internal/mutex.h"
#include "app/src/log.h"

namespace firebase {
namespace rest {

ControllerCurl::ControllerCurl(TransportCurl* transport,
TransferDirection direction, Response* response)
: transport_(transport),
direction_(direction),
is_paused_(false),
response_(response),
transferring_(false),
bytes_transferred_(0),
transfer_size_(0),
this_handle_(nullptr),
this_handle_mutex_(nullptr) {}

ControllerCurl::~ControllerCurl() {
if (this_handle_mutex_) {
MutexLock lock(*this_handle_mutex_);
*this_handle_ = nullptr;
}
}
namespace {

#if defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN)
ControllerCurl::ControllerCurl(ControllerCurl&& other) noexcept {
*this = std::move(other);
void PostToTransfer(const std::shared_ptr<TransferControl>& control,
void (*action)(RestTransfer*)) {
curl::CurlIoLoop::PostIfRunning([control, action]() {
RestTransfer* transfer = nullptr;
{
MutexLock lock(control->mutex);
transfer = control->transfer;
}
if (transfer != nullptr) action(transfer);
});
}

ControllerCurl& ControllerCurl::operator=(ControllerCurl&& other) noexcept {
direction_ = other.direction_;
transport_ = other.transport_;
is_paused_ = other.is_paused_;
response_ = other.response_;
transferring_ = other.transferring_;
bytes_transferred_ = other.bytes_transferred_;
transfer_size_ = other.transfer_size_;
this_handle_ = other.this_handle_;
this_handle_mutex_ = other.this_handle_mutex_;
if (this_handle_mutex_) {
MutexLock lock(*this_handle_mutex_);
*this_handle_ = this;
}
other.this_handle_mutex_ = nullptr;
other.transport_ = nullptr;
return *this;
}
#endif // defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN)
} // namespace

ControllerCurl::ControllerCurl(std::shared_ptr<TransferControl> control)
: control_(std::move(control)) {}

ControllerCurl::~ControllerCurl() = default;

bool ControllerCurl::Pause() {
if (this_handle_mutex_) {
MutexLock lock(*this_handle_mutex_);
if (transferring_ && !is_paused_) {
transport_->PauseRequest(response_);
is_paused_ = true;
return true;
}
if (!control_) return false;
{
MutexLock lock(control_->mutex);
if (!control_->transferring || control_->paused) return false;
control_->paused = true;
}
return false;
PostToTransfer(control_, &TransferControl::PauseOnIoThread);
return true;
}

bool ControllerCurl::Resume() {
if (this_handle_mutex_) {
MutexLock lock(*this_handle_mutex_);
if (transferring_ && is_paused_) {
transport_->ResumeRequest(response_);
is_paused_ = false;
return true;
}
if (!control_) return false;
{
MutexLock lock(control_->mutex);
if (!control_->transferring || !control_->paused) return false;
control_->paused = false;
}
return false;
PostToTransfer(control_, &TransferControl::ResumeOnIoThread);
return true;
}

bool ControllerCurl::IsPaused() {
if (this_handle_mutex_) {
MutexLock lock(*this_handle_mutex_);
return is_paused_;
}
return false;
if (!control_) return false;
MutexLock lock(control_->mutex);
return control_->paused;
}

bool ControllerCurl::Cancel() {
if (this_handle_mutex_) {
MutexLock lock(*this_handle_mutex_);
if (transferring_) {
transport_->CancelRequest(response_);
transferring_ = false;
return true;
}
if (!control_) return false;
{
MutexLock lock(control_->mutex);
if (!control_->transferring) return false;
control_->transferring = false;
control_->paused = false;
control_->canceled = true;
}
return false;
PostToTransfer(control_, &TransferControl::CancelOnIoThread);
return true;
}

float ControllerCurl::Progress() {
Expand All @@ -121,26 +95,13 @@ float ControllerCurl::Progress() {
return transferred / size;
}

int64_t ControllerCurl::TransferSize() { return transfer_size_; }

int64_t ControllerCurl::BytesTransferred() { return bytes_transferred_; }

void ControllerCurl::set_bytes_transferred(int64_t bytes_transferred) {
// Only set the bytes transferred if it is larger than the current value.
// Curl reports -1 for the bytes transferred if the transfer is just starting
// or is complete.
if (bytes_transferred > bytes_transferred_) {
bytes_transferred_ = bytes_transferred;
}
int64_t ControllerCurl::TransferSize() {
return control_ ? control_->transfer_size.load(std::memory_order_relaxed) : 0;
}

void ControllerCurl::set_transfer_size(int64_t transfer_size) {
// Only set the transfer size if it is larger than the current value.
// Curl reports -1 for the transfer size if the transfer is just starting or
// is complete.
if (transfer_size > transfer_size_) {
transfer_size_ = transfer_size;
}
int64_t ControllerCurl::BytesTransferred() {
return control_ ? control_->bytes_transferred.load(std::memory_order_relaxed)
: 0;
}

} // namespace rest
Expand Down
Loading
Loading