/* -*- 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 #include #include #include #include #include #include #include #include #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(const_cast(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(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(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 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 to the specified URL 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; }