Rewrite multiparser for Qt (#188)

Co-authored-by: Chris Rizzitello <crizzitello@ics.com>
main
crizzitello 2022-07-07 18:29:43 -04:00 committed by GitHub
parent ccaa94825c
commit 240ecdc3d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 96 additions and 147 deletions

View File

@ -15,6 +15,7 @@ add_library (HELPERS STATIC
request_builder.h
screenshot.cpp screenshot.h
stopreply.cpp stopreply.h
string_helpers.h
system_helpers.h
ui_helpers.h
hotkeys/hotkeymap.h

View File

@ -6,19 +6,6 @@
class FileHelpers {
public:
/**
* @brief randomString Generates a random String of N chars
* Each character can be a-z either be upper or lower case.
* @param numberOfChars Length of the string to return default is 6
* @return The resulting randomString
*/
static QString randomString(int numberOfChars = 6) {
QString rString;
for(int i = 0; i < numberOfChars; i++)
rString.append(_chars.at(QRandomGenerator::global()->bounded(_chars.length())));
return rString;
}
/// writeFile write the provided content to the provided path.
/// returns false if failed.
static bool writeFile(QString fileName, QByteArray content) {
@ -49,7 +36,4 @@ class FileHelpers {
/// getDirname is a small helper to convert a filepath to a file into a path to the file's parent
static QString getDirname(QString filepath) { return QFileInfo(filepath).dir().path(); }
private:
inline static const QString _chars = QStringLiteral("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
};

View File

@ -1,104 +1,46 @@
/*********************************************************************************
* File Name : multipartparser.cpp
* Created By : Ye Yangang
* Creation Date : [2017-02-20 16:50]
* Created By : Ye Yangang, ReWritten for Qt by Chris Rizzitello
* Creation Date : [2017-02-20 16:50] Modified for Qt 7/5/2022
* Last Modified : [AUTO_UPDATE_BEFORE_SAVE]
* Description : Generate multipart/form-data POST body
**********************************************************************************/
#include "multipartparser.h"
#include <algorithm>
#include <fstream>
#include <future>
#include <time.h>
#include <QFileInfo>
const std::string MultipartParser::boundary_prefix_("----ASHIRTTrayApp");
const std::string MultipartParser::rand_chars_(
"0123456789"
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ");
MultipartParser::MultipartParser() {
int i = 0;
int len = rand_chars_.size();
boundary_ = boundary_prefix_;
while (i < 16) {
int idx = rand() % len;
boundary_.push_back(rand_chars_[idx]);
++i;
}
}
const std::string &MultipartParser::GenBodyContent() {
std::vector<std::future<std::string>> futures;
body_content_.clear();
for (auto &file : files_) {
std::future<std::string> content_futures = std::async(std::launch::async, [&file]() {
std::ifstream ifile(file.second, std::ios::binary | std::ios::ate);
std::streamsize size = ifile.tellg();
ifile.seekg(0, std::ios::beg);
char *buff = new char[size];
ifile.read(buff, size);
ifile.close();
std::string ret(buff, size);
delete[] buff;
return ret;
});
futures.push_back(std::move(content_futures));
}
for (auto &param : params_) {
body_content_ += "\r\n--";
body_content_ += boundary_;
body_content_ += "\r\nContent-Disposition: form-data; name=\"";
body_content_ += param.first;
body_content_ += "\"\r\n\r\n";
body_content_ += param.second;
}
for (size_t i = 0; i < files_.size(); ++i) {
std::string filename;
std::string content_type;
std::string file_content = futures[i].get();
_get_file_name_type(files_[i].second, &filename, &content_type);
body_content_ += "\r\n--";
body_content_ += boundary_;
body_content_ += "\r\nContent-Disposition: form-data; name=\"";
body_content_ += files_[i].first;
body_content_ += "\"; filename=\"";
body_content_ += filename;
body_content_ += "\"\r\nContent-Type: ";
body_content_ += content_type;
body_content_ += "\r\n\r\n";
body_content_ += file_content;
}
body_content_ += "\r\n--";
body_content_ += boundary_;
body_content_ += "--\r\n";
return body_content_;
}
void MultipartParser::_get_file_name_type(const std::string &file_path, std::string *filename,
std::string *content_type) {
if (filename == NULL || content_type == NULL) return;
size_t last_spliter = file_path.find_last_of("/\\");
*filename = file_path.substr(last_spliter + 1);
size_t dot_pos = filename->find_last_of(".");
if (dot_pos == std::string::npos) {
*content_type = "application/octet-stream";
return;
}
std::string ext = filename->substr(dot_pos + 1);
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
if (ext == "jpg" || ext == "jpeg") {
*content_type = "image/jpeg";
return;
}
if (ext == "txt" || ext == "log") {
*content_type = "text/plain";
return;
}
*content_type = "application/octet-stream";
return;
#include "string_helpers.h"
MultipartParser::MultipartParser()
: m_boundary(QStringLiteral("----ASHIRTTrayApp%1").arg(StringHelpers::randomString(16)))
{ }
const QByteArray &MultipartParser::generateBody()
{
m_body.clear();
for (const auto &param : m_paramList) {
m_body.append(m_contentHeader.arg(m_boundary).toUtf8());
m_body.append(m_contentParam.arg(param.first).toUtf8());
m_body.append(param.second.toUtf8());
}
for (const auto &pair : m_fileList) {
QFileInfo info = QFileInfo(pair.second);
QString name = info.fileName();
QString ext = info.completeSuffix().toLower();
QString type = QStringLiteral("application/octet-stream");
QByteArray data;
QFile file(pair.second);
if (file.open(QIODevice::ReadOnly))
data = file.readAll();
if(ext.endsWith(QStringLiteral("jpg")) || ext.endsWith(QStringLiteral("jpeg")))
type = QStringLiteral("image/jpeg");
else if(ext.endsWith(QStringLiteral("txt")) || ext.endsWith(QStringLiteral("log")))
type = QStringLiteral("text/plain");
m_body.append(m_contentHeader.arg(m_boundary).toUtf8());
m_body.append(m_contentFile.arg(pair.first, name, type).toUtf8());
m_body.append(data);
}
m_body.append(QStringLiteral("\r\n--%1--\r\n").arg(m_boundary).toUtf8());
return m_body;
}

View File

@ -1,38 +1,34 @@
/*********************************************************************************
* File Name : multipartparser.h
* Created By : Ye Yangang
* Creation Date : [2017-02-20 16:50]
* Created By : Ye Yangang, ReWritten for Qt by Chris Rizzitello
* Creation Date : [2017-02-20 16:50] Modified for Qt 7/5/2022
* Last Modified : [AUTO_UPDATE_BEFORE_SAVE]
* Description : Generate multipart/form-data POST body
**********************************************************************************/
#pragma once
#include <string>
#include <vector>
#include <QList>
#include <QPair>
#include <QString>
class MultipartParser {
public:
MultipartParser();
inline const std::string &body_content() { return body_content_; }
inline const std::string &boundary() { return boundary_; }
inline void AddParameter(const std::string &name, const std::string &value) {
params_.push_back(std::pair<std::string, std::string>(name, value));
inline const QString &boundary() {return m_boundary;}
inline void addParameter(const QString &name = QString(), const QString &value = QString()) {
m_paramList.append(QPair<QString, QString>(name, value));
}
inline void AddFile(const std::string &name, const std::string &value) {
files_.push_back(std::pair<std::string, std::string>(name, value));
inline void addFile(const QString &name = QString(), const QString &value = QString()) {
m_fileList.append(QPair<QString, QString>(name, value));
}
const std::string &GenBodyContent();
const QByteArray &generateBody();
private:
void _get_file_name_type(const std::string &file_path, std::string *filenae,
std::string *content_type);
private:
static const std::string boundary_prefix_;
static const std::string rand_chars_;
std::string boundary_;
std::string body_content_;
std::vector<std::pair<std::string, std::string>> params_;
std::vector<std::pair<std::string, std::string>> files_;
inline static const auto m_contentHeader = QStringLiteral("\r\n--%1\r\n");
inline static const auto m_contentParam = QStringLiteral("Content-Disposition: form-data; name=\"%1\"\r\n\r\n");
inline static const auto m_contentFile = QStringLiteral("Content-Disposition: form-data; name=\"%1\"; filename=\"%2\"\r\nContent-Type: %3\r\n\r\n");
QString m_boundary;
QByteArray m_body;
QList<QPair<QString, QString>> m_paramList;
QList<QPair<QString, QString>> m_fileList;
};

View File

@ -69,8 +69,8 @@ class NetMan : public QObject {
/// ashirtFormPost generates a basic POST request with content type multipart/form-data.
/// No authentication is provided (use addASHIRTAuth to do this)
RequestBuilder* ashirtFormPost(QString endpoint, QByteArray body, QString boundry) {
return RequestBuilder::newFormPost(boundry)
RequestBuilder* ashirtFormPost(QString endpoint, QByteArray body, QString boundary) {
return RequestBuilder::newFormPost(boundary)
->setHost(AppConfig::getInstance().apiURL)
->setEndpoint(endpoint)
->setBody(body);
@ -159,8 +159,8 @@ class NetMan : public QObject {
/// rather than the time of capture.
QNetworkReply *uploadAsset(model::Evidence evidence) {
MultipartParser parser;
parser.AddParameter("notes", evidence.description.toStdString());
parser.AddParameter("contentType", evidence.contentType.toStdString());
parser.addParameter(QStringLiteral("notes"), evidence.description);
parser.addParameter(QStringLiteral("contentType"), evidence.contentType);
// TODO: convert this time below into a proper unix timestamp (mSecSinceEpoch and secsSinceEpoch
// produce invalid times)
@ -170,13 +170,13 @@ class NetMan : public QObject {
for (const auto& tag : evidence.tags) {
list << QString::number(tag.serverTagId);
}
parser.AddParameter("tagIds", ("[" + list.join(",") + "]").toStdString());
parser.addParameter(QStringLiteral("tagIds"), QStringLiteral("[%1]").arg(list.join(QStringLiteral(","))));
parser.AddFile("file", evidence.path.toStdString());
parser.addFile(QStringLiteral("file"), evidence.path);
auto body = QByteArray::fromStdString(parser.GenBodyContent());
auto body = parser.generateBody();
auto builder = ashirtFormPost(QStringLiteral("/api/operations/%1/evidence").arg(evidence.operationSlug), body, parser.boundary().c_str());
auto builder = ashirtFormPost(QStringLiteral("/api/operations/%1/evidence").arg(evidence.operationSlug), body, parser.boundary());
addASHIRTAuth(builder);
return builder->execute(nam);
}

View File

@ -59,11 +59,11 @@ class RequestBuilder {
}
/// newFormPost constructs a request builder with method POST and contentType multipart/form-data
static RequestBuilder* newFormPost(QString formBoundry) {
static RequestBuilder* newFormPost(QString formboundary) {
RequestBuilder* builder = new RequestBuilder();
return builder
->setMethod(METHOD_POST)
->addKnownHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("multipart/form-data; boundary=%1").arg(formBoundry));
->addKnownHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("multipart/form-data; boundary=%1").arg(formboundary));
}
/// newJSONPost constructs a request builder with method POST and contentType application/json

View File

@ -9,7 +9,7 @@
#include <QProcess>
#include "appconfig.h"
#include "helpers/file_helpers.h"
#include "helpers/string_helpers.h"
#include "helpers/system_helpers.h"
Screenshot::Screenshot(QObject *parent) : QObject(parent) {}
@ -20,7 +20,7 @@ void Screenshot::captureWindow() { basicScreenshot(AppConfig::getInstance().capt
QString Screenshot::mkName()
{
return QStringLiteral("ashirt_screenshot_%1.%2").arg(FileHelpers::randomString(), extension());
return QStringLiteral("ashirt_screenshot_%1.%2").arg(StringHelpers::randomString(), extension());
}
void Screenshot::basicScreenshot(QString cmdProto)

View File

@ -0,0 +1,23 @@
#pragma once
#include <QRandomGenerator>
#include <QString>
class StringHelpers {
public:
/**
* @brief randomString Generates a random String of N chars
* Each character can be a-z either be upper or lower case.
* @param numberOfChars Length of the string to return default is 6
* @return The resulting randomString
*/
static QString randomString(int numberOfChars = 6) {
QString rString;
for(int i = 0; i < numberOfChars; i++)
rString.append(_chars.at(QRandomGenerator::global()->bounded(_chars.length())));
return rString;
}
private:
inline static const QString _chars = QStringLiteral("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
};

View File

@ -4,6 +4,7 @@
#include <QJsonObject>
#include "helpers/file_helpers.h"
#include "helpers/string_helpers.h"
#include "helpers/system_helpers.h"
#include "helpers/jsonhelpers.h"
@ -25,7 +26,7 @@ Codeblock::Codeblock(QString content)
QString Codeblock::mkName()
{
return QStringLiteral("ashirt_codeblock_%1.%2").arg(FileHelpers::randomString(), extension());
return QStringLiteral("ashirt_codeblock_%1.%2").arg(StringHelpers::randomString(), extension());
}
QString Codeblock::extension()

View File

@ -1,5 +1,7 @@
#include "system_manifest.h"
#include "helpers/string_helpers.h"
using namespace porting;
void SystemManifest::applyManifest(SystemManifestImportOptions options, DatabaseConnection* systemDb) {
@ -93,7 +95,7 @@ QString SystemManifest::contentSensitiveFilename(const QString& contentType)
return Codeblock::mkName();
if(contentType == Screenshot::contentType())
return Screenshot::mkName();
return QStringLiteral("ashirt_unknown_type_%1.bin").arg(FileHelpers::randomString());
return QStringLiteral("ashirt_unknown_type_%1.bin").arg(StringHelpers::randomString());
}
SystemManifest* SystemManifest::readManifest(const QString& pathToExportFile) {
@ -154,7 +156,7 @@ porting::EvidenceManifest SystemManifest::copyEvidence(const QString& baseExport
for (size_t evidenceIndex = 0; evidenceIndex < allEvidence.size(); evidenceIndex++) {
auto evi = allEvidence.at(evidenceIndex);
auto newName = QStringLiteral("ashirt_evidence_%1.%2")
.arg(FileHelpers::randomString(10), contentSensitiveExtension(evi.contentType));
.arg(StringHelpers::randomString(10), contentSensitiveExtension(evi.contentType));
auto item = porting::EvidenceItem(evi.id, relativeEvidenceDir + "/" + newName);
auto dstPath = m_fileTemplate.arg(baseExportPath, item.exportPath);
QFile srcFile(evi.path);