1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 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;
}
|