diff options
Diffstat (limited to 'toolkit/components/telemetry/pingsender/pingsender.cpp')
-rw-r--r-- | toolkit/components/telemetry/pingsender/pingsender.cpp | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/pingsender/pingsender.cpp b/toolkit/components/telemetry/pingsender/pingsender.cpp new file mode 100644 index 0000000000..01fdb63c29 --- /dev/null +++ b/toolkit/components/telemetry/pingsender/pingsender.cpp @@ -0,0 +1,234 @@ +/* -*- 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 <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 { + +const char* kUserAgent = "pingsender/1.0"; +const char* kCustomVersionHeader = "X-PingSender-Version: 1.0"; +const char* kContentEncodingHeader = "Content-Encoding: gzip"; +// The maximum time, in milliseconds, we allow for the connection phase +// to the server. +const uint32_t kConnectionTimeoutMs = 30 * 1000; + +// 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; +} |