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 request_builder.h
screenshot.cpp screenshot.h screenshot.cpp screenshot.h
stopreply.cpp stopreply.h stopreply.cpp stopreply.h
string_helpers.h
system_helpers.h system_helpers.h
ui_helpers.h ui_helpers.h
hotkeys/hotkeymap.h hotkeys/hotkeymap.h

View File

@ -6,19 +6,6 @@
class FileHelpers { class FileHelpers {
public: 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. /// writeFile write the provided content to the provided path.
/// returns false if failed. /// returns false if failed.
static bool writeFile(QString fileName, QByteArray content) { 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 /// 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(); } 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 * File Name : multipartparser.cpp
* Created By : Ye Yangang * Created By : Ye Yangang, ReWritten for Qt by Chris Rizzitello
* Creation Date : [2017-02-20 16:50] * Creation Date : [2017-02-20 16:50] Modified for Qt 7/5/2022
* Last Modified : [AUTO_UPDATE_BEFORE_SAVE] * Last Modified : [AUTO_UPDATE_BEFORE_SAVE]
* Description : Generate multipart/form-data POST body * Description : Generate multipart/form-data POST body
**********************************************************************************/ **********************************************************************************/
#include "multipartparser.h" #include "multipartparser.h"
#include <algorithm> #include <QFileInfo>
#include <fstream>
#include <future>
#include <time.h>
const std::string MultipartParser::boundary_prefix_("----ASHIRTTrayApp"); #include "string_helpers.h"
const std::string MultipartParser::rand_chars_(
"0123456789" MultipartParser::MultipartParser()
"abcdefghijklmnopqrstuvwxyz" : m_boundary(QStringLiteral("----ASHIRTTrayApp%1").arg(StringHelpers::randomString(16)))
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"); { }
MultipartParser::MultipartParser() {
int i = 0; const QByteArray &MultipartParser::generateBody()
int len = rand_chars_.size(); {
boundary_ = boundary_prefix_; m_body.clear();
while (i < 16) { for (const auto &param : m_paramList) {
int idx = rand() % len; m_body.append(m_contentHeader.arg(m_boundary).toUtf8());
boundary_.push_back(rand_chars_[idx]); m_body.append(m_contentParam.arg(param.first).toUtf8());
++i; m_body.append(param.second.toUtf8());
} }
} for (const auto &pair : m_fileList) {
QFileInfo info = QFileInfo(pair.second);
const std::string &MultipartParser::GenBodyContent() { QString name = info.fileName();
std::vector<std::future<std::string>> futures; QString ext = info.completeSuffix().toLower();
body_content_.clear(); QString type = QStringLiteral("application/octet-stream");
for (auto &file : files_) { QByteArray data;
std::future<std::string> content_futures = std::async(std::launch::async, [&file]() { QFile file(pair.second);
std::ifstream ifile(file.second, std::ios::binary | std::ios::ate); if (file.open(QIODevice::ReadOnly))
std::streamsize size = ifile.tellg(); data = file.readAll();
ifile.seekg(0, std::ios::beg); if(ext.endsWith(QStringLiteral("jpg")) || ext.endsWith(QStringLiteral("jpeg")))
char *buff = new char[size]; type = QStringLiteral("image/jpeg");
ifile.read(buff, size); else if(ext.endsWith(QStringLiteral("txt")) || ext.endsWith(QStringLiteral("log")))
ifile.close(); type = QStringLiteral("text/plain");
std::string ret(buff, size); m_body.append(m_contentHeader.arg(m_boundary).toUtf8());
delete[] buff; m_body.append(m_contentFile.arg(pair.first, name, type).toUtf8());
return ret; m_body.append(data);
}); }
futures.push_back(std::move(content_futures)); m_body.append(QStringLiteral("\r\n--%1--\r\n").arg(m_boundary).toUtf8());
} return m_body;
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;
} }

View File

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

View File

@ -69,8 +69,8 @@ class NetMan : public QObject {
/// ashirtFormPost generates a basic POST request with content type multipart/form-data. /// ashirtFormPost generates a basic POST request with content type multipart/form-data.
/// No authentication is provided (use addASHIRTAuth to do this) /// No authentication is provided (use addASHIRTAuth to do this)
RequestBuilder* ashirtFormPost(QString endpoint, QByteArray body, QString boundry) { RequestBuilder* ashirtFormPost(QString endpoint, QByteArray body, QString boundary) {
return RequestBuilder::newFormPost(boundry) return RequestBuilder::newFormPost(boundary)
->setHost(AppConfig::getInstance().apiURL) ->setHost(AppConfig::getInstance().apiURL)
->setEndpoint(endpoint) ->setEndpoint(endpoint)
->setBody(body); ->setBody(body);
@ -159,8 +159,8 @@ class NetMan : public QObject {
/// rather than the time of capture. /// rather than the time of capture.
QNetworkReply *uploadAsset(model::Evidence evidence) { QNetworkReply *uploadAsset(model::Evidence evidence) {
MultipartParser parser; MultipartParser parser;
parser.AddParameter("notes", evidence.description.toStdString()); parser.addParameter(QStringLiteral("notes"), evidence.description);
parser.AddParameter("contentType", evidence.contentType.toStdString()); parser.addParameter(QStringLiteral("contentType"), evidence.contentType);
// TODO: convert this time below into a proper unix timestamp (mSecSinceEpoch and secsSinceEpoch // TODO: convert this time below into a proper unix timestamp (mSecSinceEpoch and secsSinceEpoch
// produce invalid times) // produce invalid times)
@ -170,13 +170,13 @@ class NetMan : public QObject {
for (const auto& tag : evidence.tags) { for (const auto& tag : evidence.tags) {
list << QString::number(tag.serverTagId); 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); addASHIRTAuth(builder);
return builder->execute(nam); 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 /// 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(); RequestBuilder* builder = new RequestBuilder();
return builder return builder
->setMethod(METHOD_POST) ->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 /// newJSONPost constructs a request builder with method POST and contentType application/json

View File

@ -9,7 +9,7 @@
#include <QProcess> #include <QProcess>
#include "appconfig.h" #include "appconfig.h"
#include "helpers/file_helpers.h" #include "helpers/string_helpers.h"
#include "helpers/system_helpers.h" #include "helpers/system_helpers.h"
Screenshot::Screenshot(QObject *parent) : QObject(parent) {} Screenshot::Screenshot(QObject *parent) : QObject(parent) {}
@ -20,7 +20,7 @@ void Screenshot::captureWindow() { basicScreenshot(AppConfig::getInstance().capt
QString Screenshot::mkName() 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) 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 <QJsonObject>
#include "helpers/file_helpers.h" #include "helpers/file_helpers.h"
#include "helpers/string_helpers.h"
#include "helpers/system_helpers.h" #include "helpers/system_helpers.h"
#include "helpers/jsonhelpers.h" #include "helpers/jsonhelpers.h"
@ -25,7 +26,7 @@ Codeblock::Codeblock(QString content)
QString Codeblock::mkName() 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() QString Codeblock::extension()

View File

@ -1,5 +1,7 @@
#include "system_manifest.h" #include "system_manifest.h"
#include "helpers/string_helpers.h"
using namespace porting; using namespace porting;
void SystemManifest::applyManifest(SystemManifestImportOptions options, DatabaseConnection* systemDb) { void SystemManifest::applyManifest(SystemManifestImportOptions options, DatabaseConnection* systemDb) {
@ -93,7 +95,7 @@ QString SystemManifest::contentSensitiveFilename(const QString& contentType)
return Codeblock::mkName(); return Codeblock::mkName();
if(contentType == Screenshot::contentType()) if(contentType == Screenshot::contentType())
return Screenshot::mkName(); 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) { 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++) { for (size_t evidenceIndex = 0; evidenceIndex < allEvidence.size(); evidenceIndex++) {
auto evi = allEvidence.at(evidenceIndex); auto evi = allEvidence.at(evidenceIndex);
auto newName = QStringLiteral("ashirt_evidence_%1.%2") 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 item = porting::EvidenceItem(evi.id, relativeEvidenceDir + "/" + newName);
auto dstPath = m_fileTemplate.arg(baseExportPath, item.exportPath); auto dstPath = m_fileTemplate.arg(baseExportPath, item.exportPath);
QFile srcFile(evi.path); QFile srcFile(evi.path);