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 <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;
}