diff options
Diffstat (limited to 'toolkit/components/telemetry/pingsender')
6 files changed, 807 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/pingsender/moz.build b/toolkit/components/telemetry/pingsender/moz.build new file mode 100644 index 0000000000..e3526f4c34 --- /dev/null +++ b/toolkit/components/telemetry/pingsender/moz.build @@ -0,0 +1,38 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +if CONFIG["OS_TARGET"] != "Android": + GeckoProgram("pingsender", linkage=None) + + UNIFIED_SOURCES += [ + "pingsender.cpp", + ] + + LOCAL_INCLUDES += [ + "/toolkit/crashreporter/google-breakpad/src", + ] + + USE_LIBS += [ + "zlib", + ] + +if CONFIG["OS_TARGET"] == "WINNT": + UNIFIED_SOURCES += [ + "pingsender_win.cpp", + ] + + OS_LIBS += [ + "wininet", + ] +else: + UNIFIED_SOURCES += [ + "pingsender_unix_common.cpp", + ] + + +# Don't use the STL wrappers; we don't link with -lmozalloc, and it really +# doesn't matter here anyway. +DisableStlWrapping() diff --git a/toolkit/components/telemetry/pingsender/pingsender.cpp b/toolkit/components/telemetry/pingsender/pingsender.cpp new file mode 100644 index 0000000000..30f2907c72 --- /dev/null +++ b/toolkit/components/telemetry/pingsender/pingsender.cpp @@ -0,0 +1,228 @@ +/* -*- 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 <cstdlib> +#include <cstdint> +#include <cstring> +#include <ctime> +#include <fstream> +#include <iomanip> +#include <string> +#include <vector> + +#include <zlib.h> + +#include "pingsender.h" + +using std::ifstream; +using std::ios; +using std::string; +using std::vector; + +namespace PingSender { + +// Operate in std::string because nul bytes will be preserved +bool IsValidDestination(std::string aHost) { + static const std::string kValidDestinations[] = { + "localhost", + "incoming.telemetry.mozilla.org", + }; + for (auto destination : kValidDestinations) { + if (aHost == destination) { + return true; + } + } + return false; +} + +bool IsValidDestination(char* aHost) { + return IsValidDestination(std::string(aHost)); +} + +/** + * This shared function returns a Date header string for use in HTTP requests. + * See "RFC 7231, section 7.1.1.2: Date" for its specifications. + */ +std::string GenerateDateHeader() { + char buffer[128]; + std::time_t t = std::time(nullptr); + strftime(buffer, sizeof(buffer), "Date: %a, %d %b %Y %H:%M:%S GMT", + std::gmtime(&t)); + return string(buffer); +} + +std::string GzipCompress(const std::string& rawData) { + z_stream deflater = {}; + + // Use the maximum window size when compressing: this also tells zlib to + // generate a gzip header. + const int32_t kWindowSize = MAX_WBITS + 16; + if (deflateInit2(&deflater, Z_DEFAULT_COMPRESSION, Z_DEFLATED, kWindowSize, 8, + Z_DEFAULT_STRATEGY) != Z_OK) { + PINGSENDER_LOG("ERROR: Could not initialize zlib deflating\n"); + return ""; + } + + // Initialize the output buffer. The size of the buffer is the same + // as defined by the ZIP_BUFLEN macro in Gecko. + const uint32_t kBufferSize = 4 * 1024 - 1; + unsigned char outputBuffer[kBufferSize]; + deflater.next_out = outputBuffer; + deflater.avail_out = kBufferSize; + + // Let zlib know about the input data. + deflater.avail_in = rawData.size(); + deflater.next_in = + reinterpret_cast<Bytef*>(const_cast<char*>(rawData.c_str())); + + // Compress and append chunk by chunk. + std::string gzipData; + int err = Z_OK; + + while (deflater.avail_in > 0 && err == Z_OK) { + err = deflate(&deflater, Z_NO_FLUSH); + + // Since we're using the Z_NO_FLUSH policy, zlib can decide how + // much data to compress. When the buffer is full, we repeadetly + // flush out. + while (deflater.avail_out == 0) { + gzipData.append(reinterpret_cast<const char*>(outputBuffer), kBufferSize); + + // Update the state and let the deflater know about it. + deflater.next_out = outputBuffer; + deflater.avail_out = kBufferSize; + err = deflate(&deflater, Z_NO_FLUSH); + } + } + + // Flush the deflater buffers. + while (err == Z_OK) { + err = deflate(&deflater, Z_FINISH); + size_t bytesToWrite = kBufferSize - deflater.avail_out; + if (bytesToWrite == 0) { + break; + } + gzipData.append(reinterpret_cast<const char*>(outputBuffer), bytesToWrite); + deflater.next_out = outputBuffer; + deflater.avail_out = kBufferSize; + } + + // Clean up. + deflateEnd(&deflater); + + if (err != Z_STREAM_END) { + PINGSENDER_LOG("ERROR: There was a problem while compressing the ping\n"); + return ""; + } + + return gzipData; +} + +class Ping { + public: + Ping(const string& aUrl, const string& aPath) : mUrl(aUrl), mPath(aPath) {} + bool Send() const; + bool Delete() const; + + private: + string Read() const; + + const string mUrl; + const string mPath; +}; + +bool Ping::Send() const { + string ping(Read()); + + if (ping.empty()) { + PINGSENDER_LOG("ERROR: Ping payload is empty\n"); + return false; + } + + // Compress the ping using gzip. + string gzipPing(GzipCompress(ping)); + + // In the unlikely event of failure to gzip-compress the ping, don't + // attempt to send it uncompressed: Telemetry will pick it up and send + // it compressed. + if (gzipPing.empty()) { + PINGSENDER_LOG("ERROR: Ping compression failed\n"); + return false; + } + + if (!Post(mUrl, gzipPing)) { + return false; + } + + return true; +} + +bool Ping::Delete() const { + return !mPath.empty() && !std::remove(mPath.c_str()); +} + +string Ping::Read() const { + string ping; + ifstream file; + + file.open(mPath.c_str(), ios::in | ios::binary); + + if (!file.is_open()) { + PINGSENDER_LOG("ERROR: Could not open ping file\n"); + return ""; + } + + do { + char buff[4096]; + + file.read(buff, sizeof(buff)); + + if (file.bad()) { + PINGSENDER_LOG("ERROR: Could not read ping contents\n"); + return ""; + } + + ping.append(buff, file.gcount()); + } while (!file.eof()); + + return ping; +} + +} // namespace PingSender + +using namespace PingSender; + +int main(int argc, char* argv[]) { + vector<Ping> pings; + + if ((argc >= 3) && ((argc - 1) % 2 == 0)) { + for (int i = 1; i < argc; i += 2) { + Ping ping(argv[i], argv[i + 1]); + pings.push_back(ping); + } + } else { + PINGSENDER_LOG( + "Usage: pingsender URL1 PATH1 URL2 PATH2 ...\n" + "Send the payloads stored in PATH<n> to the specified URL<n> using an " + "HTTP POST\nmessage for each payload then delete the file after a " + "successful send.\n"); + return EXIT_FAILURE; + } + + ChangeCurrentWorkingDirectory(argv[2]); + + for (const auto& ping : pings) { + if (!ping.Send()) { + return EXIT_FAILURE; + } + + if (!ping.Delete()) { + PINGSENDER_LOG("ERROR: Could not delete the ping file\n"); + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +} diff --git a/toolkit/components/telemetry/pingsender/pingsender.exe.manifest b/toolkit/components/telemetry/pingsender/pingsender.exe.manifest new file mode 100644 index 0000000000..8e4bb8749b --- /dev/null +++ b/toolkit/components/telemetry/pingsender/pingsender.exe.manifest @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<assemblyIdentity + version="1.0.0.0" + processorArchitecture="*" + name="pingsender" + type="win32" +/> +<dependency> + <dependentAssembly> + <assemblyIdentity + type="win32" + name="mozglue" + version="1.0.0.0" + language="*" + /> + </dependentAssembly> +</dependency> +</assembly> diff --git a/toolkit/components/telemetry/pingsender/pingsender.h b/toolkit/components/telemetry/pingsender/pingsender.h new file mode 100644 index 0000000000..2359738abf --- /dev/null +++ b/toolkit/components/telemetry/pingsender/pingsender.h @@ -0,0 +1,41 @@ +/* -*- 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/. */ + +#ifndef mozilla_telemetry_pingsender_h +#define mozilla_telemetry_pingsender_h + +#include <string> + +#ifdef DEBUG +# define PINGSENDER_LOG(s, ...) printf(s, ##__VA_ARGS__) +#else +# define PINGSENDER_LOG(s, ...) +#endif // DEBUG + +namespace PingSender { + +// The maximum time, in milliseconds, we allow for the connection phase +// to the server. +constexpr uint32_t kConnectionTimeoutMs = 30 * 1000; +constexpr char kUserAgent[] = "pingsender/1.0"; +constexpr char kCustomVersionHeader[] = "X-PingSender-Version: 1.0"; +constexpr char kContentEncodingHeader[] = "Content-Encoding: gzip"; + +// System-specific function that changes the current working directory to be +// the same as the one containing the ping file. This is currently required on +// Windows to release the Firefox installation folder (see bug 1597803 for more +// details) and is a no-op on other platforms. +void ChangeCurrentWorkingDirectory(const std::string& pingPath); + +// System-specific function to make an HTTP POST operation +bool Post(const std::string& url, const std::string& payload); + +bool IsValidDestination(char* aUriEndingInHost); +bool IsValidDestination(std::string aUriEndingInHost); +std::string GenerateDateHeader(); + +} // namespace PingSender + +#endif 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 diff --git a/toolkit/components/telemetry/pingsender/pingsender_win.cpp b/toolkit/components/telemetry/pingsender/pingsender_win.cpp new file mode 100644 index 0000000000..c0c250d7c5 --- /dev/null +++ b/toolkit/components/telemetry/pingsender/pingsender_win.cpp @@ -0,0 +1,180 @@ +/* -*- 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 <string> + +#include <direct.h> +#include <windows.h> +#include <wininet.h> + +#include "pingsender.h" + +namespace PingSender { + +using std::string; + +/** + * A helper to automatically close internet handles when they go out of scope + */ +class ScopedHInternet { + public: + explicit ScopedHInternet(HINTERNET handle) : mHandle(handle) {} + + ~ScopedHInternet() { + if (mHandle) { + InternetCloseHandle(mHandle); + } + } + + bool empty() { return (mHandle == nullptr); } + HINTERNET get() { return mHandle; } + + private: + HINTERNET mHandle; +}; + +const size_t kUrlComponentsSchemeLength = 256; +const size_t kUrlComponentsHostLength = 256; +const size_t kUrlComponentsPathLength = 256; + +/** + * Post the specified payload to a telemetry server + * + * @param url The URL of the telemetry server + * @param payload The ping payload + */ +bool Post(const string& url, const string& payload) { + char scheme[kUrlComponentsSchemeLength]; + char host[kUrlComponentsHostLength]; + char path[kUrlComponentsPathLength]; + + URL_COMPONENTS components = {}; + components.dwStructSize = sizeof(components); + components.lpszScheme = scheme; + components.dwSchemeLength = kUrlComponentsSchemeLength; + components.lpszHostName = host; + components.dwHostNameLength = kUrlComponentsHostLength; + components.lpszUrlPath = path; + components.dwUrlPathLength = kUrlComponentsPathLength; + + if (!InternetCrackUrl(url.c_str(), url.size(), 0, &components)) { + PINGSENDER_LOG("ERROR: Could not separate the URL components\n"); + return false; + } + + if (!IsValidDestination(host)) { + PINGSENDER_LOG("ERROR: Invalid destination host '%s'\n", host); + return false; + } + + ScopedHInternet internet(InternetOpen(kUserAgent, + INTERNET_OPEN_TYPE_PRECONFIG, + /* lpszProxyName */ NULL, + /* lpszProxyBypass */ NULL, + /* dwFlags */ 0)); + + if (internet.empty()) { + PINGSENDER_LOG("ERROR: Could not open wininet internet handle\n"); + return false; + } + + DWORD timeout = static_cast<DWORD>(kConnectionTimeoutMs); + bool rv = InternetSetOption(internet.get(), INTERNET_OPTION_CONNECT_TIMEOUT, + &timeout, sizeof(timeout)); + if (!rv) { + PINGSENDER_LOG("ERROR: Could not set the connection timeout\n"); + return false; + } + + ScopedHInternet connection( + InternetConnect(internet.get(), host, components.nPort, + /* lpszUsername */ NULL, + /* lpszPassword */ NULL, INTERNET_SERVICE_HTTP, + /* dwFlags */ 0, + /* dwContext */ NULL)); + + if (connection.empty()) { + PINGSENDER_LOG("ERROR: Could not connect\n"); + return false; + } + + DWORD flags = ((strcmp(scheme, "https") == 0) ? INTERNET_FLAG_SECURE : 0) | + INTERNET_FLAG_NO_COOKIES; + ScopedHInternet request(HttpOpenRequest(connection.get(), "POST", path, + /* lpszVersion */ NULL, + /* lpszReferer */ NULL, + /* lplpszAcceptTypes */ NULL, flags, + /* dwContext */ NULL)); + + if (request.empty()) { + PINGSENDER_LOG("ERROR: Could not open HTTP POST request\n"); + return false; + } + + // Build a string containing all the headers. + std::string headers = GenerateDateHeader() + "\r\n"; + headers += kCustomVersionHeader; + headers += "\r\n"; + headers += kContentEncodingHeader; + + rv = HttpSendRequest(request.get(), headers.c_str(), -1L, + (LPVOID)payload.c_str(), payload.size()); + if (!rv) { + PINGSENDER_LOG("ERROR: Could not execute HTTP POST request\n"); + return false; + } + + // HttpSendRequest doesn't fail if we hit an HTTP error, so manually check + // for errors. Please note that this is not needed on the Linux/MacOS version + // of the pingsender, as libcurl already automatically fails on HTTP errors. + DWORD statusCode = 0; + DWORD bufferLength = sizeof(DWORD); + rv = HttpQueryInfo( + request.get(), + /* dwInfoLevel */ HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, + /* lpvBuffer */ &statusCode, + /* lpdwBufferLength */ &bufferLength, + /* lpdwIndex */ NULL); + if (!rv) { + PINGSENDER_LOG("ERROR: Could not get the HTTP status code\n"); + return false; + } + + if (statusCode != 200) { + PINGSENDER_LOG("ERROR: Error submitting the HTTP request: code %lu\n", + statusCode); + return false; + } + + return rv; +} + +void ChangeCurrentWorkingDirectory(const string& pingPath) { + char fullPath[MAX_PATH + 1] = {}; + if (!_fullpath(fullPath, pingPath.c_str(), sizeof(fullPath))) { + PINGSENDER_LOG("Could not build the full path to the ping\n"); + return; + } + + char drive[_MAX_DRIVE] = {}; + char dir[_MAX_DIR] = {}; + if (_splitpath_s(fullPath, drive, sizeof(drive), dir, sizeof(dir), nullptr, 0, + nullptr, 0)) { + PINGSENDER_LOG("Could not split the current path\n"); + return; + } + + char cwd[MAX_PATH + 1] = {}; + if (_makepath_s(cwd, sizeof(cwd), drive, dir, nullptr, nullptr)) { + PINGSENDER_LOG("Could not assemble the path for the new cwd\n"); + return; + } + + if (_chdir(cwd) == -1) { + PINGSENDER_LOG("Could not change the current working directory\n"); + } +} + +} // namespace PingSender |