/*****************************************************************//**
* @file miragezip.h
* @brief This file defines the interface for the MirageZip
* back end, and provides functions for setting a password
* and evaluating its strength, zipping and encrypting
* imported files with libzip, and concatenating the
* archive with an imported image file.
*
* @author Kevin Crepps
* @date August 2024
*********************************************************************/
#pragma once
#include <sstream>
#include <fstream>
#include <filesystem>
#include <memory>
#include <Windows.h>
#include "zip.h"
/**
* Custom return codes specifying that
* a given operation:
*
* - Succeeded (0)
* - Failed and aborting is recommended (1)
* - Failed but program can safely continue (2)
*/
#define SUCCESS 0
#define FAILURE_ABORT 1
#define FAILURE_CONTINUE 2
/**
* Constants used in determining password strength.
*/
#define WEAK_SCORE 2
#define MEDIUM_SCORE 3
#define STRONG_SCORE 4
#define MEDIUM_LENGTH 8
#define STRONG_LENGTH 12
#define NUM_SETS 4
class MirageZip
{
public:
/**
* Used to specify path type with SetPath().
*/
enum PATH_TYPE
{
IMAGE,
FILE,
EXPORT
};
/**
* @brief
* Password containing null terminator is indicative of initial
* state. Call private function Init() to create app data folder.
*/
MirageZip() : password("\0") { Init(); }
/**
* @brief
* Stores error description for later retrieval using GetError().
*
* Overloaded for rvalues.
*/
inline void SetError(std::string&& arg) noexcept { error = std::move(arg); }
/**
* @brief
* Stores error description for later retrieval using GetError().
*
* Overloaded for lvalues.
*/
inline void SetError(const std::string& arg) noexcept { error = arg; }inline void SetError(std::string&& arg) noexcept { error = std::move(arg); }
/**
* @brief
* Stores error description for later retrieval using GetError().
*
* Overloaded for lvalues.
*/
inline void SetError(const std::string& arg) noexcept { error = arg; }inline void SetError(std::string&& arg) noexcept { error = std::move(arg); }
/**
* @brief
* Stores error description for later retrieval using GetError().
*
* Overloaded for lvalues.
*/
inline void SetError(const std::string& arg) noexcept { error = arg; }
/**
* @brief
* Retrieve exception info and other error messages set
* internally with SetError().
*/
inline std::string GetError() const noexcept { return error; }
/**
* @brief
* User sets the path of an image, file(s), and export location,
* specified with PATH_TYPE enum defined above.
*/
void SetPath(PATH_TYPE, const std::string&) noexcept;
/**
* @brief
* Use point system to evaluate password strength based on
* character diversity and length.
*
* @return
* Returns password strength score indicating:
* weak - fewer than three points
* medium - three points
* strong - four points
*/
unsigned int TestPassword(const std::string&) const noexcept;
/**
* @brief
* Set the password after evaluating, used to encrypt file
* archive in ZipFile().
*/
inline void SetPassword(const std::string& arg) noexcept { password = arg; }
/**
* @brief
* Zip and password-protect files using libzip functions.
*
* @return
* Custom return code defined at the top of miragezip.h.
*/
unsigned int ZipFile();
/**
* @brief
* Combines image and archive using Windows copy command
* with binary flag, issued through CreateProcessA().
*/
unsigned int Concatenate() const;
/**
* @brief
* Get path of new zip file - used in HideFile().
*/
inline const char* GetArchivePath() const noexcept { return archivePath.c_str(); }inline std::string GetError() const noexcept { return error; }
/**
* @brief
* User sets the path of an image, file(s), and export location,
* specified with PATH_TYPE enum defined above.
*/
void SetPath(PATH_TYPE, const std::string&) noexcept;
/**
* @brief
* Use point system to evaluate password strength based on
* character diversity and length.
*
* @return
* Returns password strength score indicating:
* weak - fewer than three points
* medium - three points
* strong - four points
*/
unsigned int TestPassword(const std::string&) const noexcept;
/**
* @brief
* Set the password after evaluating, used to encrypt file
* archive in ZipFile().
*/
inline void SetPassword(const std::string& arg) noexcept { password = arg; }
/**
* @brief
* Zip and password-protect files using libzip functions.
*
* @return
* Custom return code defined at the top of miragezip.h.
*/
unsigned int ZipFile();
/**
* @brief
* Combines image and archive using Windows copy command
* with binary flag, issued through CreateProcessA().
*/
unsigned int Concatenate() const;
/**
* @brief
* Get path of new zip file - used in HideFile().
*/
inline const char* GetArchivePath() const noexcept { return archivePath.c_str(); }inline std::string GetError() const noexcept { return error; }
/**
* @brief
* User sets the path of an image, file(s), and export location,
* specified with PATH_TYPE enum defined above.
*/
void SetPath(PATH_TYPE, const std::string&) noexcept;
/**
* @brief
* Use point system to evaluate password strength based on
* character diversity and length.
*
* @return
* Returns password strength score indicating:
* weak - fewer than three points
* medium - three points
* strong - four points
*/
unsigned int TestPassword(const std::string&) const noexcept;
/**
* @brief
* Set the password after evaluating, used to encrypt file
* archive in ZipFile().
*/
inline void SetPassword(const std::string& arg) noexcept { password = arg; }
/**
* @brief
* Zip and password-protect files using libzip functions.
*
* @return
* Custom return code defined at the top of miragezip.h.
*/
unsigned int ZipFile();
/**
* @brief
* Combines image and archive using Windows copy command
* with binary flag, issued through CreateProcessA().
*/
unsigned int Concatenate() const;
/**
* @brief
* Get path of new zip file - used in HideFile().
*/
inline const char* GetArchivePath() const noexcept { return archivePath.c_str(); }
~MirageZip() {}
private:
std::string error, // Error info buffer, set in SetError()
archivePath, // Path to zip; this is set in CreateAppData()
imagePath, // Path to user-selected image file
filePath, // Path to user-selected file to be zipped
exportPath, // Destination of output file
password; // Password used to encrypt archive
struct stat Statinfo; /* Used for checking whether
directory exists with stat() */
/**
* Character set types used in password evaluation.
*/
enum CHAR_TYPE
{
LOWER,
UPPER,
NUMBERS,
CHARACTERS
};
/**
* @brief
* Called from the constructor. CreateAppData is run from here.
* If unable to create directory, user is notified, program terminates.
*/
void Init();
/**
* @brief
* Checks for existence of %LOCALAPPDATA%\MirageZip, attempts to
* create it if it doesn't exist.
*
* @return
* Custom return code defined at the top of miragezip.h.
*/
unsigned int CreateAppData();
};
/**
* @brief
* Non-member non-friend function attempts to zip file,
* combine it with image, then purge app data files.
*/
extern unsigned int HideFile(MirageZip*);
/*****************************************************************//**
* @file miragezip.cpp
* @brief MirageZip implementation.
*
* @author Kevin Crepps
* @date August 2024
*********************************************************************/
#include "miragezip.h"
void MirageZip::Init()
{
/* If %LOCALAPPDATA%\MirageZip doesn't exist, and
attempt to create it fails, notify user and abort */
if (CreateAppData())
{
MessageBoxA(NULL, GetError().c_str(), "Failed to create app data folder.", MB_OK);
abort();
}
}
unsigned int MirageZip::CreateAppData()unsigned int MirageZip::CreateAppData()unsigned int MirageZip::CreateAppData()
{
try
{
// Set archivePath member, check whether folder already exists
std::string path{ getenv("LOCALAPPDATA") };
path += "\\MirageZip";
archivePath = path + "\\archive.zip";
stat(path.c_str(), &Statinfo);
// If directory doesn't exist, create it
if (!(Statinfo.st_mode & S_IFDIR))
{
std::error_code err;
err.clear();
if (!std::filesystem::create_directories(path, err))
{
std::stringstream msg("Error code: ");
msg << err;
SetError(std::move(msg.str()));
return FAILURE_ABORT;
}
}
return SUCCESS;
}
catch (...)
{
SetError("Unknown exception thrown.");
return FAILURE_ABORT;
}
}
void MirageZip::SetPath(PATH_TYPE type, const std::string& path) noexceptvoid MirageZip::SetPath(PATH_TYPE type, const std::string& path) noexceptvoid MirageZip::SetPath(PATH_TYPE type, const std::string& path) noexcept
{
switch (type)
{
case IMAGE:
imagePath = path;
break;
case FILE:
filePath = path;
break;
case EXPORT:
exportPath = path;
}
}
unsigned int MirageZip::TestPassword(const std::string& pw) const noexceptunsigned int MirageZip::TestPassword(const std::string& pw) const noexceptunsigned int MirageZip::TestPassword(const std::string& pw) const noexcept
{
bool found[NUM_SETS]{ false, false, false, false };
char lowerSet[]{ "abcdefghijklmnopqrstuvwxyz" },
upperSet[]{ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" },
numberSet[]{ "0123456789" },
charSet[]{ "!@#$%^&*()-_=+,.<>/?|[]{}:;~\'\"\\" };
char* sets[NUM_SETS]{ lowerSet, upperSet, numberSet, charSet };
float points{ 0.0f };bool found[NUM_SETS]{ false, false, false, false };
char lowerSet[]{ "abcdefghijklmnopqrstuvwxyz" },
upperSet[]{ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" },
numberSet[]{ "0123456789" },
charSet[]{ "!@#$%^&*()-_=+,.<>/?|[]{}:;~\'\"\\" };
char* sets[NUM_SETS]{ lowerSet, upperSet, numberSet, charSet };
float points{ 0.0f };bool found[NUM_SETS]{ false, false, false, false };
char lowerSet[]{ "abcdefghijklmnopqrstuvwxyz" },
upperSet[]{ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" },
numberSet[]{ "0123456789" },
charSet[]{ "!@#$%^&*()-_=+,.<>/?|[]{}:;~\'\"\\" };
char* sets[NUM_SETS]{ lowerSet, upperSet, numberSet, charSet };
float points{ 0.0f };
/* Compare each character in password to each character set if not yet matched,
add half a point for each character set with one or more instances
of lowercase, uppercase, digits or special characters in password */
for (auto& c : pw)
{
for (unsigned char type = LOWER; type < NUM_SETS; ++type)
{
if (!found[type])
{
if (strchr(sets[type], c))
{
points += 0.5f;
found[type] = true;
}
}
}
}
// If pass is medium length (less than 12), add one point if it has at least three character types
if (pw.length() >= MEDIUM_LENGTH && pw.length() < STRONG_LENGTH && points >= 1.5f)
++points;
// If strong length, add two points if it has at least two character types
else if (pw.length() >= STRONG_LENGTH && points >= 1.0f)
points += 2;
/* strong - four points
medium - three points
weak - fewer than three points */
return (unsigned int)std::floor(points);
}
unsigned int MirageZip::ZipFile()unsigned int MirageZip::ZipFile()unsigned int MirageZip::ZipFile()
{
/* This function will read file data into a buffer, zip the file using the buffer, and encrypt the data using 256-bit AES if a password has been set by the user. Error handling is done in HideFile() */
// Create archive and open
std::remove(archivePath.c_str());
int errCode = 0;
zip* archive = zip_open(archivePath.c_str(), ZIP_CREATE, &errCode);
if (!archive) return FAILURE_ABORT;
// Open file, get size
std::ifstream file(filePath, std::ios::binary);
if (!file.is_open()) return FAILURE_ABORT;
file.seekg(0, std::ios::end);
int fileSize = file.tellg();
file.seekg(0, std::ios::beg);
// Store file data in resource management object, close file
std::unique_ptr<char[]> fileData(new char[fileSize]);std::unique_ptr<char[]> fileData(new char[fileSize]);std::unique_ptr<char[]> fileData(new char[fileSize]);
file.read(fileData.get(), fileSize);
file.close();
// Get filename from file path
std::string fileName(filePath);
size_t strPos = fileName.find_last_of("\\");
fileName.erase(0, strPos + 1);
// Zip file using buffer
zip_source_t* source;
source = zip_source_buffer(archive, fileData.get(), fileSize, 0);
if (!source) return FAILURE_ABORT;
zip_file_add(archive, fileName.c_str(), source, 0);
// Encrypt archive if password was set
if (password != "\0")
zip_file_set_encryption(archive, 0, ZIP_EM_AES_256, password.c_str());
// Close archive
zip_close(archive);
return SUCCESS;
}
unsigned int MirageZip::Concatenate() constunsigned int MirageZip::Concatenate() constunsigned int MirageZip::Concatenate() const
{
/* Execute Windows system command to combine files, e.g.:
COPY /B <image path> + <archive path> <destination path>
Error handling is done in HideFile() */
STARTUPINFOA info{ sizeof(info) };
PROCESS_INFORMATION processInfo;
std::stringstream cmd{ "" };
cmd << "/c COPY /B \"" << imagePath << "\" + \"" << archivePath << "\" \"" << exportPath << "\"";
if (CreateProcessA("C:\\Windows\\System32\\cmd.exe",
cmd.str().data(), NULL, NULL, TRUE, 0, NULL, NULL, &info, &processInfo))
{
WaitForSingleObject(processInfo.hProcess, INFINITE);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
return SUCCESS;
}
return FAILURE_ABORT;
}
// -- External --
unsigned int HideFile(MirageZip* obj)unsigned int HideFile(MirageZip* obj)unsigned int HideFile(MirageZip* obj)
{
// Attempt to zip selected file
try
{
obj->ZipFile();
}
catch (const std::exception& e)
{
obj->SetError(e.what());
return FAILURE_ABORT;
}
catch (...)
{
obj->SetError("Exception thrown while attempting to zip file.");
return FAILURE_ABORT;
}
// Attempt to concatenate image and temp archive
try
{
obj->Concatenate();
}
catch (const std::exception& e)
{
obj->SetError(e.what());
return FAILURE_ABORT;
}
catch (...)
{
obj->SetError("Exception thrown while attempting to concatenate files.");
return FAILURE_ABORT;
}
// Attempt to delete temp archive
try
{
std::remove(obj->GetArchivePath());
}
catch (const std::exception& e)
{
obj->SetError(e.what());
return FAILURE_CONTINUE;
}
catch (...)
{
obj->SetError("Exception thrown while attempting to remove temp archive.");
return FAILURE_CONTINUE;
}
return SUCCESS;
}
|
Member functions declared const can make an interface more intuitive by letting the client know which functions may
or may not modify the object, but there's another popular technique involving const functions that can be very useful. First, consider
the following derived class:
class Tesla : public Car
{
private:
std::string id;
public:
Tesla();
virtual ~Tesla();
};
bool prepare(Tesla t);
Imagine that the base class contains a string as well. Passing by value as shown above in a call
to prepare will copy the entire object, then destroy the copy - this means calling Tesla's copy constructor and destructor, as well as Car's
copy constructor and destructor, not to mention the constructors and destructors for both of the string objects. That's eight function calls
just from passing one object by value, making this a much more expensive operation than it has to be.
bool prepare(const Tesla& t);
Passing by reference-to-const allows us to circumvent all of those constructor/destructor calls, as well as guarantee that the object
being passed in won't be modified, just as passing by value does. Any member functions we call using this reference will require const versions.
In simple terms, polymorphism means one symbol (like an object or function name) can point to different things
under different circumstances. Function overloading is one example of compile-time polymorphism.
At some point during compilation, each object or function call is replaced with a machine language instruction saying
"go to this address for the value or function definition." This is called early binding, or static binding.
During this process, what happens when a call to a virtual function is encountered? The definition the call
points to won't be determined until runtime! Whenever a virtual function is declared, a virtual table,
or v-table containing the addresses of the class's virtual functions will be created for the
class and all of its child classes. The base class also receives a hidden data member - the virtual
pointer, or v-pointer, which will point to the v-table containing the appropriate function's address.
So, during compilation, when a virtual function call is encountered, it's instead replaced with an instruction
to go to the address of the v-pointer, which will, at some point during runtime, point to the proper definition.
When we call a virtual function like this:
obj->call();
...what's actually happening is this:
obj->vptr->call();
This is called late binding, or dynamic binding, and it's how runtime polymorphism works.
int x = 0;
int y(0);
int z{ 0 };
There are several ways to initialize variables in C++, but some syntaxes only work in certain scenarios; for example,
we can initialize non-static data members with = or {}, but not (). We can initialize uncopyable
objects with () or {}, but not =.
With C++11 came uniform initialization syntax, or braced initialization. This syntax is meant to work in every
scenario to make things easier, and, in addition to being the only way to initialize STL containers, it has some unique
features.
float x, y;
int z(x + y);
The above results in an implicit narrowing conversion, potentially truncating the data stored in x and y. Brace syntax
instead would not compile.
Suppose you'd like to use () syntax to declare an object, calling its default constructor:
Node n1();
Perfectly valid, although what the above code actually does is declare a function called n1 that
returns a Node! This is known as C++'s most vexing parse, and it's avoided with uniform initialization syntax.
We could have made this another member function, but a non-member, non-friend function that works
just as well would mean one fewer function that has access to private members.
Basically, the fewer
functions that can see the data members, the more encapsulated they are. The more encapsulated
they are, the more flexibility we have to make changes, and the less client code affected.
If we use the new keyword, we have to release the memory back to the OS using delete. If
an exception occurs before the delete statement is reached, we'll have leaked the memory.
Perhaps we employ an auto_ptr. These automatically call delete on what they point to when the scope
in which they're declared is exited (or during stack unwinding if an exception occurs - the object's destructor is called in either case).
The resource (dynamically allocated memory, here) is obtained in the same statement as the object's initialization,
so this technique is referred to as Resource Acquisition Is Initialization (RAII).
What happens if there are more than one auto_ptr pointing to the same resource? One will be destroyed first, calling delete, and the
second one will eventually attempt to call delete on something that doesn't exist, which can lead to undefined behavior. For this reason,
there's certain copy functionality burned into auto_ptrs - that is, if we attempt to copy one, the original sets itself to NULL.
This isn't "normal copying behavior", so use of auto_ptr with STL containers is prohibited.
One alternative that's immune to the drawbacks above is shared_ptr. This one keeps track of how many objects are
pointing to the resource, so it's able to call delete only when nothing else is pointing to it - this is called a
reference-counting smart pointer (RCSP).
It doesn't make sense to call delete on a socket or a mutex, but resource management objects can be used for more than
just memory. shared_ptr can be passed a deleter function, in which we can define any behavior we want, like closing a socket or
unlocking a mutex.
|
|