Skip to content
Open
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
91 changes: 20 additions & 71 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -447,17 +447,31 @@ 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:
Expand All @@ -481,7 +495,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 +511,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 +520,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
6 changes: 4 additions & 2 deletions app/rest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ enable_language(CXX)
set(rest_SRCS
controller_curl.cc
controller_interface.cc
curl/curl_handles.cc
curl/tls_policy.cc
gzipheader.cc
request.cc
request_binary_gzip.cc
Expand Down Expand Up @@ -75,8 +77,8 @@ if (MSVC)
# libcurl on Windows (Schannel/SSPI) depends on these Windows SDK libraries:
# crypt32 (certificate store), bcrypt (BCryptGenRandom), iphlpapi
# (if_nametoindex) and secur32 (InitSecurityInterface). These are also
# pulled in via #pragma comment(lib) in transport_curl.cc for consumers of
# the merged firebase_app.lib.
# pulled in via #pragma comment(lib) in curl_handles.cc and transport_curl.cc
# for consumers of the merged firebase_app.lib.
set(FIREBASE_CURL_DEPENDENCY ${FIREBASE_CURL_DEPENDENCY} crypt32 bcrypt iphlpapi secur32)
endif()

Expand Down
66 changes: 66 additions & 0 deletions app/rest/curl/curl_handles.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "app/rest/curl/curl_handles.h"

#include <cstdint>
#include <mutex>

#include "app/src/assert.h"

#if defined(_MSC_VER)
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "bcrypt.lib")
#pragma comment(lib, "crypt32.lib")
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "secur32.lib")
#pragma comment(lib, "ws2_32.lib")
#endif // defined(_MSC_VER)

namespace firebase {
namespace rest {
namespace curl {

namespace {

std::mutex g_curl_global_mutex;
int64_t g_curl_global_refcount = 0;

} // namespace

void AcquireCurlGlobal() {
std::lock_guard<std::mutex> lock(g_curl_global_mutex);
if (g_curl_global_refcount++ == 0) {
CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
FIREBASE_ASSERT_MESSAGE(code == CURLE_OK,
"curl_global_init(CURL_GLOBAL_ALL) failed: %s",
curl_easy_strerror(code));
}
}

void ReleaseCurlGlobal() {
std::lock_guard<std::mutex> lock(g_curl_global_mutex);
FIREBASE_ASSERT_MESSAGE(
g_curl_global_refcount > 0,
"ReleaseCurlGlobal called more times than AcquireCurlGlobal");
if (--g_curl_global_refcount == 0) {
curl_global_cleanup();
}
}

} // namespace curl
} // namespace rest
} // namespace firebase
74 changes: 74 additions & 0 deletions app/rest/curl/curl_handles.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef FIREBASE_APP_REST_CURL_CURL_HANDLES_H_
#define FIREBASE_APP_REST_CURL_CURL_HANDLES_H_

#include <memory>

#include "curl/curl.h"

// The desktop SDK drives libcurl through APIs that only exist in recent
// releases: curl_multi_poll()/curl_multi_wakeup() (7.68), CURLOPT_PROTOCOLS_STR
// (7.85), CURLOPT_CONNECT_ONLY=2 WebSocket handshakes (7.86) and
// CURLWS_NOAUTOPONG (8.14). Fail at compile time with a clear message rather
// than with scattered "undeclared identifier" errors when a user points
// CURL_SOURCE_DIR at an older tree.
static_assert(LIBCURL_VERSION_NUM >= 0x080e00,
"Firebase C++ SDK requires libcurl >= 8.14.0 (WebSockets with "
"CURLWS_NOAUTOPONG)");

namespace firebase {
namespace rest {
namespace curl {

// RAII ownership for libcurl easy, multi, and slist handles. All deleters are
// null-safe. These are plain std::unique_ptr aliases so ownership transfer,
// release() and reset() behave exactly as for any other unique_ptr.
struct EasyDeleter {
void operator()(CURL* easy) const {
if (easy != nullptr) curl_easy_cleanup(easy);
}
};

struct MultiDeleter {
void operator()(CURLM* multi) const {
if (multi != nullptr) curl_multi_cleanup(multi);
}
};

struct SListDeleter {
void operator()(curl_slist* list) const {
if (list != nullptr) curl_slist_free_all(list);
}
};

using UniqueEasy = std::unique_ptr<CURL, EasyDeleter>;
using UniqueMulti = std::unique_ptr<CURLM, MultiDeleter>;
using UniqueSList = std::unique_ptr<curl_slist, SListDeleter>;

// Reference-counted curl_global_init() / curl_global_cleanup(). Thread-safe.
// Every libcurl user in the SDK holds a reference while it owns any CURL
// handle, so libcurl's process-wide state is initialized exactly once and torn
// down only after the last user is gone.
void AcquireCurlGlobal();
void ReleaseCurlGlobal();

} // namespace curl
} // namespace rest
} // namespace firebase

#endif // FIREBASE_APP_REST_CURL_CURL_HANDLES_H_
Loading
Loading