Source code

Revision control

Other Tools

/* -*- 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"
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 ::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 = ::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