Skip to content

Commit

Permalink
Fix tool unicode usage in Windows (#664)
Browse files Browse the repository at this point in the history
IB-8055

Signed-off-by: Raul Metsma <raul@metsma.ee>
  • Loading branch information
metsma authored Feb 25, 2025
1 parent 5250d87 commit cecba69
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 62 deletions.
4 changes: 4 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,11 @@ target_link_libraries(digidocpp PRIVATE ${CMAKE_DL_LIBS} minizip digidocpp_priv)

if( BUILD_TOOLS )
add_executable(digidoc-tool digidoc-tool.cpp)
target_compile_features(digidoc-tool PUBLIC cxx_std_20)
target_link_libraries(digidoc-tool digidocpp digidocpp_priv Threads::Threads)
target_link_options(digidoc-tool PRIVATE
$<$<PLATFORM_ID:Windows>: /MANIFEST:NO /MANIFEST:EMBED /MANIFESTINPUT:${CMAKE_CURRENT_SOURCE_DIR}/digidoc-tool.manifest>
)
configure_file( digidoc-tool.1.cmake digidoc-tool.1 )
endif()

Expand Down
144 changes: 82 additions & 62 deletions src/digidoc-tool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,30 @@ string ConsolePinSigner::pin(const X509Cert &certificate) const
return result;
}

struct value: public string_view {
using string_view::string_view;
using string_view::operator=;

constexpr value(string_view arg, string_view param) noexcept
: string_view(arg.size() > param.size() && arg.starts_with(param) ?
arg.substr(param.size()) : string_view{})
{}

operator string() const
{
return {begin(), end()};
}

constexpr operator bool() const noexcept
{
return !empty();
}
};

class ToolConfig final: public XmlConfCurrent
{
public:
enum Warning {
enum Warning : uint8_t {
WError,
WWarning,
WIgnore
Expand All @@ -272,10 +292,6 @@ class ToolConfig final: public XmlConfCurrent
string TSLUrl() const final { return tslurl.value_or(XmlConfCurrent::TSLUrl()); }

unique_ptr<Signer> getSigner(bool getwebsigner = false) const;
static string toUTF8(string_view param)
{
return fs::path(param).u8string();
}

// Config
optional<int> _logLevel;
Expand All @@ -288,19 +304,19 @@ class ToolConfig final: public XmlConfCurrent
optional<string> siguri;

// Signer
string profile, city, street, state, postalCode, country, userAgent;
value profile, city, street, state, postalCode, country, userAgent;
vector<string> roles;
bool XAdESEN = false;

// Token
optional<bool> rsaPss;
string pkcs11, pkcs12, pin, cert;
value pkcs11, pkcs12, pin, cert;
vector<unsigned char> thumbprint;
bool cng = true, selectFirst = false;

// Params
string path;
unordered_map<string,string> files;
value path;
unordered_map<value,value,std::hash<string_view>> files;
bool doSign = true, dontValidate = false;
static string_view RED, GREEN, YELLOW, RESET;
};
Expand Down Expand Up @@ -391,40 +407,39 @@ ToolConfig::ToolConfig(int argc, char *argv[])
for(int i = 2; i < argc; i++)
{
string_view arg(argv[i]);
if(arg.find("--profile=") == 0)
profile = arg.substr(10);
else if(arg.find("--file=") == 0)
if(value v{arg, "--profile="}) profile = v;
else if(value v{arg, "--file="})
{
string_view arg2(i+1 < argc ? argv[i+1] : string_view());
files.emplace(arg.substr(7),
arg2.find("--mime=") == 0 ? toUTF8(arg2.substr(7)) : "application/octet-stream");
value mime(i+1 < argc ? argv[i+1] : string_view(), "--mime=");
files.emplace(v, mime ? mime : "application/octet-stream");
}
#ifdef _WIN32
else if(arg == "--cng") cng = true;
else if(arg == "--selectFirst") selectFirst = true;
else if(arg.find("--thumbprint=") == 0) thumbprint = File::hexToBin(arg.substr(arg.find('=') + 1));
else if(value v{arg, "--thumbprint="}) thumbprint = File::hexToBin(v);
#endif
else if(arg.find("--pkcs11") == 0)
else if(arg == "--pkcs11")
cng = false;
else if(value v{arg, "--pkcs11="})
{
cng = false;
if(arg.find('=') != string::npos)
pkcs11 = toUTF8(arg.substr(arg.find('=') + 1));
pkcs11 = v;
}
else if(arg.find("--pkcs12=") == 0)
else if(value v{arg, "--pkcs12="})
{
cng = false;
pkcs12 = toUTF8(arg.substr(9));
pkcs12 = v;
}
else if(arg == "--dontValidate") dontValidate = true;
else if(arg == "--XAdESEN") XAdESEN = true;
else if(arg.find("--pin=") == 0) pin = arg.substr(6);
else if(arg.find("--cert=") == 0) cert = toUTF8(arg.substr(7));
else if(arg.find("--city=") == 0) city = toUTF8(arg.substr(7));
else if(arg.find("--street=") == 0) street = toUTF8(arg.substr(9));
else if(arg.find("--state=") == 0) state = toUTF8(arg.substr(8));
else if(arg.find("--postalCode=") == 0) postalCode = toUTF8(arg.substr(13));
else if(arg.find("--country=") == 0) country = toUTF8(arg.substr(10));
else if(arg.find("--role=") == 0) roles.push_back(toUTF8(arg.substr(7)));
else if(value v{arg, "--pin="}) pin = v;
else if(value v{arg, "--cert="}) cert = v;
else if(value v{arg, "--city="}) city = v;
else if(value v{arg, "--street="}) street = v;
else if(value v{arg, "--state="}) state = v;
else if(value v{arg, "--postalCode="}) postalCode = v;
else if(value v{arg, "--country="}) country = v;
else if(value v{arg, "--role="}) roles.emplace_back(v);
else if(arg == "--sha224") uri = URI_SHA224;
else if(arg == "--sha256") uri = URI_SHA256;
else if(arg == "--sha384") uri = URI_SHA384;
Expand All @@ -439,15 +454,15 @@ ToolConfig::ToolConfig(int argc, char *argv[])
else if(arg == "--sigpsssha512") { siguri = URI_SHA512; rsaPss = true; }
else if(arg == "--rsapkcs15") rsaPss = false;
else if(arg == "--rsapss") rsaPss = true;
else if(arg.find("--tsurl") == 0) tsurl = arg.substr(8);
else if(arg.find("--tslurl=") == 0) tslurl = arg.substr(9);
else if(arg.find("--tslcert=") == 0) tslcerts = vector<X509Cert>{ X509Cert(toUTF8(arg.substr(10))) };
else if(value v{arg, "--tsurl"}) tsurl = v;
else if(value v{arg, "--tslurl="}) tslurl = v;
else if(value v{arg, "--tslcert="}) tslcerts = vector<X509Cert>{ X509Cert(v) };
else if(arg == "--TSLAllowExpired") expired = true;
else if(arg == "--dontsign") doSign = false;
else if(arg == "--nocolor") RED = GREEN = YELLOW = RESET = {};
else if(arg.find("--loglevel=") == 0) _logLevel = atoi(arg.substr(11).data());
else if(arg.find("--logfile=") == 0) _logFile = toUTF8(arg.substr(10));
else path = toUTF8(arg);
else if(value v{arg, "--loglevel="}) _logLevel = atoi(v.data());
else if(value v{arg, "--logfile="}) _logFile = v;
else path = arg;
}
}

Expand Down Expand Up @@ -478,7 +493,7 @@ unique_ptr<Signer> ToolConfig::getSigner(bool getwebsigner) const
#ifdef _WIN32
else if(cng)
{
unique_ptr<WinSigner> win = make_unique<WinSigner>(pin, selectFirst);
auto win = make_unique<WinSigner>(pin, selectFirst);
win->setThumbprint(thumbprint);
signer = std::move(win);
}
Expand Down Expand Up @@ -544,7 +559,7 @@ static int validateSignature(const Signature *s, ToolConfig::Warning warning = T
static int open(int argc, char* argv[])
{
ToolConfig::Warning reportwarnings = ToolConfig::WWarning;
string path;
value path;
fs::path extractPath;
bool validateOnExtract = false;
int returnCode = EXIT_SUCCESS;
Expand All @@ -557,31 +572,28 @@ static int open(int argc, char* argv[])
// Parse command line arguments.
for(int i = 2; i < argc; i++)
{
string arg(ToolConfig::toUTF8(argv[i]));
string_view arg(argv[i]);
if(arg == "--list")
continue;
if(arg.find("--warnings=") == 0)
if(value v{arg, "--warnings="})
{
if(arg.substr(11, 6) == "ignore") reportwarnings = ToolConfig::WIgnore;
if(arg.substr(11, 5) == "error") reportwarnings = ToolConfig::WError;
if(v == "ignore") reportwarnings = ToolConfig::WIgnore;
if(v == "error") reportwarnings = ToolConfig::WError;
}
else if(arg.find("--extractAll") == 0)
{
else if(arg == "--extractAll")
extractPath = fs::current_path();
if(auto pos = arg.find('='); pos != string::npos)
{
fs::path newPath = fs::u8path(arg.substr(pos + 1));
extractPath = newPath.is_relative() ? extractPath / newPath : std::move(newPath);
}
else if(value v{arg, "--extractAll="})
{
extractPath = fs::absolute(fs::path(v.begin(), v.end()));
if(!fs::is_directory(extractPath))
THROW("Path is not directory");
}
else if(arg == "--validateOnExtract")
validateOnExtract = true;
else if(arg.find("--offline") == 0)
else if(arg == "--offline")
cb.online = false;
else
path = std::move(arg);
path = arg;
}

if(path.empty())
Expand All @@ -601,8 +613,8 @@ static int open(int argc, char* argv[])
for(const DataFile *file: doc->dataFiles())
{
try {
string dst = (extractPath / fs::u8path(file->fileName()).filename()).u8string();
file->saveAs(dst);
auto dst = (extractPath / fs::path(file->fileName()).filename());
file->saveAs(dst.string());
cout << " Document(" << file->mediaType() << ") extracted to " << dst << " (" << file->fileSize() << " bytes)" << endl;
} catch(const Exception &e) {
cout << " Document " << file->fileName() << " extraction: " << ToolConfig::RED << "FAILED" << ToolConfig::RESET << endl;
Expand Down Expand Up @@ -689,16 +701,16 @@ static int open(int argc, char* argv[])
static int remove(int argc, char *argv[])
{
vector<unsigned int> documents, signatures;
string path;
value path;
for(int i = 2; i < argc; i++)
{
string arg(ToolConfig::toUTF8(argv[i]));
if(arg.find("--document=") == 0)
documents.push_back(unsigned(stoi(arg.substr(11))));
else if(arg.find("--signature=") == 0)
signatures.push_back(unsigned(stoi(arg.substr(12))));
string_view arg(argv[i]);
if(value v{arg, "--document="})
documents.push_back(unsigned(atoi(v.data())));
else if(value v{arg, "--signature="})
signatures.push_back(unsigned(atoi(v.data())));
else
path = std::move(arg);
path = arg;
}

if(path.empty())
Expand Down Expand Up @@ -831,11 +843,11 @@ static int createBatch(const ToolConfig &p, const char *program)
unique_ptr<Signer> signer = p.getSigner();
error_code ec;
int returnCode = EXIT_SUCCESS;
for(const auto &file: fs::directory_iterator(fs::u8path(p.path), ec))
for(const auto &file: fs::directory_iterator(string_view(p.path), ec))
{
if(!fs::is_regular_file(file.status()) || file.path().extension() == ".asice")
continue;
const auto path = file.path().u8string();
const auto path = file.path().string();
cout << "Signing file: " << path << endl;
try {
unique_ptr<Container> doc = Container::createPtr(path + ".asice");
Expand Down Expand Up @@ -952,7 +964,7 @@ static int tslcmd(int /*argc*/, char* /*argv*/[])
for(const X509Cert &cert: p.certs)
cout << " Signer: " << cert << endl;
string path = cache + "/" + p.territory + ".xml";
if(error_code ec; !fs::exists(fs::u8path(path), ec))
if(error_code ec; !fs::exists(fs::path(path), ec))
{
cout << " TSL: missing" << endl;
continue;
Expand Down Expand Up @@ -994,6 +1006,14 @@ static int tslcmd(int /*argc*/, char* /*argv*/[])
*/
int main(int argc, char *argv[]) try
{
#ifdef _WIN32
struct codepage_scope {
~codepage_scope() noexcept { SetConsoleOutputCP(oldOutputCP); }
UINT oldOutputCP;
};
codepage_scope scope{GetConsoleOutputCP()};
SetConsoleOutputCP(CP_UTF8);
#endif
printf("Version\n");
printf(" digidoc-tool version: %s\n", VERSION_STR);
printf(" libdigidocpp version: %s\n", version().c_str());
Expand Down
15 changes: 15 additions & 0 deletions src/digidoc-tool.manifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
</windowsSettings>
</application>
</assembly>

0 comments on commit cecba69

Please sign in to comment.