summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/pingsender/pingsender_unix_common.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /toolkit/components/telemetry/pingsender/pingsender_unix_common.cpp
parentInitial commit. (diff)
downloadfirefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz
firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/telemetry/pingsender/pingsender_unix_common.cpp')
-rw-r--r--toolkit/components/telemetry/pingsender/pingsender_unix_common.cpp301
1 files changed, 301 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/pingsender/pingsender_unix_common.cpp b/toolkit/components/telemetry/pingsender/pingsender_unix_common.cpp
new file mode 100644
index 0000000000..1f201d177e
--- /dev/null
+++ b/toolkit/components/telemetry/pingsender/pingsender_unix_common.cpp
@@ -0,0 +1,301 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+#include <cerrno>
+#include <cstring>
+#include <dlfcn.h>
+#include <string>
+#include <unistd.h>
+#include "mozilla/Unused.h"
+#include "third_party/curl/curl.h"
+
+#include "pingsender.h"
+
+namespace PingSender {
+
+using std::string;
+
+using mozilla::Unused;
+
+/**
+ * A simple wrapper around libcurl "easy" functions. Provides RAII opening
+ * and initialization of the curl library
+ */
+class CurlWrapper {
+ public:
+ CurlWrapper();
+ ~CurlWrapper();
+ bool Init();
+ bool IsValidDestination(const string& url);
+ bool Post(const string& url, const string& payload);
+
+ // libcurl functions
+ CURL* (*easy_init)(void);
+ CURLcode (*easy_setopt)(CURL*, CURLoption, ...);
+ CURLcode (*easy_perform)(CURL*);
+ CURLcode (*easy_getinfo)(CURL*, CURLINFO, ...);
+ curl_slist* (*slist_append)(curl_slist*, const char*);
+ void (*slist_free_all)(curl_slist*);
+ const char* (*easy_strerror)(CURLcode);
+ void (*easy_cleanup)(CURL*);
+ void (*global_cleanup)(void);
+
+ CURLU* (*curl_url)();
+ CURLUcode (*curl_url_get)(CURLU*, CURLUPart, char**, unsigned int);
+ CURLUcode (*curl_url_set)(CURLU*, CURLUPart, const char*, unsigned int);
+ void (*curl_free)(char*);
+ void (*curl_url_cleanup)(CURLU*);
+
+ private:
+ void* mLib;
+ void* mCurl;
+ bool mCanParseUrl;
+};
+
+CurlWrapper::CurlWrapper()
+ : easy_init(nullptr),
+ easy_setopt(nullptr),
+ easy_perform(nullptr),
+ easy_getinfo(nullptr),
+ slist_append(nullptr),
+ slist_free_all(nullptr),
+ easy_strerror(nullptr),
+ easy_cleanup(nullptr),
+ global_cleanup(nullptr),
+ curl_url(nullptr),
+ curl_url_get(nullptr),
+ curl_url_set(nullptr),
+ curl_free(nullptr),
+ curl_url_cleanup(nullptr),
+ mLib(nullptr),
+ mCurl(nullptr) {}
+
+CurlWrapper::~CurlWrapper() {
+ if (mLib) {
+ if (mCurl && easy_cleanup) {
+ easy_cleanup(mCurl);
+ }
+
+ if (global_cleanup) {
+ global_cleanup();
+ }
+
+ dlclose(mLib);
+ }
+}
+
+bool CurlWrapper::Init() {
+ const char* libcurlPaths[] = {
+#if defined(XP_MACOSX)
+ // macOS
+ "/usr/lib/libcurl.dylib",
+ "/usr/lib/libcurl.4.dylib",
+ "/usr/lib/libcurl.3.dylib",
+#else // Linux, *BSD, ...
+ "libcurl.so",
+ "libcurl.so.4",
+ // Debian gives libcurl a different name when it is built against GnuTLS
+ "libcurl-gnutls.so",
+ "libcurl-gnutls.so.4",
+ // Older versions in case we find nothing better
+ "libcurl.so.3",
+ "libcurl-gnutls.so.3", // See above for Debian
+#endif
+ };
+
+ // libcurl might show up under different names & paths, try them all until
+ // we find it
+ for (const char* libname : libcurlPaths) {
+ mLib = dlopen(libname, RTLD_NOW);
+
+ if (mLib) {
+ break;
+ }
+ }
+
+ if (!mLib) {
+ PINGSENDER_LOG("ERROR: Could not find libcurl\n");
+ return false;
+ }
+
+ *(void**)(&easy_init) = dlsym(mLib, "curl_easy_init");
+ *(void**)(&easy_setopt) = dlsym(mLib, "curl_easy_setopt");
+ *(void**)(&easy_perform) = dlsym(mLib, "curl_easy_perform");
+ *(void**)(&easy_getinfo) = dlsym(mLib, "curl_easy_getinfo");
+ *(void**)(&slist_append) = dlsym(mLib, "curl_slist_append");
+ *(void**)(&slist_free_all) = dlsym(mLib, "curl_slist_free_all");
+ *(void**)(&easy_strerror) = dlsym(mLib, "curl_easy_strerror");
+ *(void**)(&easy_cleanup) = dlsym(mLib, "curl_easy_cleanup");
+ *(void**)(&global_cleanup) = dlsym(mLib, "curl_global_cleanup");
+
+ *(void**)(&curl_url) = dlsym(mLib, "curl_url");
+ *(void**)(&curl_url_set) = dlsym(mLib, "curl_url_set");
+ *(void**)(&curl_url_get) = dlsym(mLib, "curl_url_get");
+ *(void**)(&curl_free) = dlsym(mLib, "curl_free");
+ *(void**)(&curl_url_cleanup) = dlsym(mLib, "curl_url_cleanup");
+
+ if (!easy_init || !easy_setopt || !easy_perform || !easy_getinfo ||
+ !slist_append || !slist_free_all || !easy_strerror || !easy_cleanup ||
+ !global_cleanup) {
+ PINGSENDER_LOG("ERROR: libcurl is missing one of the required symbols\n");
+ return false;
+ }
+
+ mCanParseUrl = true;
+ if (!curl_url || !curl_url_get || !curl_url_set || !curl_free ||
+ !curl_url_cleanup) {
+ mCanParseUrl = false;
+ PINGSENDER_LOG("WARNING: Do not have url parsing functions in libcurl\n");
+ }
+
+ mCurl = easy_init();
+
+ if (!mCurl) {
+ PINGSENDER_LOG("ERROR: Could not initialize libcurl\n");
+ return false;
+ }
+
+ return true;
+}
+
+static size_t DummyWriteCallback(char* ptr, size_t size, size_t nmemb,
+ void* userdata) {
+ Unused << ptr;
+ Unused << size;
+ Unused << nmemb;
+ Unused << userdata;
+
+ return size * nmemb;
+}
+
+// If we can't use curl's URL parsing (which is safer) we have to fallback
+// to this handwritten one (which is only as safe as we are clever.)
+bool FallbackIsValidDestination(const string& aUrl) {
+ // Lowercase the url
+ string url = aUrl;
+ std::transform(url.begin(), url.end(), url.begin(),
+ [](unsigned char c) { return std::tolower(c); });
+ // Strip off the scheme in the beginning
+ if (url.find("http://") == 0) {
+ url = url.substr(7);
+ } else if (url.find("https://") == 0) {
+ url = url.substr(8);
+ }
+
+ // Remove any user information. If a @ appeared in the userinformation,
+ // it would need to be encoded.
+ unsigned long atStart = url.find_first_of("@");
+ url = (atStart == std::string::npos) ? url : url.substr(atStart + 1);
+
+ // Remove any path or fragment information, leaving us with a url that may
+ // contain host, and port.
+ unsigned long fragStart = url.find_first_of("#");
+ url = (fragStart == std::string::npos) ? url : url.substr(0, fragStart);
+ unsigned long pathStart = url.find_first_of("/");
+ url = (pathStart == std::string::npos) ? url : url.substr(0, pathStart);
+
+ // Remove the port, because we run tests targeting localhost:port
+ unsigned long portStart = url.find_last_of(":");
+ url = (portStart == std::string::npos) ? url : url.substr(0, portStart);
+
+ return PingSender::IsValidDestination(url);
+}
+
+bool CurlWrapper::IsValidDestination(const string& aUrl) {
+ if (!mCanParseUrl) {
+ return FallbackIsValidDestination(aUrl);
+ }
+
+ bool ret = false;
+ CURLU* h = curl_url();
+ if (!h) {
+ return FallbackIsValidDestination(aUrl);
+ }
+
+ if (CURLUE_OK != curl_url_set(h, CURLUPART_URL, aUrl.c_str(), 0)) {
+ goto cleanup;
+ }
+
+ char* host;
+ if (CURLUE_OK != curl_url_get(h, CURLUPART_HOST, &host, 0)) {
+ goto cleanup;
+ }
+
+ ret = PingSender::IsValidDestination(host);
+ curl_free(host);
+
+cleanup:
+ curl_url_cleanup(h);
+ return ret;
+}
+
+bool CurlWrapper::Post(const string& url, const string& payload) {
+ easy_setopt(mCurl, CURLOPT_URL, url.c_str());
+ easy_setopt(mCurl, CURLOPT_USERAGENT, kUserAgent);
+ easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, DummyWriteCallback);
+
+ // Build the date header.
+ std::string dateHeader = GenerateDateHeader();
+
+ // Set the custom headers.
+ curl_slist* headerChunk = nullptr;
+ headerChunk = slist_append(headerChunk, kCustomVersionHeader);
+ headerChunk = slist_append(headerChunk, kContentEncodingHeader);
+ headerChunk = slist_append(headerChunk, dateHeader.c_str());
+ CURLcode err = easy_setopt(mCurl, CURLOPT_HTTPHEADER, headerChunk);
+ if (err != CURLE_OK) {
+ PINGSENDER_LOG("ERROR: Failed to set HTTP headers, %s\n",
+ easy_strerror(err));
+ slist_free_all(headerChunk);
+ return false;
+ }
+
+ // Set the size of the POST data
+ easy_setopt(mCurl, CURLOPT_POSTFIELDSIZE, payload.length());
+
+ // Set the contents of the POST data
+ easy_setopt(mCurl, CURLOPT_POSTFIELDS, payload.c_str());
+
+ // Fail if the server returns a 4xx code
+ easy_setopt(mCurl, CURLOPT_FAILONERROR, 1);
+
+ // Override the default connection timeout, which is 5 minutes.
+ easy_setopt(mCurl, CURLOPT_CONNECTTIMEOUT_MS, kConnectionTimeoutMs);
+
+ // Block until the operation is performend. Ignore the response, if the POST
+ // fails we can't do anything about it.
+ err = easy_perform(mCurl);
+ // Whatever happens, we want to clean up the header memory.
+ slist_free_all(headerChunk);
+
+ if (err != CURLE_OK) {
+ PINGSENDER_LOG("ERROR: Failed to send HTTP request, %s\n",
+ easy_strerror(err));
+ return false;
+ }
+
+ return true;
+}
+
+bool Post(const string& url, const string& payload) {
+ CurlWrapper curl;
+
+ if (!curl.Init()) {
+ return false;
+ }
+ if (!curl.IsValidDestination(url)) {
+ PINGSENDER_LOG("ERROR: Invalid destination host\n");
+ return false;
+ }
+
+ return curl.Post(url, payload);
+}
+
+void ChangeCurrentWorkingDirectory(const string& pingPath) {
+ // This is not needed under Linux/macOS
+}
+
+} // namespace PingSender