From a7fe8c4fb9c71ae6616ffe3cbbbe905fe2408f63 Mon Sep 17 00:00:00 2001 From: Leonetienne Date: Fri, 21 Jan 2022 18:38:13 +0100 Subject: [PATCH] Initialized cli project. --- .gitignore | 2 + GhettoCrypt.sln | 13 + GhettoCryptCLI/CommandlineInterface.cpp | 144 ++ GhettoCryptCLI/CommandlineInterface.h | 20 + GhettoCryptCLI/GhettoCryptCLI.vcxproj | 158 ++ GhettoCryptCLI/GhettoCryptCLI.vcxproj.filters | 36 + GhettoCryptCLI/Hazelnupp.cpp | 1302 +++++++++++++++++ GhettoCryptCLI/Hazelnupp.h | 813 ++++++++++ GhettoCryptCLI/Version.h | 2 + GhettoCryptCLI/main.cpp | 161 ++ 10 files changed, 2651 insertions(+) create mode 100644 GhettoCryptCLI/CommandlineInterface.cpp create mode 100644 GhettoCryptCLI/CommandlineInterface.h create mode 100644 GhettoCryptCLI/GhettoCryptCLI.vcxproj create mode 100644 GhettoCryptCLI/GhettoCryptCLI.vcxproj.filters create mode 100644 GhettoCryptCLI/Hazelnupp.cpp create mode 100644 GhettoCryptCLI/Hazelnupp.h create mode 100644 GhettoCryptCLI/Version.h create mode 100644 GhettoCryptCLI/main.cpp diff --git a/.gitignore b/.gitignore index 45584dc..310e849 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ #infilename-tag *_gitignore_* +*.jpg + # User-specific files *.rsuser *.suo diff --git a/GhettoCrypt.sln b/GhettoCrypt.sln index 844e746..e33ff97 100644 --- a/GhettoCrypt.sln +++ b/GhettoCrypt.sln @@ -7,6 +7,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GhettoCrypt", "GhettoCrypt\ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ExampleApp", "ExampleApp\ExampleApp.vcxproj", "{B9A390AC-F382-42E6-92DD-3321293E7C27}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GhettoCryptCLI", "GhettoCryptCLI\GhettoCryptCLI.vcxproj", "{719B8ECB-BA7B-471A-9B61-9EACA2289175}" + ProjectSection(ProjectDependencies) = postProject + {2B2CF665-F5E6-44DB-961F-FC81C88A356D} = {2B2CF665-F5E6-44DB-961F-FC81C88A356D} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -31,6 +36,14 @@ Global {B9A390AC-F382-42E6-92DD-3321293E7C27}.Release|x64.Build.0 = Release|x64 {B9A390AC-F382-42E6-92DD-3321293E7C27}.Release|x86.ActiveCfg = Release|Win32 {B9A390AC-F382-42E6-92DD-3321293E7C27}.Release|x86.Build.0 = Release|Win32 + {719B8ECB-BA7B-471A-9B61-9EACA2289175}.Debug|x64.ActiveCfg = Debug|x64 + {719B8ECB-BA7B-471A-9B61-9EACA2289175}.Debug|x64.Build.0 = Debug|x64 + {719B8ECB-BA7B-471A-9B61-9EACA2289175}.Debug|x86.ActiveCfg = Debug|Win32 + {719B8ECB-BA7B-471A-9B61-9EACA2289175}.Debug|x86.Build.0 = Debug|Win32 + {719B8ECB-BA7B-471A-9B61-9EACA2289175}.Release|x64.ActiveCfg = Release|x64 + {719B8ECB-BA7B-471A-9B61-9EACA2289175}.Release|x64.Build.0 = Release|x64 + {719B8ECB-BA7B-471A-9B61-9EACA2289175}.Release|x86.ActiveCfg = Release|Win32 + {719B8ECB-BA7B-471A-9B61-9EACA2289175}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/GhettoCryptCLI/CommandlineInterface.cpp b/GhettoCryptCLI/CommandlineInterface.cpp new file mode 100644 index 0000000..d09738c --- /dev/null +++ b/GhettoCryptCLI/CommandlineInterface.cpp @@ -0,0 +1,144 @@ +#include "CommandlineInterface.h" +#include +#include "../GhettoCrypt/Version.h" +#include "../GhettoCrypt/Config.h" +#include "Version.h" + +using namespace Hazelnp; + +void CommandlineInterface::Init(int argc, const char* const* argv) +{ + /* General information */ + std::stringstream ss; + ss << "CLI for the ghettocrypt cipher/obfuscator" << std::endl + << "Copyright (c) 2022 Leon Etienne" << std::endl + << "Ghettocrypt v" << GHETTOCRYPT_VERSION << std::endl + << "Ghettocrypt CLI v" << GHETTOCRYPTCLI_VERSION << std::endl + << "THIS IS EXPERIMENTAL SOFTWARE AND MUST BE CONSIDERED INSECURE. DO NOT USE THIS TO ENCRYPT SENSITIVE DATA! READ THE README FILE ACCESSIBLE AT \"https://github.com/Leonetienne/GhettoCrypt/blob/master/readme.md\""; + nupp.SetBriefDescription(ss.str()); + ss.str(""); + nupp.SetCatchHelp("true"); + nupp.SetCrashOnFail("true"); + + /* Builtin documentation */ + nupp.RegisterDescription("--encrypt", "Use the encryption routine."); + nupp.RegisterConstraint("--encrypt", ParamConstraint(true, DATA_TYPE::VOID, {}, false, {"--decrypt"})); + nupp.RegisterAbbreviation("-e", "--encrypt"); + + nupp.RegisterDescription("--decrypt", "Use decryption routine."); + nupp.RegisterConstraint("--decrypt", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--encrypt" })); + nupp.RegisterAbbreviation("-d", "--decrypt"); + + nupp.RegisterDescription("--intext", "Encrypt this string. Dumps to stdout."); + nupp.RegisterConstraint("--intext", ParamConstraint(true, DATA_TYPE::STRING, {}, false, { "--infile" })); + nupp.RegisterAbbreviation("-it", "--intext"); + + nupp.RegisterDescription("--infile", "Encrypt this file. Saves as {filename}.crypt, if not specified otherwise."); + nupp.RegisterConstraint("--infile", ParamConstraint(true, DATA_TYPE::STRING, {}, false, { "--intext" })); + nupp.RegisterAbbreviation("-if", "--infile"); + + nupp.RegisterDescription("--ofile", "Use this filename for output if --infile is specified. Gets ignored otherwise."); + nupp.RegisterConstraint("--ofile", ParamConstraint(true, DATA_TYPE::STRING, {}, false, { "--ostdout" })); + nupp.RegisterAbbreviation("-of", "--ofile"); + nupp.RegisterAbbreviation("-o", "--ofile"); + + nupp.RegisterDescription("--ostdout", "Output of digested files will be dumped to stdout instead of a file."); + nupp.RegisterConstraint("--ostdout", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--ofile" })); + + nupp.RegisterDescription("--key", "Use this value as a password to extrapolate the encryption key. WARNING: Arguments may be logged by the system!"); + nupp.RegisterConstraint("--key", ParamConstraint(true, DATA_TYPE::STRING, {}, false, { "--keyfile", "--keyask" })); + nupp.RegisterAbbreviation("-k", "--key"); + + ss << "Read in the first {KEYSIZE}(=" << GhettoCipher::BLOCK_SIZE << ") bits of this file and use that as an encryption key. WARNING: Arguments may be logged by the system!"; + nupp.RegisterDescription("--keyfile", ss.str()); + ss.str(""); + nupp.RegisterConstraint("--keyfile", ParamConstraint(true, DATA_TYPE::STRING, {}, false, { "--key", "--keyask" })); + nupp.RegisterAbbreviation("-kf", "--keyfile"); + + nupp.RegisterDescription("--keyask", "Read the encryption key from stdin."); + nupp.RegisterConstraint("--keyask", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--key", "--keyfile" })); + nupp.RegisterAbbreviation("-ka", "--keyask"); + + nupp.RegisterDescription("--progress", "Print digestion progress to stdout. May be advisable for large files, as the cipher is rather slow."); + nupp.RegisterConstraint("--progress", ParamConstraint(true, DATA_TYPE::VOID, {}, false, {})); + nupp.RegisterAbbreviation("-p", "--progress"); + + nupp.RegisterDescription("--version", "Will supply the version of ghettocrypt used."); + nupp.RegisterConstraint("--version", ParamConstraint(true, DATA_TYPE::VOID, {}, false, {})); + nupp.RegisterAbbreviation("-v", "--version"); + + nupp.RegisterDescription("--cli-version", "Will supply the version of ghettocrypt-cli used."); + nupp.RegisterConstraint("--cli-version", ParamConstraint(true, DATA_TYPE::VOID, {}, false, {})); + + /* Now parse */ + nupp.Parse(argc, argv); + + CatchVersionQueries(); + SpecialCompatibilityChecking(); + + return; +} + +Hazelnp::CmdArgsInterface& CommandlineInterface::Get() +{ + return nupp; +} + +void CommandlineInterface::SpecialCompatibilityChecking() +{ + // Encryption key + // Do we have EITHER --key, --keyask or --keyfile given? + if ( + (!nupp.HasParam("--key")) && + (!nupp.HasParam("--keyfile")) && + (!nupp.HasParam("--keyask")) + ) + CrashWithMsg("No encryption key supplied! Please supply either --key, --keyfile, or --keyask!"); + + // Encryption input + // Do we have EITHER --intext or --infile? + if ( + (!nupp.HasParam("--intext")) && + (!nupp.HasParam("--infile")) + ) + CrashWithMsg("No encryption input supplied! Please supply either --intext or --infile!"); + + // Encryption mode + // Do we have EITHER --encrypt or --decrypt? + if ( + (!nupp.HasParam("--encrypt")) && + (!nupp.HasParam("--decrypt")) + ) + CrashWithMsg("No encryption mode supplied! Please supply either --encrypt or --decrypt!"); + + return; +} + +void CommandlineInterface::CrashWithMsg(const std::string& msg) +{ + std::cerr + << nupp.GetBriefDescription() + << std::endl + << "Fatal error! Unable to continue! More information:" << std::endl + << msg << std::endl; + + exit(-1); +} + +void CommandlineInterface::CatchVersionQueries() +{ + if (nupp.HasParam("--version")) + { + std::cout << GHETTOCRYPT_VERSION << std::endl; + exit(0); + } + else if (nupp.HasParam("--cli-version")) + { + std::cout << GHETTOCRYPTCLI_VERSION << std::endl; + exit(0); + } + + return; +} + +CmdArgsInterface CommandlineInterface::nupp; diff --git a/GhettoCryptCLI/CommandlineInterface.h b/GhettoCryptCLI/CommandlineInterface.h new file mode 100644 index 0000000..d142933 --- /dev/null +++ b/GhettoCryptCLI/CommandlineInterface.h @@ -0,0 +1,20 @@ +#pragma once +#include "Hazelnupp.h" + +class CommandlineInterface +{ +public: + static void Init(const int argc, const char* const* argv); + + static Hazelnp::CmdArgsInterface& Get(); + +private: + //! Special command compatibility checking unique to this app + static void SpecialCompatibilityChecking(); + static void CrashWithMsg(const std::string& msg); + static void CatchVersionQueries(); + + CommandlineInterface() {}; + + static Hazelnp::CmdArgsInterface nupp; +}; diff --git a/GhettoCryptCLI/GhettoCryptCLI.vcxproj b/GhettoCryptCLI/GhettoCryptCLI.vcxproj new file mode 100644 index 0000000..a7fc35a --- /dev/null +++ b/GhettoCryptCLI/GhettoCryptCLI.vcxproj @@ -0,0 +1,158 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {719b8ecb-ba7b-471a-9b61-9eaca2289175} + GhettoCryptCLI + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + {2b2cf665-f5e6-44db-961f-fc81c88a356d} + + + + + + + + + + \ No newline at end of file diff --git a/GhettoCryptCLI/GhettoCryptCLI.vcxproj.filters b/GhettoCryptCLI/GhettoCryptCLI.vcxproj.filters new file mode 100644 index 0000000..786c2ed --- /dev/null +++ b/GhettoCryptCLI/GhettoCryptCLI.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + + + Headerdateien + + + Headerdateien + + + \ No newline at end of file diff --git a/GhettoCryptCLI/Hazelnupp.cpp b/GhettoCryptCLI/Hazelnupp.cpp new file mode 100644 index 0000000..005a24e --- /dev/null +++ b/GhettoCryptCLI/Hazelnupp.cpp @@ -0,0 +1,1302 @@ +#include "Hazelnupp.h" + +/*** ../Hazelnupp/CmdArgsInterface.cpp ***/ + +#include +#include + +using namespace Hazelnp; + +CmdArgsInterface::CmdArgsInterface() +{ + return; +} + +CmdArgsInterface::CmdArgsInterface(const int argc, const char* const* argv) +{ + Parse(argc, argv); + return; +} + +CmdArgsInterface::~CmdArgsInterface() +{ + for (auto& it : parameters) + delete it.second; + + parameters.clear(); + + return; +} + +void CmdArgsInterface::Parse(const int argc, const char* const* argv) +{ + try + { + // Populate raw arguments + PopulateRawArgs(argc, argv); + + // Expand abbreviations + ExpandAbbreviations(); + + executableName = std::string(rawArgs[0]); + + // Read and parse all parameters + std::size_t i = 1; + while (i < rawArgs.size()) + { + if ((rawArgs[i].length() > 2) && (rawArgs[i].substr(0, 2) == "--")) + { + Parameter* param = nullptr; + i = ParseNextParameter(i, param); + + parameters.insert(std::pair(param->Key(), param)); + } + else + i++; + } + + // Apply constraints such as default values, and required parameters. + // Types have already been enforced. + // Dont apply constraints when we are just printind the param docs + if ((!catchHelp) || (!HasParam("--help"))) + ApplyConstraints(); + } + catch (const HazelnuppConstraintIncompatibleParameters& exc) + { + if (crashOnFail) + { + std::cout << GenerateDocumentation() << std::endl << std::endl; + std::cerr << "Parameter error: " << exc.What() << std::endl; + quick_exit(-1000); + } + else + throw exc; // yeet + } + catch (const HazelnuppConstraintMissingValue& exc) + { + if (crashOnFail) + { + std::cout << GenerateDocumentation() << std::endl << std::endl; + std::cerr << "Parameter error: " << exc.What() << std::endl; + quick_exit(-1001); + } + else + throw exc; // yeet + } + catch (const HazelnuppConstraintTypeMissmatch& exc) + { + if (crashOnFail) + { + std::cout << GenerateDocumentation() << std::endl << std::endl; + std::cerr << "Parameter error: " << exc.What() << std::endl; + quick_exit(-1002); + } + else + throw exc; // yeet + } + catch (const HazelnuppConstraintException& exc) + { + if (crashOnFail) + { + std::cout << GenerateDocumentation() << std::endl << std::endl; + std::cerr << "Parameter error: " << exc.What() << std::endl; + quick_exit(-1003); + } + else + throw exc; // yeet + } + catch (const HazelnuppException& exc) + { + if (crashOnFail) + { + std::cout << GenerateDocumentation() << std::endl << std::endl; + std::cerr << "Parameter error: " << exc.What() << std::endl; + quick_exit(-1004); + } + else + throw exc; // yeet + } + + // Catch --help parameter + if ((catchHelp) && (HasParam("--help"))) + { + std::cout << GenerateDocumentation() << std::endl; + quick_exit(0); + } + + return; +} + +std::size_t CmdArgsInterface::ParseNextParameter(const std::size_t parIndex, Parameter*& out_Par) +{ + std::size_t i = parIndex; + const std::string key = rawArgs[parIndex]; + std::vector values; + + // Get values + for (i++; i < rawArgs.size(); i++) + // If not another parameter + if ((rawArgs[i].length() < 2) || (rawArgs[i].substr(0, 2) != "--")) + values.emplace_back(rawArgs[i]); + else + { + break; + } + + // Fetch constraint info + const ParamConstraint* pcn = GetConstraintForKey(key); + + Value* parsedVal = ParseValue(values, pcn); + if (parsedVal != nullptr) + { + out_Par = new Parameter(key, parsedVal); + + delete parsedVal; + parsedVal = nullptr; + } + else + throw std::runtime_error("Unable to parse parameter!"); + + return i; +} + +void CmdArgsInterface::PopulateRawArgs(const int argc, const char* const* argv) +{ + rawArgs.clear(); + rawArgs.reserve(argc); + + for (int i = 0; i < argc; i++) + rawArgs.emplace_back(std::string(argv[i])); + + return; +} + +void CmdArgsInterface::ExpandAbbreviations() +{ + // Abort if no abbreviations + if (parameterAbreviations.size() == 0) + return; + + for (std::string& arg : rawArgs) + { + // Is arg registered as an abbreviation? + auto abbr = parameterAbreviations.find(arg); + if (abbr != parameterAbreviations.end()) + { + // Yes: replace arg with the long form + arg = abbr->second; + } + } + + return; +} + +bool CmdArgsInterface::HasParam(const std::string& key) const +{ + return parameters.find(key) != parameters.end(); +} + +Value* CmdArgsInterface::ParseValue(const std::vector& values, const ParamConstraint* constraint) +{ + // This is the raw (unconverted) data type the user provided + DATA_TYPE rawInputType; + + // Constraint values + const bool constrainType = (constraint != nullptr) && (constraint->constrainType); + + // Void-type + if (values.size() == 0) + { + rawInputType = DATA_TYPE::VOID; + + // Is a list forced via a constraint? If yes, return an empty list + if ((constrainType) && + (constraint->requiredType == DATA_TYPE::LIST)) + return new ListValue(); + + // Is a string forced via a constraint? If yes, return an empty string + else if ((constrainType) && + (constraint->requiredType == DATA_TYPE::STRING)) + return new StringValue(""); + + // Is an int or float forced via constraint? If yes, throw an exception + else if ((constrainType) && + ((constraint->requiredType == DATA_TYPE::INT) || + (constraint->requiredType == DATA_TYPE::FLOAT))) + throw HazelnuppConstraintTypeMissmatch( + constraint->key, + constraint->requiredType, + rawInputType, + GetDescription(constraint->key) + ); + + // Else, just return the void type + return new VoidValue; + } + + // Force void type by constraint + else if ((constrainType) && + (constraint->requiredType == DATA_TYPE::VOID)) + { + return new VoidValue; + } + + // List-type + else if (values.size() > 1) + { + rawInputType = DATA_TYPE::LIST; + + // Should the type be something other than list? + if ((constrainType) && + (constraint->requiredType != DATA_TYPE::LIST)) + { + throw HazelnuppConstraintTypeMissmatch( + constraint->key, + constraint->requiredType, + rawInputType, + GetDescription(constraint->key) + ); + } + + ListValue* newList = new ListValue(); + for (const std::string& val : values) + { + Value* tmp = ParseValue({ val }); + newList->AddValue(tmp); + delete tmp; + } + return newList; + } + + // Now we're only dealing with a single value + const std::string& val = values[0]; + + // String + if (!Internal::StringTools::IsNumeric(val, true)) + { + rawInputType = DATA_TYPE::STRING; + + // Is the type not supposed to be a string? + // void and list are already sorted out + if ((constrainType) && + (constraint->requiredType != DATA_TYPE::STRING)) + { + // We can only force a list-value from here + if (constraint->requiredType == DATA_TYPE::LIST) + { + ListValue* list = new ListValue(); + Value* tmp = ParseValue({ val }); + list->AddValue(tmp); + delete tmp; + tmp = nullptr; + return list; + } + // Else it is not possible to convert to a numeric + else + throw HazelnuppConstraintTypeMissmatch( + constraint->key, + constraint->requiredType, + rawInputType, + GetDescription(constraint->key) + ); + } + + return new StringValue(val); + } + + // In this case we have a numeric value. + // We should still produce a string if requested + if ((constrainType) && + (constraint->requiredType == DATA_TYPE::STRING)) + return new StringValue(val); + + // Numeric + bool isInt; + long double num; + + if (Internal::StringTools::ParseNumber(val, isInt, num)) + { + rawInputType = isInt ? DATA_TYPE::INT : DATA_TYPE::FLOAT; + + // Is the type constrained? + // (only int and float left) + if (constrainType) + { + // Must it be an integer? + if (constraint->requiredType == DATA_TYPE::INT) + return new IntValue((long long int)num); + // Must it be a floating point? + else if (constraint->requiredType == DATA_TYPE::FLOAT) + return new FloatValue(num); + // Else it must be a List + else + { + ListValue* list = new ListValue(); + Value* tmp = ParseValue({ val }); + list->AddValue(tmp); + delete tmp; + tmp = nullptr; + return list; + } + } + // Type is not constrained + else + { + // Integer + if (isInt) + return new IntValue((long long int)num); + + // Double + return new FloatValue(num); + } + } + + // Failed + return nullptr; +} + +bool CmdArgsInterface::GetCrashOnFail() const +{ + return crashOnFail; +} + +void CmdArgsInterface::SetCatchHelp(bool catchHelp) +{ + this->catchHelp = catchHelp; + return; +} + +bool CmdArgsInterface::GetCatchHelp() const +{ + return catchHelp; +} + +void CmdArgsInterface::SetBriefDescription(const std::string& description) +{ + briefDescription = description; + return; +} + +const std::string& CmdArgsInterface::GetBriefDescription() +{ + return briefDescription; +} + +void Hazelnp::CmdArgsInterface::RegisterDescription(const std::string& parameter, const std::string& description) +{ + parameterDescriptions[parameter] = description; + return; +} + +const std::string& Hazelnp::CmdArgsInterface::GetDescription(const std::string& parameter) const +{ + // Do we already have a description for this parameter? + if (!HasDescription(parameter)) + // No? Then return "" + return Placeholders::g_emptyString; + + // We do? Then return it + return parameterDescriptions.find(parameter)->second; +} + +bool CmdArgsInterface::HasDescription(const std::string& parameter) const +{ + return parameterDescriptions.find(parameter) != parameterDescriptions.end(); +} + +void CmdArgsInterface::ClearDescription(const std::string& parameter) +{ + // This will just do nothing if the entry does not exist + parameterDescriptions.erase(parameter); + return; +} + +void Hazelnp::CmdArgsInterface::ClearDescriptions() +{ + parameterDescriptions.clear(); + return; +} + +std::string CmdArgsInterface::GenerateDocumentation() const +{ + std::stringstream ss; + + // Add brief, if available + if (briefDescription.length() > 0) + ss << briefDescription << std::endl; + + // Collect parameter information + struct ParamDocEntry + { + std::string abbreviation; + std::string description; + std::string type; + bool required = false; + bool typeIsForced = false; + std::string defaultVal; + std::string incompatibilities; + }; + std::unordered_map paramInfos; + + // Collect descriptions + for (const auto& it : parameterDescriptions) + { + // Do we already have that param in the paramInfo set? + if (paramInfos.find(it.first) == paramInfos.end()) + // No? Create it. + paramInfos[it.first] = ParamDocEntry(); + + paramInfos[it.first].description = it.second; + } + + // Collect abbreviations + // first value is abbreviation, second is long form + for (const auto& it : parameterAbreviations) + { + // Do we already have that param in the paramInfo set? + if (paramInfos.find(it.second) == paramInfos.end()) + // No? Create it. + paramInfos[it.second] = ParamDocEntry(); + + paramInfos[it.second].abbreviation = it.first; + } + + // Collect constraints + for (const auto& it : parameterConstraints) + { + // Do we already have that param in the paramInfo set? + if (paramInfos.find(it.first) == paramInfos.end()) + // No? Create it. + paramInfos[it.first] = ParamDocEntry(); + + ParamDocEntry& cached = paramInfos[it.first]; + cached.required = it.second.required; + cached.typeIsForced = it.second.constrainType; + cached.type = DataTypeToString(it.second.requiredType); + + // Build default-value string + std::stringstream vec2str_ss; + for (const std::string& s : it.second.defaultValue) + { + vec2str_ss << '\'' << s << '\''; + + // Add a space if we are not at the last entry + if ((void*)&s != (void*)&it.second.defaultValue.back()) + vec2str_ss << " "; + } + cached.defaultVal = vec2str_ss.str(); + + + // Build incompatibilities string + vec2str_ss.str(""); + for (const std::string& s : it.second.incompatibleParameters) + { + vec2str_ss << s; + + // Add a comma-space if we are not at the last entry + if ((void*)&s != (void*)&it.second.incompatibleParameters.back()) + vec2str_ss << ", "; + } + cached.incompatibilities = vec2str_ss.str(); + } + + // Now generate the documentation body + if (paramInfos.size() > 0) + { + ss << std::endl + << "==== AVAILABLE PARAMETERS ====" + << std::endl << std::endl; + + std::size_t counter = 0; + for (const auto& it : paramInfos) + { + const ParamDocEntry& pde = it.second; + + // Put name + ss << it.first << " "; + + // Put abbreviation + if (pde.abbreviation.length() > 0) + ss << pde.abbreviation << " "; + + // Put type + if (pde.typeIsForced) + ss << pde.type << " "; + + // Put default value + if (pde.defaultVal.length() > 0) + ss << "default=[" << pde.defaultVal << "] "; + + // Put incompatibilities + if (pde.incompatibilities.length() > 0) + ss << "incompatibilities=[" << pde.incompatibilities << "] "; + + // Put required tag, but only if no default value + if ((pde.required) && (pde.defaultVal.length() == 0)) + ss << "[[REQUIRED]] "; + + // Put brief description + if (pde.description.length() > 0) + ss << pde.description; + + // Omit linebreaks when we're on the last element + if (counter < paramInfos.size() - 1) + ss << std::endl << std::endl; + + counter++; + } + } + + return ss.str(); +} + +void CmdArgsInterface::ApplyConstraints() +{ + // Enforce required parameters / default values + for (const auto& pc : parameterConstraints) + // Parameter in question is not supplied + if (!HasParam(pc.second.key)) + { + // Do we have a default value? + if (pc.second.defaultValue.size() > 0) + { + // Then create it now, by its default value + Value* tmp = ParseValue(pc.second.defaultValue, &pc.second); + parameters.insert(std::pair( + pc.second.key, + new Parameter(pc.second.key, tmp) + )); + + delete tmp; + tmp = nullptr; + } + // So we do not have a default value... + else + { + // Is it important to have the missing parameter? + if (pc.second.required) + // Throw an error message then + throw HazelnuppConstraintMissingValue( + pc.second.key, + GetDescription(pc.second.key) + ); + } + } + // The parameter in question IS supplied + else + { + // Enforce parameter incompatibility + + // Is ANY parameter present listed as incompatible with our current one? + for (const std::string& incompatibility : pc.second.incompatibleParameters) + for (const std::pair& otherParam : parameters) + { + if (otherParam.first == incompatibility) + throw HazelnuppConstraintIncompatibleParameters(pc.second.key, incompatibility); + } + } + + return; +} + +ParamConstraint CmdArgsInterface::GetConstraint(const std::string& parameter) const +{ + return parameterConstraints.find(parameter)->second; +} + +void CmdArgsInterface::ClearConstraint(const std::string& parameter) +{ + parameterConstraints.erase(parameter); + return; +} + +const std::string& CmdArgsInterface::GetExecutableName() const +{ + return executableName; +} + +const Value& CmdArgsInterface::operator[](const std::string& key) const +{ + // Throw exception if param is unknown + if (!HasParam(key)) + throw HazelnuppInvalidKeyException(); + + return *parameters.find(key)->second->GetValue(); +} + +void CmdArgsInterface::RegisterAbbreviation(const std::string& abbrev, const std::string& target) +{ + parameterAbreviations.insert(std::pair(abbrev, target)); + return; +} + +const std::string& CmdArgsInterface::GetAbbreviation(const std::string& abbrev) const +{ + if (!HasAbbreviation(abbrev)) + return Placeholders::g_emptyString; + + return parameterAbreviations.find(abbrev)->second; +} + +bool CmdArgsInterface::HasAbbreviation(const std::string& abbrev) const +{ + return parameterAbreviations.find(abbrev) != parameterAbreviations.end(); +} + +void CmdArgsInterface::ClearAbbreviation(const std::string& abbrevation) +{ + parameterAbreviations.erase(abbrevation); + return; +} + +void CmdArgsInterface::ClearAbbreviations() +{ + parameterAbreviations.clear(); + return; +} + +void CmdArgsInterface::RegisterConstraint(const std::string& key, const ParamConstraint& constraint) +{ + // Magic syntax, wooo + (parameterConstraints[key] = constraint).key = key; + return; +} + +void CmdArgsInterface::ClearConstraints() +{ + parameterConstraints.clear(); + return; +} + +void CmdArgsInterface::SetCrashOnFail(bool crashOnFail) +{ + this->crashOnFail = crashOnFail; + return; +} + +const ParamConstraint* CmdArgsInterface::GetConstraintForKey(const std::string& key) const +{ + const auto constraint = parameterConstraints.find(key); + + if (constraint == parameterConstraints.end()) + return nullptr; + + return &constraint->second; +} + + +/*** ../Hazelnupp/FloatValue.cpp ***/ + +#include + +using namespace Hazelnp; + +FloatValue::FloatValue(const long double& value) + : + Value(DATA_TYPE::FLOAT), + value{ value } +{ + return; +} + +Value* FloatValue::Deepcopy() const +{ + return new FloatValue(value); +} + +std::string FloatValue::GetAsOsString() const +{ + std::stringstream ss; + ss << "FloatValue: " << value; + return ss.str(); +} + +const long double& FloatValue::GetValue() const +{ + return value; +} + +FloatValue::operator long double() const +{ + return value; +} + +FloatValue::operator double() const +{ + return (double)value; +} + + + +long long int FloatValue::GetInt64() const +{ + return (long long int)value; +} + +int FloatValue::GetInt32() const +{ + return (int)value; +} + +long double FloatValue::GetFloat64() const +{ + return value; +} + +double FloatValue::GetFloat32() const +{ + return (double)value; +} + +std::string FloatValue::GetString() const +{ + std::stringstream ss; + ss << value; + + return ss.str(); +} + +const std::vector& FloatValue::GetList() const +{ + throw HazelnuppValueNotConvertibleException(); +} + + +/*** ../Hazelnupp/IntValue.cpp ***/ + +#include + +using namespace Hazelnp; + +IntValue::IntValue(const long long int& value) + : + Value(DATA_TYPE::INT), + value{ value } +{ + return; +} + +Value* IntValue::Deepcopy() const +{ + return new IntValue(value); +} + +std::string IntValue::GetAsOsString() const +{ + std::stringstream ss; + ss << "IntValue: " << value; + return ss.str(); +} + +const long long int& IntValue::GetValue() const +{ + return value; +} + +IntValue::operator long long int() const +{ + return value; +} + +IntValue::operator int() const +{ + return (int)value; +} + + + +long long int IntValue::GetInt64() const +{ + return value; +} + +int IntValue::GetInt32() const +{ + return (int)value; +} + +long double IntValue::GetFloat64() const +{ + return (long double)value; +} + +double IntValue::GetFloat32() const +{ + return (double)value; +} + +std::string IntValue::GetString() const +{ + std::stringstream ss; + ss << value; + + return ss.str(); +} + +const std::vector& IntValue::GetList() const +{ + throw HazelnuppValueNotConvertibleException(); +} + + +/*** ../Hazelnupp/ListValue.cpp ***/ + +#include + +using namespace Hazelnp; + +ListValue::ListValue() : + Value(DATA_TYPE::LIST) +{ + return; +} + +ListValue::~ListValue() +{ + for (Value* val : value) + delete val; + + value.clear(); + + return; +} + +Value* ListValue::Deepcopy() const +{ + ListValue* newList = new ListValue(); + + for (const Value* val : value) + newList->AddValue(val); + + return newList; +} + +void ListValue::AddValue(const Value* value) +{ + this->value.emplace_back(value->Deepcopy()); + return; +} + +const std::vector& ListValue::GetValue() const +{ + return value; +} + +std::string ListValue::GetAsOsString() const +{ + std::stringstream ss; + + ss << "ListValue: ["; + + for (const Value* val : value) + { + ss << *val; + if (val != value.back()) + ss << ", "; + } + + ss << "]"; + + return ss.str(); +} + +ListValue::operator std::vector() const +{ + return value; +} + + + +long long int ListValue::GetInt64() const +{ + throw HazelnuppValueNotConvertibleException(); +} + +int ListValue::GetInt32() const +{ + throw HazelnuppValueNotConvertibleException(); +} + +long double ListValue::GetFloat64() const +{ + throw HazelnuppValueNotConvertibleException(); +} + +double ListValue::GetFloat32() const +{ + throw HazelnuppValueNotConvertibleException(); +} + +std::string ListValue::GetString() const +{ + throw HazelnuppValueNotConvertibleException(); +} + +const std::vector& ListValue::GetList() const +{ + return value; +} + + +/*** ../Hazelnupp/Parameter.cpp ***/ + + +using namespace Hazelnp; + +Parameter::Parameter(const std::string& key, const ::Value* value) + : + key{ key } +{ + this->value = value->Deepcopy(); + return; +} + +Parameter::~Parameter() +{ + delete value; + value = nullptr; + + return; +} + +const std::string& Parameter::Key() const +{ + return key; +} + +const ::Value* Parameter::GetValue() const +{ + return value; +} + + +/*** ../Hazelnupp/StringTools.cpp ***/ + + +using namespace Hazelnp; + +bool Internal::StringTools::Contains(const std::string& str, const char c) +{ + for (const char& i : str) + if (i == c) + return true; + + return false; +} + +std::string Internal::StringTools::Replace(const std::string& str, const char find, const std::string& subst) +{ + std::stringstream ss; + + for (std::size_t i = 0; i < str.length(); i++) + { + if (str[i] != find) ss << str[i]; + else ss << subst; + } + + return ss.str(); +} + +std::string Internal::StringTools::Replace(const std::string& str, const std::string& find, const std::string& subst) +{ + if (find.length() == 0) return str; + + std::stringstream ss; + + std::size_t posFound = 0; + std::size_t lastFound = 0; + + while (posFound != std::string::npos) + { + lastFound = posFound; + posFound = str.find(find, posFound); + + if (posFound != std::string::npos) + { + ss << str.substr(lastFound, posFound - lastFound) << subst; + posFound += find.length(); + } + else + { + ss << str.substr(lastFound, (str.length()) - lastFound); + } + } + + return ss.str(); +} + + +bool Internal::StringTools::IsNumeric(const std::string& str, const bool allowDecimalPoint) +{ + if (str.length() == 0) return false; + + bool alreadyParsedDecimalPoint = false; + std::size_t digitCount = 0; + + for (std::size_t i = 0; i < str.length(); i++) + { + if (!( + ((str[i] >= '0') && (str[i] <= '9')) || + ((str[i] == '-') && (i == 0)) || + ((str[i] == '.') && (allowDecimalPoint) && (!alreadyParsedDecimalPoint) && (digitCount > 0)) + )) return false; + + + // Here we just have to check for the character. Not for any other conditions. + // Why? Because if these conditions failed, the function would have already returned false. + if (((str[i] >= '0') && (str[i] <= '9'))) digitCount++; + if (str[i] == '.') alreadyParsedDecimalPoint = true; + } + + // Even if we did not find any invalid chars, we should still return false, if we found no digits at all. + return digitCount > 0; +} + +bool Internal::StringTools::ParseNumber(const std::string& str, bool& out_isInt, long double& out_number) +{ + bool isDecimal = false; + + if (str.length() == 0) return false; + if (Contains(str, '.')) isDecimal = true; + + if (isDecimal) + { + try + { + out_number = std::stold(str); + out_isInt = false; + } + catch (std::invalid_argument&) + { + return false; + } + catch (std::out_of_range&) + { + return false; + } + } + else + { + try + { + out_number = (long double)std::stoll(str); + out_isInt = true; + } + catch (std::invalid_argument&) + { + return false; + } + catch (std::out_of_range&) + { + return false; + } + } + + return true; +} + +std::vector Internal::StringTools::SplitString(const std::string& str, const char delimiter) +{ + if (str.length() == 0) return std::vector(); + + return SplitString(str, delimiter); +} + +std::vector Internal::StringTools::SplitString(const std::string& str, const std::string& delimiter) +{ + if (str.length() == 0) return std::vector(); + + std::vector parts; + + if (delimiter.length() == 0) // If the delimiter is "" (empty), just split between every single char. Not useful, but logical + { + for (std::size_t i = 0; i < str.length(); i++) + { + parts.push_back(std::string({ str[i] })); + } + return parts; + } + + std::size_t posFound = 0; + std::size_t lastFound = 0; + + while (posFound != std::string::npos) + { + lastFound = posFound; + posFound = str.find(delimiter, posFound); + + std::string found; + + if (posFound != std::string::npos) + { + found = str.substr(lastFound, posFound - lastFound); + posFound += delimiter.length(); + } + else + { + found = str.substr(lastFound, str.length() - lastFound); + } + + parts.push_back(found); + } + + return parts; +} + +std::string Internal::StringTools::ToLower(const std::string& str) +{ + std::stringstream ss; + for (std::size_t i = 0; i < str.length(); i++) + { + if ((str[i] >= 'A') && (str[i] <= 'Z')) ss << (char)(((int)str[i]) + 32); + else if (str[i] == -60) ss << (char)-28; // AE => ae + else if (str[i] == -42) ss << (char)-10; // OE => oe + else if (str[i] == -36) ss << (char)-4; // UE => ue + else ss << str[i]; + } + + return ss.str(); +} + + +/*** ../Hazelnupp/StringValue.cpp ***/ + +#include + +using namespace Hazelnp; + +StringValue::StringValue(const std::string& value) + : + Value(DATA_TYPE::STRING), + value{ value } +{ + return; +} + +Value* StringValue::Deepcopy() const +{ + return new StringValue(value); +} + +std::string StringValue::GetAsOsString() const +{ + std::stringstream ss; + ss << "StringValue: " << value; + return ss.str(); +} + +const std::string& StringValue::GetValue() const +{ + return value; +} + +StringValue::operator std::string() const +{ + return value; +} + + + +long long int StringValue::GetInt64() const +{ + throw HazelnuppValueNotConvertibleException(); +} + +int StringValue::GetInt32() const +{ + throw HazelnuppValueNotConvertibleException(); +} + +long double StringValue::GetFloat64() const +{ + throw HazelnuppValueNotConvertibleException(); +} + +double StringValue::GetFloat32() const +{ + throw HazelnuppValueNotConvertibleException(); +} + +std::string StringValue::GetString() const +{ + return value; +} + +const std::vector& StringValue::GetList() const +{ + throw HazelnuppValueNotConvertibleException(); +} + + +/*** ../Hazelnupp/Value.cpp ***/ + + +using namespace Hazelnp; + +Value::Value(DATA_TYPE type) + : + type{ type } +{ + return; +} + +DATA_TYPE Value::GetDataType() const +{ + return type; +} + + +/*** ../Hazelnupp/VoidValue.cpp ***/ + + +using namespace Hazelnp; + +VoidValue::VoidValue() + : + Value(DATA_TYPE::VOID) +{ + return; +} + +Value* VoidValue::Deepcopy() const +{ + return new VoidValue(); +} + +std::string VoidValue::GetAsOsString() const +{ + return "VoidValue"; +} + + + +long long int VoidValue::GetInt64() const +{ + throw HazelnuppValueNotConvertibleException(); +} + +int VoidValue::GetInt32() const +{ + throw HazelnuppValueNotConvertibleException(); +} + +long double VoidValue::GetFloat64() const +{ + throw HazelnuppValueNotConvertibleException(); +} + +double VoidValue::GetFloat32() const +{ + throw HazelnuppValueNotConvertibleException(); +} + +std::string VoidValue::GetString() const +{ + return ""; +} + +const std::vector& VoidValue::GetList() const +{ + static const std::vector empty; + return empty; +} diff --git a/GhettoCryptCLI/Hazelnupp.h b/GhettoCryptCLI/Hazelnupp.h new file mode 100644 index 0000000..81cf344 --- /dev/null +++ b/GhettoCryptCLI/Hazelnupp.h @@ -0,0 +1,813 @@ +#pragma once + +/*** ../Hazelnupp/StringTools.h ***/ + +#include +#include +#include +#include + +namespace Hazelnp +{ + namespace Internal + { + /** Internal helper class. Feel free to use it tho. + */ + class StringTools + { + public: + //! Will return wether or not a given char is in a string + static bool Contains(const std::string& str, const char c); + + //! Will replace a part of a string with another string + static std::string Replace(const std::string& str, const char find, const std::string& subst); + + //! Will replace a part of a string with another string + static std::string Replace(const std::string& str, const std::string& find, const std::string& subst); + + //! Will return true if the given string consists only of digits (including signage) + static bool IsNumeric(const std::string& str, const bool allowDecimalPoint = false); + + //! Will convert the number in str to a number. + //! Returns wether or not the operation was successful. + //! Also returns wether the number is an integer, or floating point. If int, cast out_number to int. + static bool ParseNumber(const std::string& str, bool& out_isInt, long double& out_number); + + //! Will split a string by a delimiter char. The delimiter will be excluded! + static std::vector SplitString(const std::string& str, const char delimiter); + + //! Will split a string by a delimiter string. The delimiter will be excluded! + static std::vector SplitString(const std::string& str, const std::string& delimiter); + + //! Will make a string all lower-case + static std::string ToLower(const std::string& str); + }; + } +} + + +/*** ../Hazelnupp/Placeholders.h ***/ + +#include + +namespace Hazelnp +{ + namespace Placeholders + { + //! The only purpose of this is to provide the ability to return an empty string as an error for std::string& methods. + static const std::string g_emptyString; + } +} + +/*** ../Hazelnupp/DataType.h ***/ + +#include + +namespace Hazelnp +{ + /** The different data types a paramater can be + */ + enum class DATA_TYPE + { + VOID, + INT, + FLOAT, + STRING, + LIST + }; + + static inline std::string DataTypeToString(DATA_TYPE type) + { + switch (type) + { + case DATA_TYPE::VOID: + return "VOID"; + + case DATA_TYPE::INT: + return "INT"; + + case DATA_TYPE::FLOAT: + return "FLOAT"; + + case DATA_TYPE::STRING: + return "STRING"; + + case DATA_TYPE::LIST: + return "LIST"; + } + + return ""; + } +} + +/*** ../Hazelnupp/HazelnuppException.h ***/ + +#include +#include +#include + +namespace Hazelnp +{ + /** Generic hazelnupp exception + */ + class HazelnuppException : public std::exception + { + public: + HazelnuppException() {}; + HazelnuppException(const std::string& msg) : message{ msg } {}; + + //! Will return an error message + const std::string& What() const + { + return message; + } + + protected: + std::string message; + }; + + /** Gets thrown when an non-existent key gets dereferenced + */ + class HazelnuppInvalidKeyException : public HazelnuppException + { + public: + HazelnuppInvalidKeyException() : HazelnuppException() {}; + HazelnuppInvalidKeyException(const std::string& msg) : HazelnuppException(msg) {}; + }; + + /** Gets thrown when an attempt is made to retrieve the wrong data type from a value, when the value not convertible + */ + class HazelnuppValueNotConvertibleException : public HazelnuppException + { + public: + HazelnuppValueNotConvertibleException() : HazelnuppException() {}; + HazelnuppValueNotConvertibleException(const std::string& msg) : HazelnuppException(msg) {}; + }; + + /** Gets thrown something bad happens because of parameter constraints + */ + class HazelnuppConstraintException : public HazelnuppException + { + public: + HazelnuppConstraintException() : HazelnuppException() {}; + HazelnuppConstraintException(const std::string& msg) : HazelnuppException(msg) {}; + }; + + /** Gets thrown when a parameter is of a type that does not match the required type, and is not convertible to it + */ + class HazelnuppConstraintTypeMissmatch : public HazelnuppConstraintException + { + public: + HazelnuppConstraintTypeMissmatch() : HazelnuppConstraintException() {}; + HazelnuppConstraintTypeMissmatch(const std::string& msg) : HazelnuppConstraintException(msg) {}; + + HazelnuppConstraintTypeMissmatch(const std::string& key, const DATA_TYPE requiredType, const DATA_TYPE actualType, const std::string& paramDescription = "") + { + // Generate descriptive error message + std::stringstream ss; + ss << "Cannot convert parameter " << key << " to type " << DataTypeToString(requiredType) + << ". You supplied type: " << DataTypeToString(actualType) << "."; + + // Add the parameter description, if provided + if (paramDescription.length() > 0) + ss << std::endl << key << " => " << paramDescription; + + message = ss.str(); + return; + }; + }; + + /** Gets thrown when a parameter constrained to be required is not provided, and has no default value set + */ + class HazelnuppConstraintMissingValue : public HazelnuppConstraintException + { + public: + HazelnuppConstraintMissingValue() : HazelnuppConstraintException() {}; + HazelnuppConstraintMissingValue(const std::string& key, const std::string& paramDescription = "") + { + // Generate descriptive error message + std::stringstream ss; + ss << "Missing required parameter " << key << "."; + + // Add the parameter description, if provided + if (paramDescription.length() > 0) + ss << std::endl << key << " => " << paramDescription; + + message = ss.str(); + return; + }; + }; + + /** Gets thrown when a parameter constrained to be incompatible with other parameters gets supplied alongside at least one of those incompatible ones + */ + class HazelnuppConstraintIncompatibleParameters : public HazelnuppConstraintException + { + public: + HazelnuppConstraintIncompatibleParameters() : HazelnuppConstraintException() {}; + HazelnuppConstraintIncompatibleParameters(const std::string& key1, const std::string& key2) + { + // Generate descriptive error message + std::stringstream ss; + ss << "Parameter \"" << key1 << "\" is NOT compatible with parameter \"" << key2 << "\"!"; + + message = ss.str(); + return; + }; + }; +} + +/*** ../Hazelnupp/ParamConstraint.h ***/ + +#include +#include + +namespace Hazelnp +{ + struct ParamConstraint + { + public: + //! Empty constructor + ParamConstraint() = default; + + //! Constructs a require constraint. + //! Think of the default value like of a list ofparameters. Like {"--width", "800"} + static ParamConstraint Require(const std::initializer_list& defaultValue = {}, bool required = true) + { + ParamConstraint pc; + pc.defaultValue = defaultValue; + pc.required = required; + + return pc; + } + + //! Daisychain-method. Will add a the "required-argument" aspect. + //! Think of the default value like of a list ofparameters. Like {"--width", "800"} + ParamConstraint AddRequire(const std::initializer_list& defaultValue = {}, bool required = true) + { + ParamConstraint pc = *this; + pc.defaultValue = defaultValue; + pc.required = required; + + return pc; + } + + //! Constructs a type-safety constraint + static ParamConstraint TypeSafety(DATA_TYPE requiredType, bool constrainType = true) + { + ParamConstraint pc; + pc.constrainType = constrainType; + pc.requiredType = requiredType; + + return pc; + } + + //! Daisychain-method. Will add a the "type-safety" aspect. + //! Constructs a type-safety constraint + ParamConstraint AddTypeSafety(DATA_TYPE requiredType, bool constrainType = true) + { + ParamConstraint pc = *this; + pc.constrainType = constrainType; + pc.requiredType = requiredType; + + return pc; + } + + //! Constructs an incompatibility constraint. + //! This means, that the following parameters are NOT compatible with this one and will throw an error if passed together + static ParamConstraint Incompatibility(const std::initializer_list& incompatibleParameters) + { + ParamConstraint pc; + pc.incompatibleParameters = incompatibleParameters; + + return pc; + } + + //! Constructs an incompatibility constraint. + //! This means, that the following parameters are NOT compatible with this one and will throw an error if passed together. + //! Syntactical-sugar proxy method that will convert the lonely string to an initializer list for you :3 + static ParamConstraint Incompatibility(const std::string& incompatibleParameters) + { + ParamConstraint pc; + pc.incompatibleParameters = { incompatibleParameters }; + + return pc; + } + + //! Daisychain-method. Will add a the "incompatiblity" aspect. + //! This means, that the following parameters are NOT compatible with this one and will throw an error if passed together. + //! Syntactical-sugar proxy method that will convert the lonely string to an initializer list for you :3 + ParamConstraint AddIncompatibilities(const std::string& incompatibleParameters) + { + ParamConstraint pc = *this; + pc.incompatibleParameters = { incompatibleParameters }; + + return pc; + } + + //! Daisychain-method. Will add a the "incompatiblity" aspect. + //! This means, that the following parameters are NOT compatible with this one and will throw an error if passed together. + ParamConstraint AddIncompatibilities(const std::initializer_list& incompatibleParameters) + { + ParamConstraint pc = *this; + pc.incompatibleParameters = incompatibleParameters; + + return pc; + } + + //! Whole constructor + ParamConstraint(bool constrainType, DATA_TYPE requiredType, const std::initializer_list& defaultValue, bool required, const std::initializer_list& incompatibleParameters) + : + constrainType{ constrainType }, + requiredType{ requiredType }, + defaultValue{ defaultValue }, + required{ required }, + incompatibleParameters{ incompatibleParameters } + { + return; + } + + //! Should this parameter be forced to be of a certain type? + //! Remember to set `constrainTo` to the wanted type + bool constrainType = false; + + //! Constrain the parameter to this value. Requires `constrainType` to be set to true. + DATA_TYPE requiredType = DATA_TYPE::VOID; + + //! The default value for this parameter. + //! Gets applied if this parameter was not given. + //! Think of this like a list of parameters. Like {"--width", "800"} + std::vector defaultValue; + + //! If set to true, and no default value set, + //! an error will be produced if this parameter is not supplied by the user. + bool required = false; + + //! Parameters that are incompatible with this parameter + std::vector incompatibleParameters; + + private: + //! The parameter this constraint is for. + //! This value is automatically set by Hazelnupp. + std::string key; + + friend class CmdArgsInterface; + }; +} + +/*** ../Hazelnupp/Version.h ***/ + +#define HAZELNUPP_VERSION (1.121) + +/*** ../Hazelnupp/Value.h ***/ + +#include +#include + +namespace Hazelnp +{ + /** Abstract class for values + */ + class Value + { + public: + virtual ~Value() {}; + + //! Will return a deeopopy of this object + virtual Value* Deepcopy() const = 0; + + //! Will return a string suitable for an std::ostream + virtual std::string GetAsOsString() const = 0; + + //! Will return the data type of this value + DATA_TYPE GetDataType() const; + + friend std::ostream& operator<< (std::ostream& os, const Value& v) + { + return os << v.GetAsOsString(); + } + + //! Will attempt to return the integer data (long long) + virtual long long int GetInt64() const = 0; + //! Will attempt to return the integer data (int) + virtual int GetInt32() const = 0; + + //! Will attempt to return the floating-point data (long double) + virtual long double GetFloat64() const = 0; + //! Will attempt to return the floating-point data (double) + virtual double GetFloat32() const = 0; + + //! Will attempt to return the string-data + virtual std::string GetString() const = 0; + + //! Will attempt to return the list-data + virtual const std::vector& GetList() const = 0; + + protected: + Value(DATA_TYPE type); + + DATA_TYPE type; + }; +} + +/*** ../Hazelnupp/ListValue.h ***/ + +#include + +namespace Hazelnp +{ + /** Specializations for list values (uses std::vector) + */ + class ListValue : public Value + { + public: + ListValue(); + ~ListValue() override; + + //! Will return a deeopopy of this object + Value* Deepcopy() const override; + + //! Will return a string suitable for an std::ostream; + std::string GetAsOsString() const override; + + //! Will add this value to the list + void AddValue(const Value* value); + + //! Will return the raw value + const std::vector& GetValue() const; + + operator std::vector() const; + + //! Throws HazelnuppValueNotConvertibleException + long long int GetInt64() const override; + //! Throws HazelnuppValueNotConvertibleException + int GetInt32() const override; + + //! Throws HazelnuppValueNotConvertibleException + long double GetFloat64() const override; + //! Throws HazelnuppValueNotConvertibleException + double GetFloat32() const override; + + //! Throws HazelnuppValueNotConvertibleException + std::string GetString() const override; + + //! Will return this values list + const std::vector& GetList() const override; + + private: + std::vector value; + }; +} + +/*** ../Hazelnupp/FloatValue.h ***/ + +#include + +namespace Hazelnp +{ + /** Specializations for floating point values (uses long double) + */ + class FloatValue : public Value + { + public: + FloatValue(const long double& value); + ~FloatValue() override {}; + + //! Will return a deeopopy of this object + Value* Deepcopy() const override; + + //! Will return a string suitable for an std::ostream; + std::string GetAsOsString() const override; + + //! Will return the raw value + const long double& GetValue() const; + + operator long double() const; + operator double() const; + + //! Will return the data as a long long int + long long int GetInt64() const override; + //! Will return the data as an int + int GetInt32() const override; + + //! Will return the data as a long double + long double GetFloat64() const override; + //! Will return the data as a double + double GetFloat32() const override; + + //! Will return the data as a string + std::string GetString() const override; + + //! Throws HazelnuppValueNotConvertibleException + const std::vector& GetList() const override; + + private: + long double value; + }; +} + +/*** ../Hazelnupp/IntValue.h ***/ + + +namespace Hazelnp +{ + /** Specializations for integer values (uses long long int) + */ + class IntValue : public Value + { + public: + IntValue(const long long int& value); + ~IntValue() override {}; + + //! Will return a deeopopy of this object + Value* Deepcopy() const override; + + //! Will return a string suitable for an std::ostream; + std::string GetAsOsString() const override; + + //! Will return the raw value + const long long int& GetValue() const; + + operator long long int() const; + operator int() const; + + + //! Will return the data as a long long int + long long int GetInt64() const override; + //! Will return the data as an int + int GetInt32() const override; + + //! Will return the data as a long double + long double GetFloat64() const override; + //! Will return the data as a double + double GetFloat32() const override; + + //! Will return the data as a string + std::string GetString() const override; + + //! Throws HazelnuppValueNotConvertibleException + const std::vector& GetList() const override; + + private: + long long int value; + }; +} + +/*** ../Hazelnupp/VoidValue.h ***/ + + +namespace Hazelnp +{ + /** Specializations for void values. These house no value whatsoever, but only communicate information by merely existing. + */ + class VoidValue : public Value + { + public: + VoidValue(); + ~VoidValue() override {}; + + //! Will return a deeopopy of this object + Value* Deepcopy() const override; + + //! Will return a string suitable for an std::ostream; + std::string GetAsOsString() const override; + + //! Throws HazelnuppValueNotConvertibleException + long long int GetInt64() const override; + //! Throws HazelnuppValueNotConvertibleException + int GetInt32() const override; + + //! Throws HazelnuppValueNotConvertibleException + long double GetFloat64() const override; + //! Throws HazelnuppValueNotConvertibleException + double GetFloat32() const override; + + //! Returns an empty string + std::string GetString() const override; + + //! Returns an empty list + const std::vector& GetList() const; + }; +} + +/*** ../Hazelnupp/StringValue.h ***/ + +#include + +namespace Hazelnp +{ + /** Specializations for string values (uses std::string) + */ + class StringValue : public Value + { + public: + StringValue(const std::string& value); + ~StringValue() override {}; + + //! Will return a deeopopy of this object + Value* Deepcopy() const override; + + //! Will return a string suitable for an std::ostream; + std::string GetAsOsString() const override; + + //! Will return the raw value + const std::string& GetValue() const; + + operator std::string() const; + + //! Throws HazelnuppValueNotConvertibleException + long long int GetInt64() const override; + //! Throws HazelnuppValueNotConvertibleException + int GetInt32() const override; + + //! Throws HazelnuppValueNotConvertibleException + long double GetFloat64() const override; + //! Throws HazelnuppValueNotConvertibleException + double GetFloat32() const override; + + //! Will return this value as a string + std::string GetString() const override; + + //! Throws HazelnuppValueNotConvertibleException + const std::vector& GetList() const override; + + private: + std::string value; + }; +} + +/*** ../Hazelnupp/Parameter.h ***/ + +#include +#include + +namespace Hazelnp +{ + class Parameter + { + public: + explicit Parameter(const std::string& key, const Value* value); + ~Parameter(); + + //! Will return the key of this parameter + const std::string& Key() const; + + //! Will return the value of this parameter + const Value* GetValue() const; + + friend std::ostream& operator<< (std::ostream& os, const Parameter& p) + { + return os << "{ Key: \"" << p.key << "\" -> " << *p.value << " }"; + } + + private: + std::string key; + Hazelnp::Value* value; + }; +} + +/*** ../Hazelnupp/CmdArgsInterface.h ***/ + +#include +#include + + +namespace Hazelnp +{ + /** The main class to interface with + */ + class CmdArgsInterface + { + public: + CmdArgsInterface(); + CmdArgsInterface(const int argc, const char* const* argv); + + ~CmdArgsInterface(); + + //! Will parse command line arguments + void Parse(const int argc, const char* const* argv); + + //! Will return argv[0], the name of the executable. + const std::string& GetExecutableName() const; + + //! Will return the value given a key + const Value& operator[](const std::string& key) const; + + //! Will check wether a parameter exists given a key, or not + bool HasParam(const std::string& key) const; + + // Abbreviations + //! Will register an abbreviation (like -f for --force) + void RegisterAbbreviation(const std::string& abbrev, const std::string& target); + + //! Will return the long form of an abbreviation (like --force for -f) + //! Returns "" if no match is found + const std::string& GetAbbreviation(const std::string& abbrev) const; + + //! Will check wether or not an abbreviation is registered + bool HasAbbreviation(const std::string& abbrev) const; + + //! Will delete the abbreviation for a given parameter. + //! IMPORTANT: This parameter is the abbreviation! Not the long form! + void ClearAbbreviation(const std::string& abbrevation); + + //! Will delete all abbreviations + void ClearAbbreviations(); + + //! Will register a constraint for a parameter. + //! IMPORTANT: Any parameter can only have ONE constraint. Applying a new one will overwrite the old one! + //! Construct the ParamConstraint struct yourself to combine Require, TypeSafety and Incompatibilities! You can also use the ParamConstraint constructor! + void RegisterConstraint(const std::string& key, const ParamConstraint& constraint); + + //! Will return the constraint information for a specific parameter + ParamConstraint GetConstraint(const std::string& parameter) const; + + //! Will the constraint of a specific parameter + void ClearConstraint(const std::string& parameter); + + //! Will delete all constraints + void ClearConstraints(); + + //! Sets whether to crash the application, and print to stderr, when an exception is + //! raised whilst parsing, or not. + void SetCrashOnFail(bool crashOnFail); + + //! Gets whether the application crashes on an exception whilst parsing, and prints to stderr. + bool GetCrashOnFail() const; + + //! Sets whether the CmdArgsInterface should automatically catch the --help parameter, print the parameter documentation to stdout, and exit or not. + void SetCatchHelp(bool catchHelp); + + //! Retruns whether the CmdArgsInterface should automatically catch the --help parameter, print the parameter documentation to stdout, and exit or not. + bool GetCatchHelp() const; + + //! Sets a brief description of the application to be automatically added to the documentation. + void SetBriefDescription(const std::string& description); + + //! Returns the brief description of the application to be automatically added to the documentation. + const std::string& GetBriefDescription(); + + //! Willl register a short description for a parameter. + //! Will overwrite existing descriptions for that parameter. + void RegisterDescription(const std::string& parameter, const std::string& description); + + //! Will return a short description for a parameter, if it exists. + //! Empty string if it does not exist. + const std::string& GetDescription(const std::string& parameter) const; + + //! Returns whether or not a given parameter has a registered description + bool HasDescription(const std::string& parameter) const; + + //! Will delete the description of a parameter if it exists. + void ClearDescription(const std::string& parameter); + + //! Will delete all parameter descriptions + void ClearDescriptions(); + + //! Will generate a text-based documentation suited to show the user, for example on --help. + std::string GenerateDocumentation() const; + + private: + //! Will translate the c-like args to an std::vector + void PopulateRawArgs(const int argc, const char* const* argv); + + //! Will replace all args matching an abbreviation with their long form (like -f for --force) + void ExpandAbbreviations(); + + //! Will parse the next parameter. Returns the index of the next parameter. + std::size_t ParseNextParameter(const std::size_t parIndex, Parameter*& out_Par); + + //! Will convert a vector of string-values to an actual Value + Value* ParseValue(const std::vector& values, const ParamConstraint* constraint = nullptr); + + //! Will apply the loaded constraints on the loaded values, exluding types. + void ApplyConstraints(); + + //! Will return a pointer to a paramConstraint given a key. If there is no, it returns nullptr + const ParamConstraint* GetConstraintForKey(const std::string& key) const; + + std::string executableName; //! The path of the executable. Always argv[0] + std::unordered_map parameters; + + //! These are abbreviations. Like, -f for --force. + std::unordered_map parameterAbreviations; + + //! Parameter constraints, mapped to keys + std::unordered_map parameterConstraints; + + //! Raw argv + std::vector rawArgs; + + //! Short descriptions for parameters + //! First member is the abbreviation + std::unordered_map parameterDescriptions; + + //! A brief description of the application to be added to the generated documentation. Optional. + std::string briefDescription; + + //! If set to true, CmdArgsInterface will automatically catch the --help parameter, print the parameter documentation to stdout and exit. + bool catchHelp = true; + + //! If set to true, CmdArgsInterface will crash the application with output to stderr when an exception is thrown whilst parsing. + bool crashOnFail = true; + }; +} diff --git a/GhettoCryptCLI/Version.h b/GhettoCryptCLI/Version.h new file mode 100644 index 0000000..e2318b4 --- /dev/null +++ b/GhettoCryptCLI/Version.h @@ -0,0 +1,2 @@ +#pragma once +#define GHETTOCRYPTCLI_VERSION 0.1 diff --git a/GhettoCryptCLI/main.cpp b/GhettoCryptCLI/main.cpp new file mode 100644 index 0000000..b3fd5d4 --- /dev/null +++ b/GhettoCryptCLI/main.cpp @@ -0,0 +1,161 @@ +#include +#include +#include +#include "CommandlineInterface.h" +#include "../GhettoCrypt/Util.h" +#include "../GhettoCrypt/Config.h" +#include "../GhettoCrypt/Cipher.h" +#include "../GhettoCrypt/Flexblock.h" +#include "../GhettoCrypt/Block.h" + +using namespace GhettoCipher; + +Block GetEncryptionKey() +{ + // Easy-case: key supplied as param + if (CommandlineInterface::Get().HasParam("--key")) + return StringToBitblock(CommandlineInterface::Get()["--key"].GetString()); + + // Case: Ask for key + else if (CommandlineInterface::Get().HasParam("--keyask")) + { + std::string key; + std::cin >> key; + return StringToBitblock(key); + } + + // Case: Read key from file + else if (CommandlineInterface::Get().HasParam("--keyfile")) + { + const std::string keyfilepath = CommandlineInterface::Get()["--keyfile"].GetString(); + + // Read this many chars + const std::size_t maxChars = GhettoCipher::BLOCK_SIZE / 8; + + // Open ifilestream for keyfile + std::ifstream ifs(keyfilepath, std::ios::in | std::ios::binary); + + // Is the file open now? Or were there any issues... + if (!ifs.good()) + { + std::cerr << "Unable to open ifilestream for keyfile \"" << keyfilepath << "\"! Aborting..." << std::endl; + exit(-1); + } + + // Read these chars to buffer + char* ckeyfileContent = new char[maxChars]; + memset(ckeyfileContent, 0, maxChars * sizeof(char)); + ifs.read(ckeyfileContent, maxChars); + ifs.close(); + + // Convert the buffer to a bit block of key size + std::stringstream ss; + for (std::size_t i = 0; i < maxChars; i++) + ss << std::bitset<8>(ckeyfileContent[i]); + + Block key(ss.str()); + + // And delete the buffer + delete[] ckeyfileContent; + ckeyfileContent = nullptr; + + // Return it + return key; + } + + // Unreachable + throw std::runtime_error("This code should not have been reached. Most likely, the cli argument parser failed making sure at least one key method was supplied."); +} + +const Flexblock GetInputText(bool encryptionMode) +{ + // Easy-case: input text supplied as param + if (CommandlineInterface::Get().HasParam("--intext")) + // Encryption mode: We want to return the text as-is, as bits + if (encryptionMode) + return StringToBits(CommandlineInterface::Get()["--intext"].GetString()); + // Decryption mode: We need to first convert hexstring to bitstring + else + return HexstringToBits(CommandlineInterface::Get()["--intext"].GetString()); + + + // Case: Read from file + else if (CommandlineInterface::Get().HasParam("--infile")) + return ReadFileToBits(CommandlineInterface::Get()["--infile"].GetString()); + + // Unreachable + throw std::runtime_error("This code should not have been reached. Most likely, the cli argument parser failed making sure at least one key method was supplied."); +} + +const std::string GetOutfileName(const bool isEncryptionMode) +{ + // Do we have an output file name specified? + // Use it. + if (CommandlineInterface::Get().HasParam("--ofile")) + return CommandlineInterface::Get()["--ofile"].GetString(); + + // Else: append a custom postfix to the inputs filename + else + return CommandlineInterface::Get()["--infile"].GetString() + (isEncryptionMode ? ".crypt" : ".plain"); +} + +int main(int argc, char** argv) +{ + // Init cmdargs + CommandlineInterface::Init(argc, argv); + + // Get encryption key + const Block encryptionKey = GetEncryptionKey(); + + // Get operation modes + const bool shouldEncrypt = CommandlineInterface::Get().HasParam("--encrypt"); + const bool isFileMode = CommandlineInterface::Get().HasParam("--infile"); + + // Get the input text + const Flexblock input = GetInputText(shouldEncrypt); + + // Digest + Flexblock output; + Cipher cipher(encryptionKey); + + const bool printDigestionProgress = CommandlineInterface::Get().HasParam("--progress"); + + if (shouldEncrypt) + output = cipher.Encipher(input, printDigestionProgress); + else + output = cipher.Decipher(input, printDigestionProgress); + + // Now output the darn thing. + // Outputting a file + if (isFileMode) + { + // File mode is a bit different. + + // Dump to stdout? + if (CommandlineInterface::Get().HasParam("--ostdout")) + { + const std::string outstr = BitsToString(output); + + // We have to print char-by-char to prevent a nullbyte terminating output. + for (std::size_t i = 0; i < outstr.size(); i++) + std::cout << outstr[i]; + } + // Else: Dump to file + else + { + const std::string outfileName = GetOutfileName(shouldEncrypt); + WriteBitsToFile(outfileName, output); + } + } + // Else: we are just dealing with a string + else + { + // Output to stdout as a hexstring + if (shouldEncrypt) + std::cout << BitsToHexstring(output) << std::endl; + else + std::cout << BitsToString(output) << std::endl; + } + + return 0; +}