changeset 6304:36cd91a53403

merged utf8-branch -> default
author Alain Mazy <am@orthanc.team>
date Tue, 09 Sep 2025 15:35:30 +0200
parents 88b7494557f2 (current diff) a933e08efd7f (diff)
children e6755569b9d2
files NEWS OrthancFramework/Sources/HttpServer/HttpServer.cpp
diffstat 50 files changed, 495 insertions(+), 252 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Tue Sep 09 14:26:51 2025 +0200
+++ b/NEWS	Tue Sep 09 15:35:30 2025 +0200
@@ -10,7 +10,12 @@
   was not able to send a full buffer over the network, Orthanc was not retrying 
   to send the remaining part.  This is now fixed.  
   (https://discourse.orthanc-server.org/t/incomplete-zip-downloads-from-get-studies-id-media/6046)
-
+* Reworked all the paths handling, improving general support of non ASCII-only paths on Windows,
+  specifically for the Storage directories and for the configuration files.
+  However, on Windows, some features might still not support non ASCII-only paths:
+  - Reading a DCMTK dictionary ("ExternalDictionaries" configuration)
+  - Using a "TemporaryDirectory" to save zip file or to export DICOMDIR
+  - The "SslCertificate" and other related configurations
 
 
 Version 1.12.9 (2025-08-11)
--- a/OrthancFramework/Sources/Compression/HierarchicalZipWriter.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/Compression/HierarchicalZipWriter.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -149,7 +149,7 @@
   }
 
 
-  HierarchicalZipWriter::HierarchicalZipWriter(const char* path)
+  HierarchicalZipWriter::HierarchicalZipWriter(const boost::filesystem::path& path)
   {
     writer_.SetOutputPath(path);
     writer_.Open();
--- a/OrthancFramework/Sources/Compression/HierarchicalZipWriter.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/Compression/HierarchicalZipWriter.h	Tue Sep 09 15:35:30 2025 +0200
@@ -29,6 +29,7 @@
 #include <map>
 #include <list>
 #include <boost/lexical_cast.hpp>
+#include <boost/filesystem.hpp>
 
 #if ORTHANC_BUILD_UNIT_TESTS == 1
 #  include <gtest/gtest_prod.h>
@@ -83,7 +84,7 @@
     ZipWriter writer_;
 
   public:
-    explicit HierarchicalZipWriter(const char* path);
+    explicit HierarchicalZipWriter(const boost::filesystem::path& path);
 
     HierarchicalZipWriter(ZipWriter::IOutputStream* stream,  // transfers ownership
                           bool isZip64);
--- a/OrthancFramework/Sources/Compression/ZipReader.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/Compression/ZipReader.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -248,7 +248,7 @@
 
 
 #if ORTHANC_SANDBOXED != 1
-  bool ZipReader::IsZipFile(const std::string& path)
+  bool ZipReader::IsZipFile(const boost::filesystem::path& path)
   {
     std::string content;
     SystemToolbox::ReadFileRange(content, path, 0, 4,
@@ -409,20 +409,20 @@
   
 
 #if ORTHANC_SANDBOXED != 1
-  ZipReader* ZipReader::CreateFromFile(const std::string& path)
+  ZipReader* ZipReader::CreateFromFile(const boost::filesystem::path& path)
   {
     if (!IsZipFile(path))
     {
-      throw OrthancException(ErrorCode_BadFileFormat, "The file doesn't contain a ZIP archive: " + path);
+      throw OrthancException(ErrorCode_BadFileFormat, "The file doesn't contain a ZIP archive: " + SystemToolbox::PathToUtf8(path));
     }
     else
     {
       std::unique_ptr<ZipReader> reader(new ZipReader);
 
-      reader->pimpl_->unzip_ = unzOpen64(path.c_str());
+      reader->pimpl_->unzip_ = unzOpen64(SystemToolbox::PathToUtf8(path).c_str());
       if (reader->pimpl_->unzip_ == NULL)
       {
-        throw OrthancException(ErrorCode_BadFileFormat, "Cannot open ZIP archive from file: " + path);
+        throw OrthancException(ErrorCode_BadFileFormat, "Cannot open ZIP archive from file: " + SystemToolbox::PathToUtf8(path));
       }
       else
       {
--- a/OrthancFramework/Sources/Compression/ZipReader.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/Compression/ZipReader.h	Tue Sep 09 15:35:30 2025 +0200
@@ -44,6 +44,9 @@
 #include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
 
+#if ORTHANC_SANDBOXED != 1
+#  include <boost/filesystem.hpp>
+#endif
 
 namespace Orthanc
 {
@@ -73,7 +76,7 @@
     static ZipReader* CreateFromMemory(const std::string& buffer);
 
 #if ORTHANC_SANDBOXED != 1
-    static ZipReader* CreateFromFile(const std::string& path);    
+    static ZipReader *CreateFromFile(const boost::filesystem::path& path);
 #endif
 
     static bool IsZipMemoryBuffer(const void* buffer,
@@ -82,7 +85,7 @@
     static bool IsZipMemoryBuffer(const std::string& content);
 
 #if ORTHANC_SANDBOXED != 1
-    static bool IsZipFile(const std::string& path);
+    static bool IsZipFile(const boost::filesystem::path& path);
 #endif
   };
 }
--- a/OrthancFramework/Sources/Compression/ZipWriter.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/Compression/ZipWriter.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -544,11 +544,11 @@
 
       if (isZip64_)
       {
-        pimpl_->file_ = zipOpen64(path_.c_str(), mode);
+        pimpl_->file_ = zipOpen64(SystemToolbox::PathToUtf8(path_).c_str(), mode);
       }
       else
       {
-        pimpl_->file_ = zipOpen(path_.c_str(), mode);
+        pimpl_->file_ = zipOpen(SystemToolbox::PathToUtf8(path_).c_str(), mode);
       }
 
       if (!pimpl_->file_)
@@ -559,13 +559,13 @@
     }
   }
 
-  void ZipWriter::SetOutputPath(const char* path)
+  void ZipWriter::SetOutputPath(const boost::filesystem::path& path)
   {
     Close();
     path_ = path;
   }
 
-  const std::string &ZipWriter::GetOutputPath() const
+  const boost::filesystem::path& ZipWriter::GetOutputPath() const
   {
     return path_;
   }
@@ -603,7 +603,7 @@
     return compressionLevel_;
   }
 
-  void ZipWriter::OpenFile(const char* path)
+  void ZipWriter::OpenFile(const char* filename)
   {
     Open();
 
@@ -614,7 +614,7 @@
 
     if (isZip64_)
     {
-      result = zipOpenNewFileInZip64(pimpl_->file_, path,
+      result = zipOpenNewFileInZip64(pimpl_->file_, filename,
                                      &zfi,
                                      NULL,   0,
                                      NULL,   0,
@@ -624,7 +624,7 @@
     }
     else
     {
-      result = zipOpenNewFileInZip(pimpl_->file_, path,
+      result = zipOpenNewFileInZip(pimpl_->file_, filename,
                                    &zfi,
                                    NULL,   0,
                                    NULL,   0,
--- a/OrthancFramework/Sources/Compression/ZipWriter.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/Compression/ZipWriter.h	Tue Sep 09 15:35:30 2025 +0200
@@ -46,6 +46,7 @@
 #include <string>
 #include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
+#include <boost/filesystem.hpp>
 
 namespace Orthanc
 {
@@ -132,7 +133,7 @@
     bool hasFileInZip_;
     bool append_;
     uint8_t compressionLevel_;
-    std::string path_;
+    boost::filesystem::path path_;
 
     std::unique_ptr<IOutputStream> outputStream_;
 
@@ -159,11 +160,11 @@
 
     bool IsOpen() const;
 
-    void SetOutputPath(const char* path);
+    void SetOutputPath(const boost::filesystem::path& path);
 
-    const std::string& GetOutputPath() const;
+    const boost::filesystem::path& GetOutputPath() const;
 
-    void OpenFile(const char* path);
+    void OpenFile(const char* filename);
 
     void Write(const void* data, size_t length);
 
--- a/OrthancFramework/Sources/DicomParsing/DicomDirWriter.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomDirWriter.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -135,7 +135,7 @@
     {
       if (dir_.get() == NULL)
       {
-        dir_.reset(new DcmDicomDir(file_.GetPath().c_str(), 
+        dir_.reset(new DcmDicomDir(SystemToolbox::PathToUtf8(file_.GetPath()).c_str(), 
                                    fileSetId_.c_str()));
         //SetTagValue(dir_->getRootRecord(), DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(Encoding_Utf8));
       }
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -46,6 +46,7 @@
 
 #if ORTHANC_SANDBOXED == 0
 #  include "../TemporaryFile.h"
+#  include "../SystemToolbox.h"
 #endif
 
 #include <list>
@@ -179,7 +180,7 @@
     TemporaryFile tmp;
     tmp.Write(content);
 
-    if (!dictionary.loadDictionary(tmp.GetPath().c_str()))
+    if (!dictionary.loadDictionary(SystemToolbox::PathToUtf8(tmp.GetPath()).c_str()))
     {
       throw OrthancException(ErrorCode_InternalError,
                              "Cannot read embedded dictionary. Under Windows, make sure that " 
--- a/OrthancFramework/Sources/FileStorage/FilesystemStorage.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/FileStorage/FilesystemStorage.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -72,7 +72,7 @@
     return path;
   }
 
-  void FilesystemStorage::Setup(const std::string& root)
+  void FilesystemStorage::Setup(const boost::filesystem::path& root)
   {
     //root_ = boost::filesystem::absolute(root).string();
     root_ = root;
@@ -80,13 +80,13 @@
     SystemToolbox::MakeDirectory(root);
   }
 
-  FilesystemStorage::FilesystemStorage(const std::string &root) :
+  FilesystemStorage::FilesystemStorage(const boost::filesystem::path &root) :
     fsyncOnWrite_(false)
   {
     Setup(root);
   }
 
-  FilesystemStorage::FilesystemStorage(const std::string &root,
+  FilesystemStorage::FilesystemStorage(const boost::filesystem::path &root,
                                        bool fsyncOnWrite) :
     fsyncOnWrite_(fsyncOnWrite)
   {
@@ -171,7 +171,7 @@
 
       try 
       {
-        SystemToolbox::WriteFile(content, size, path.string(), fsyncOnWrite_);
+        SystemToolbox::WriteFile(content, size, path, fsyncOnWrite_);
         
         LOG(INFO) << "Created attachment \"" << uuid << "\" (" << timer.GetHumanTransferSpeed(true, size) << ")";
         return;
@@ -214,7 +214,7 @@
 
     std::string content;
     SystemToolbox::ReadFileRange(
-      content, GetPath(uuid).string(), start, end, true /* throw if overflow */);
+      content, GetPath(uuid), start, end, true /* throw if overflow */);
 
     LOG(INFO) << "Read range of attachment \"" << uuid << "\" (" << timer.GetHumanTransferSpeed(true, content.size()) << ")";
     return StringMemoryBuffer::CreateFromSwap(content);
--- a/OrthancFramework/Sources/FileStorage/FilesystemStorage.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/FileStorage/FilesystemStorage.h	Tue Sep 09 15:35:30 2025 +0200
@@ -55,7 +55,7 @@
 
     boost::filesystem::path GetPath(const std::string& uuid) const;
 
-    void Setup(const std::string& root);
+    void Setup(const boost::filesystem::path& root);
     
 #if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1
     // Alias for binary compatibility with Orthanc Framework 1.7.2 => don't use it anymore
@@ -70,9 +70,9 @@
 #endif
 
   public:
-    explicit FilesystemStorage(const std::string& root);
+    explicit FilesystemStorage(const boost::filesystem::path& root);
 
-    FilesystemStorage(const std::string& root,
+    FilesystemStorage(const boost::filesystem::path& root,
                       bool fsyncOnWrite);
 
     virtual void Create(const std::string& uuid,
--- a/OrthancFramework/Sources/HttpClient.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/HttpClient.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -459,7 +459,7 @@
   private:
     boost::mutex    mutex_;
     bool            httpsVerifyPeers_;
-    std::string     httpsCACertificates_;
+    boost::filesystem::path  httpsCACertificates_;
     std::string     proxy_;
     long            timeout_;
     bool            verbose_;
@@ -480,7 +480,7 @@
     }
 
     void ConfigureSsl(bool httpsVerifyPeers,
-                      const std::string& httpsCACertificates)
+                      const boost::filesystem::path& httpsCACertificates)
     {
       boost::mutex::scoped_lock lock(mutex_);
       httpsVerifyPeers_ = httpsVerifyPeers;
@@ -488,7 +488,7 @@
     }
 
     void GetSslConfiguration(bool& httpsVerifyPeers,
-                             std::string& httpsCACertificates)
+                             boost::filesystem::path& httpsCACertificates)
     {
       boost::mutex::scoped_lock lock(mutex_);
       httpsVerifyPeers = httpsVerifyPeers_;
@@ -1179,19 +1179,19 @@
     return verifyPeers_;
   }
 
-  void HttpClient::SetHttpsCACertificates(const std::string &certificates)
+  void HttpClient::SetHttpsCACertificates(const boost::filesystem::path& certificates)
   {
     caCertificates_ = certificates;
   }
 
-  const std::string &HttpClient::GetHttpsCACertificates() const
+  const boost::filesystem::path& HttpClient::GetHttpsCACertificates() const
   {
     return caCertificates_;
   }
 
 
   void HttpClient::ConfigureSsl(bool httpsVerifyPeers,
-                                const std::string& httpsVerifyCertificates)
+                                const boost::filesystem::path& httpsVerifyCertificates)
   {
 #if ORTHANC_ENABLE_SSL == 1
     if (httpsVerifyPeers)
@@ -1337,8 +1337,8 @@
   }
 
 
-  void HttpClient::SetClientCertificate(const std::string& certificateFile,
-                                        const std::string& certificateKeyFile,
+  void HttpClient::SetClientCertificate(const boost::filesystem::path& certificateFile, 
+                                        const boost::filesystem::path &certificateKeyFile,
                                         const std::string& certificateKeyPassword)
   {
     if (certificateFile.empty())
@@ -1349,14 +1349,14 @@
     if (!SystemToolbox::IsRegularFile(certificateFile))
     {
       throw OrthancException(ErrorCode_InexistentFile,
-                             "Cannot open certificate file: " + certificateFile);
+                             "Cannot open certificate file: " + SystemToolbox::PathToUtf8(certificateFile));
     }
 
     if (!certificateKeyFile.empty() && 
         !SystemToolbox::IsRegularFile(certificateKeyFile))
     {
       throw OrthancException(ErrorCode_InexistentFile,
-                             "Cannot open key file: " + certificateKeyFile);
+                             "Cannot open key file: " + SystemToolbox::PathToUtf8(certificateKeyFile));
     }
 
     clientCertificateFile_ = certificateFile;
@@ -1374,17 +1374,17 @@
     return pkcs11Enabled_;
   }
 
-  const std::string &HttpClient::GetClientCertificateFile() const
+  const boost::filesystem::path& HttpClient::GetClientCertificateFile() const
   {
     return clientCertificateFile_;
   }
 
-  const std::string &HttpClient::GetClientCertificateKeyFile() const
+  const boost::filesystem::path& HttpClient::GetClientCertificateKeyFile() const
   {
     return clientCertificateKeyFile_;
   }
 
-  const std::string &HttpClient::GetClientCertificateKeyPassword() const
+  const std::string& HttpClient::GetClientCertificateKeyPassword() const
   {
     return clientCertificateKeyPassword_;
   }
--- a/OrthancFramework/Sources/HttpClient.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/HttpClient.h	Tue Sep 09 15:35:30 2025 +0200
@@ -31,6 +31,7 @@
 #include <string>
 #include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
+#include <boost/filesystem.hpp>
 #include <json/value.h>
 
 #if !defined(ORTHANC_ENABLE_CURL)
@@ -100,9 +101,9 @@
     long timeout_;
     std::string proxy_;
     bool verifyPeers_;
-    std::string caCertificates_;
-    std::string clientCertificateFile_;
-    std::string clientCertificateKeyFile_;
+    boost::filesystem::path caCertificates_;
+    boost::filesystem::path clientCertificateFile_;
+    boost::filesystem::path clientCertificateKeyFile_;
     std::string clientCertificateKeyPassword_;
     bool pkcs11Enabled_;
     bool headersToLowerCase_;
@@ -196,21 +197,21 @@
 
     bool IsHttpsVerifyPeers() const;
 
-    void SetHttpsCACertificates(const std::string& certificates);
+    void SetHttpsCACertificates(const boost::filesystem::path& certificates);
 
-    const std::string& GetHttpsCACertificates() const;
+    const boost::filesystem::path& GetHttpsCACertificates() const;
 
-    void SetClientCertificate(const std::string& certificateFile,
-                              const std::string& certificateKeyFile,
+    void SetClientCertificate(const boost::filesystem::path& certificateFile, 
+                              const boost::filesystem::path& certificateKeyFile,
                               const std::string& certificateKeyPassword);
 
     void SetPkcs11Enabled(bool enabled);
 
     bool IsPkcs11Enabled() const;
 
-    const std::string& GetClientCertificateFile() const;
+    const boost::filesystem::path& GetClientCertificateFile() const;
 
-    const std::string& GetClientCertificateKeyFile() const;
+    const boost::filesystem::path& GetClientCertificateKeyFile() const;
 
     const std::string& GetClientCertificateKeyPassword() const;
 
@@ -231,7 +232,7 @@
                                  bool verbose);
 
     static void ConfigureSsl(bool httpsVerifyPeers,
-                             const std::string& httpsCACertificates);
+                             const boost::filesystem::path& httpsCACertificates);
 
     static void SetDefaultVerbose(bool verbose);
 
--- a/OrthancFramework/Sources/HttpServer/FilesystemHttpSender.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/HttpServer/FilesystemHttpSender.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -25,6 +25,7 @@
 #include "FilesystemHttpSender.h"
 
 #include "../OrthancException.h"
+#include "../SystemToolbox.h"
 
 static const size_t  CHUNK_SIZE = 64 * 1024;   // Use 64KB chunks
 
@@ -33,7 +34,7 @@
   void FilesystemHttpSender::Initialize(const boost::filesystem::path& path)
   {
     SetContentFilename(path.filename().string());
-    file_.open(path.string().c_str(), std::ifstream::binary);
+    file_.open(SystemToolbox::PathToUtf8(path).c_str(), std::ifstream::binary);
 
     if (!file_.is_open())
     {
@@ -47,7 +48,7 @@
 
   FilesystemHttpSender::FilesystemHttpSender(const std::string& path)
   {
-    Initialize(path);
+    Initialize(SystemToolbox::PathFromUtf8(path));
   }
 
   FilesystemHttpSender::FilesystemHttpSender(const boost::filesystem::path& path)
@@ -62,6 +63,13 @@
     Initialize(path);
   }
 
+  FilesystemHttpSender::FilesystemHttpSender(const boost::filesystem::path& path, 
+                                             MimeType contentType)
+  {
+    SetContentType(contentType);
+    Initialize(path);
+  }
+
   FilesystemHttpSender::FilesystemHttpSender(const FilesystemStorage& storage,
                                              const std::string& uuid)
   {
--- a/OrthancFramework/Sources/HttpServer/FilesystemHttpSender.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/HttpServer/FilesystemHttpSender.h	Tue Sep 09 15:35:30 2025 +0200
@@ -49,6 +49,9 @@
     FilesystemHttpSender(const std::string& path,
                          MimeType contentType);
 
+    FilesystemHttpSender(const boost::filesystem::path& path, 
+                         MimeType contentType);
+
     FilesystemHttpSender(const FilesystemStorage& storage,
                          const std::string& uuid);
 
--- a/OrthancFramework/Sources/HttpServer/HttpServer.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpServer.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -32,6 +32,7 @@
 #include "../Logging.h"
 #include "../OrthancException.h"
 #include "../TemporaryFile.h"
+#include "../SystemToolbox.h"
 #include "HttpToolbox.h"
 #include "IHttpHandler.h"
 #include "MultipartStreamReader.h"
@@ -1575,7 +1576,7 @@
         catch (boost::filesystem::filesystem_error& e)
         {
           throw OrthancException(ErrorCode_InternalError,
-                                 "Error while accessing the filesystem: " + e.path1().string());
+                                 "Error while accessing the filesystem: " + SystemToolbox::PathToUtf8(e.path1()));
         }
         catch (std::runtime_error&)
         {
@@ -1806,7 +1807,7 @@
       {
         // Set the trusted client certificates (for X509 mutual authentication)
         options.push_back("ssl_ca_file");
-        options.push_back(trustedClientCertificates_.c_str());
+        options.push_back(SystemToolbox::PathToUtf8(trustedClientCertificates_).c_str());
       }
       
       if (ssl_)
@@ -1824,7 +1825,7 @@
 
         // Set the SSL certificate, if any
         options.push_back("ssl_certificate");
-        options.push_back(certificate_.c_str());
+        options.push_back(SystemToolbox::PathToUtf8(certificate_).c_str());
       };
 
       assert(options.size() % 2 == 0);
@@ -2067,7 +2068,7 @@
 #endif
   }
 
-  const std::string &HttpServer::GetSslCertificate() const
+  const boost::filesystem::path& HttpServer::GetSslCertificate() const
   {
     return certificate_;
   }
@@ -2084,7 +2085,7 @@
     return ssl_;
   }
 
-  void HttpServer::SetSslCertificate(const char* path)
+  void HttpServer::SetSslCertificate(const boost::filesystem::path& path)
   {
     Stop();
     certificate_ = path;
@@ -2095,7 +2096,7 @@
     return remoteAllowed_;
   }
 
-  void HttpServer::SetSslTrustedClientCertificates(const char* path)
+  void HttpServer::SetSslTrustedClientCertificates(const boost::filesystem::path &path)
   {
     Stop();
     trustedClientCertificates_ = path;
--- a/OrthancFramework/Sources/HttpServer/HttpServer.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpServer.h	Tue Sep 09 15:35:30 2025 +0200
@@ -57,6 +57,8 @@
 #include <set>
 #include <stdint.h>
 #include <boost/shared_ptr.hpp>
+#include <boost/filesystem.hpp>
+
 
 namespace Orthanc
 {
@@ -99,9 +101,9 @@
     bool remoteAllowed_;
     bool authentication_;
     bool sslVerifyPeers_;
-    std::string trustedClientCertificates_;
+    boost::filesystem::path trustedClientCertificates_;
     bool ssl_;
-    std::string certificate_;
+    boost::filesystem::path certificate_;
     unsigned int sslMinimumVersion_;
     bool sslHasCiphers_;
     std::string sslCiphers_;
@@ -160,7 +162,7 @@
 
     void SetSslCiphers(const std::list<std::string>& ciphers);
     
-    void SetSslTrustedClientCertificates(const char* path);
+    void SetSslTrustedClientCertificates(const boost::filesystem::path& path);
 
     bool IsKeepAliveEnabled() const;
 
@@ -170,9 +172,9 @@
 
     void SetKeepAliveTimeout(unsigned int timeout);
 
-    const std::string& GetSslCertificate() const;
+    const boost::filesystem::path& GetSslCertificate() const;
 
-    void SetSslCertificate(const char* path);
+    void SetSslCertificate(const boost::filesystem::path& path);
 
     bool IsRemoteAccessAllowed() const;
 
--- a/OrthancFramework/Sources/Images/JpegReader.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/Images/JpegReader.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -94,7 +94,7 @@
 
 
 #if ORTHANC_SANDBOXED == 0
-  void JpegReader::ReadFromFile(const std::string& filename)
+  void JpegReader::ReadFromFile(const boost::filesystem::path& filename)
   {
     FILE* fp = SystemToolbox::OpenFile(filename, FileMode_ReadBinary);
     if (!fp)
--- a/OrthancFramework/Sources/Images/JpegReader.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/Images/JpegReader.h	Tue Sep 09 15:35:30 2025 +0200
@@ -40,6 +40,11 @@
 
 #include <string>
 
+#if ORTHANC_SANDBOXED != 1
+#include <boost/filesystem.hpp>
+#endif
+
+
 namespace Orthanc
 {
   class ORTHANC_PUBLIC JpegReader : public ImageAccessor
@@ -49,7 +54,7 @@
 
   public:
 #if ORTHANC_SANDBOXED == 0
-    void ReadFromFile(const std::string& filename);
+    void ReadFromFile(const boost::filesystem::path& filename);
 #endif
 
     void ReadFromMemory(const void* buffer,
--- a/OrthancFramework/Sources/Images/PamReader.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/Images/PamReader.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -272,7 +272,7 @@
 
   
 #if ORTHANC_SANDBOXED == 0
-  void PamReader::ReadFromFile(const std::string& filename)
+  void PamReader::ReadFromFile(const boost::filesystem::path& filename)
   {
     SystemToolbox::ReadFile(content_, filename);
     ParseContent();
--- a/OrthancFramework/Sources/Images/PamReader.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/Images/PamReader.h	Tue Sep 09 15:35:30 2025 +0200
@@ -26,6 +26,10 @@
 
 #include "ImageAccessor.h"
 
+#if ORTHANC_SANDBOXED != 1
+#include <boost/filesystem.hpp>
+#endif
+
 #if !defined(ORTHANC_SANDBOXED)
 #  error The macro ORTHANC_SANDBOXED must be defined
 #endif
@@ -72,7 +76,7 @@
     virtual ~PamReader();
 
 #if ORTHANC_SANDBOXED == 0
-    void ReadFromFile(const std::string& filename);
+    void ReadFromFile(const boost::filesystem::path& filename);
 #endif
 
     void ReadFromMemory(const std::string& buffer);
--- a/OrthancFramework/Sources/Images/PngReader.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/Images/PngReader.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -44,7 +44,7 @@
     {
       FILE* fp_;
 
-      explicit FileRabi(const char* filename)
+      explicit FileRabi(const boost::filesystem::path& filename)
       {
         fp_ = SystemToolbox::OpenFile(filename, FileMode_ReadBinary);
         if (!fp_)
@@ -215,9 +215,9 @@
 
 
 #if ORTHANC_SANDBOXED == 0
-  void PngReader::ReadFromFile(const std::string& filename)
+  void PngReader::ReadFromFile(const boost::filesystem::path& filename)
   {
-    FileRabi f(filename.c_str());
+    FileRabi f(filename);
 
     char header[8];
     if (fread(header, 1, 8, f.fp_) != 8)
--- a/OrthancFramework/Sources/Images/PngReader.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/Images/PngReader.h	Tue Sep 09 15:35:30 2025 +0200
@@ -44,6 +44,11 @@
 #  error The macro ORTHANC_SANDBOXED must be defined
 #endif
 
+#if ORTHANC_SANDBOXED != 1
+#include <boost/filesystem.hpp>
+#endif
+
+
 namespace Orthanc
 {
   class ORTHANC_PUBLIC PngReader : public ImageAccessor
@@ -61,7 +66,7 @@
     PngReader();
 
 #if ORTHANC_SANDBOXED == 0
-    void ReadFromFile(const std::string& filename);
+    void ReadFromFile(const boost::filesystem::path& filename);
 #endif
 
     void ReadFromMemory(const void* buffer,
--- a/OrthancFramework/Sources/SharedLibrary.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/SharedLibrary.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -27,6 +27,7 @@
 
 #include "Logging.h"
 #include "OrthancException.h"
+#include "SystemToolbox.h"
 
 #include <boost/filesystem.hpp>
 
@@ -40,15 +41,15 @@
 
 namespace Orthanc
 {
-  SharedLibrary::SharedLibrary(const std::string& path) : 
+  SharedLibrary::SharedLibrary(const boost::filesystem::path& path) : 
     path_(path), 
     handle_(NULL)
   {
 #if defined(_WIN32)
-    handle_ = ::LoadLibraryA(path_.c_str());
+    handle_ = ::LoadLibraryW(path_.wstring().c_str());
     if (handle_ == NULL)
     {
-      LOG(ERROR) << "LoadLibrary(" << path_ << ") failed: Error " << ::GetLastError();
+      LOG(ERROR) << "LoadLibrary(" << SystemToolbox::PathToUtf8(path_) << ") failed: Error " << ::GetLastError();
 
       if (::GetLastError() == ERROR_BAD_EXE_FORMAT &&
           sizeof(void*) == 4)
@@ -120,7 +121,7 @@
   }
 
 
-  const std::string &SharedLibrary::GetPath() const
+  const boost::filesystem::path& SharedLibrary::GetPath() const
   {
     return path_;
   }
--- a/OrthancFramework/Sources/SharedLibrary.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/SharedLibrary.h	Tue Sep 09 15:35:30 2025 +0200
@@ -40,6 +40,7 @@
 
 #include <string>
 #include <boost/noncopyable.hpp>
+#include <boost/filesystem.hpp>
 
 namespace Orthanc
 {
@@ -53,17 +54,17 @@
 #endif
 
   private:
-    std::string path_;
+    boost::filesystem::path path_;
     void *handle_;
 
     FunctionPointer GetFunctionInternal(const std::string& name);
 
   public:
-    explicit SharedLibrary(const std::string& path);
+    explicit SharedLibrary(const boost::filesystem::path& path);
 
     ~SharedLibrary();
 
-    const std::string& GetPath() const;
+    const boost::filesystem::path& GetPath() const;
 
     bool HasFunction(const std::string& name);
 
--- a/OrthancFramework/Sources/SystemToolbox.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/SystemToolbox.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -222,24 +222,23 @@
 
 
   void SystemToolbox::ReadFile(std::string& content,
-                               const std::string& path,
+                               const boost::filesystem::path& path,
                                bool log)
   {
     if (!IsRegularFile(path))
     {
       throw OrthancException(ErrorCode_RegularFileExpected,
-                             "The path does not point to a regular file: " + path,
+                             "The path does not point to a regular file: " + PathToUtf8(path),
                              log);
     }
 
     try
     {
       boost::filesystem::ifstream f;
-      f.open(PathFromUtf8(path), std::ifstream::in | std::ifstream::binary);
+      f.open(path, std::ifstream::in | std::ifstream::binary);
       if (!f.good())
       {
-        throw OrthancException(ErrorCode_InexistentFile,
-                               "File not found: " + path,
+        throw OrthancException(ErrorCode_InexistentFile, "File not found: " + PathToUtf8(path),
                                log);
       }
 
@@ -269,27 +268,51 @@
     }
   }
 
+  void SystemToolbox::ReadFile(std::string &content, 
+                               const std::string& path, 
+                               bool log) 
+  {
+    ReadFile(content, PathFromUtf8(path), log);
+  }
 
-  void SystemToolbox::ReadFile(std::string &content, const std::string &path)
-  {
+  void SystemToolbox::ReadFile(std::string& content, const boost::filesystem::path& path)
+  { 
     ReadFile(content, path, true /* log */);
   }
 
 
+  void SystemToolbox::ReadFile(std::string &content, const std::string &path)
+  {
+    ReadFile(content, PathFromUtf8(path), true /* log */);
+  }
+
+  void SystemToolbox::ReadFile(std::string &content, const char* path) 
+  { 
+    ReadFile(content, std::string(path)); 
+   }
+
+  bool SystemToolbox::ReadHeader(std::string& header, 
+                                 const std::string& path, 
+                                 size_t headerSize)
+  {
+    return ReadHeader(header, PathFromUtf8(path), headerSize);
+  }
+
+
   bool SystemToolbox::ReadHeader(std::string& header,
-                                 const std::string& path,
+                                 const boost::filesystem::path& path,
                                  size_t headerSize)
   {
     if (!IsRegularFile(path))
     {
       throw OrthancException(ErrorCode_RegularFileExpected,
-                             "The path does not point to a regular file: " + path);
+                             "The path does not point to a regular file: " + PathToUtf8(path));
     }
 
     try
     {
       boost::filesystem::ifstream f;
-      f.open(PathFromUtf8(path), std::ifstream::in | std::ifstream::binary);
+      f.open(path, std::ifstream::in | std::ifstream::binary);
       if (!f.good())
       {
         throw OrthancException(ErrorCode_InexistentFile);
@@ -423,12 +446,27 @@
               content.size(), path, callFsync);
   }
 
+  void SystemToolbox::WriteFile(const std::string &content, 
+                                const boost::filesystem::path& path, 
+                                bool callFsync) 
+  { 
+    WriteFile(content.size() > 0 ? content.c_str() : NULL, 
+              content.size(), path, callFsync); 
+  }
 
   void SystemToolbox::WriteFile(const std::string &content, const std::string &path)
   {
     WriteFile(content, path, false /* don't automatically call fsync */);
   }
 
+  
+  void SystemToolbox::WriteFile(const std::string &content, 
+                                const boost::filesystem::path& path) 
+  { 
+    WriteFile(content.size() > 0 ? content.c_str() : NULL, 
+              content.size(), path, false /* don't automatically call fsync */); 
+  }
+
 
   void SystemToolbox::RemoveFile(const std::string& path)
   {
@@ -537,6 +575,11 @@
 
   void SystemToolbox::MakeDirectory(const std::string& path)
   {
+    MakeDirectory(PathFromUtf8(path));
+  }
+
+  void SystemToolbox::MakeDirectory(const boost::filesystem::path& path)
+  {
     if (boost::filesystem::exists(path))
     {
       if (!boost::filesystem::is_directory(path))
@@ -633,7 +676,7 @@
 
 
 #ifdef _WIN32
-  std::wstring Utf8ToWString(const std::string &str)
+  std::wstring SystemToolbox::Utf8ToWString(const std::string &str)
   {
     if (str.empty())
     {
@@ -648,7 +691,7 @@
     return wstr;
   }
 
-  std::string WStringToUtf8(const std::wstring &wstr)
+  std::string SystemToolbox::WStringToUtf8(const std::wstring &wstr)
   {
     if (wstr.empty())
     {
@@ -663,12 +706,27 @@
     return str;
   }
 
+  std::wstring SystemToolbox::WStringFromCharPtr(const char *str)
+  {
+    if (str == NULL)
+    {
+      return std::wstring();
+    }
+
+    int sizeNeeded = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
+
+    std::wstring wstr(sizeNeeded, 0);
+    MultiByteToWideChar(CP_UTF8, 0, str, -1, &wstr[0], sizeNeeded);
+
+    return wstr;
+
+  }
 #endif
 
     boost::filesystem::path SystemToolbox::PathFromUtf8(const std::string &utf8)
   {
-#ifdef _WIN32
-    return boost::filesystem::path(Utf8ToWString(utf8));
+#if defined(_WIN32) && !defined(__MINGW32__)  // non-ASCII paths are not supported when building with MinGW
+return boost::filesystem::path(Utf8ToWString(utf8));
 #else
     return boost::filesystem::path(utf8); // POSIX: std::string is UTF-8
 #endif
@@ -676,7 +734,7 @@
 
   std::string SystemToolbox::PathToUtf8(const boost::filesystem::path &p)
   {
-#ifdef _WIN32
+#if defined(_WIN32) && !defined(__MINGW32__)  // non-ASCII paths are not supported when building with MinGW
     return WStringToUtf8(p.wstring());
 #else
     return p.string(); // POSIX: already UTF-8
@@ -785,14 +843,34 @@
   }
 
 
-  FILE* SystemToolbox::OpenFile(const std::string& path,
+  FILE *SystemToolbox::OpenFile(const std::string &path, 
+                                FileMode mode)
+  {
+    return OpenFile(PathFromUtf8(path), mode);
+  }
+  
+  FILE* SystemToolbox::OpenFile(const boost::filesystem::path& path,
                                 FileMode mode)
   {
 #if defined(_WIN32)
-    // TODO Deal with special characters by converting to the current locale
-#endif
+    const wchar_t *m;
+    switch (mode)
+    {
+      case FileMode_ReadBinary:
+        m = L"rb";
+        break;
 
-    const char* m;
+      case FileMode_WriteBinary:
+        m = L"wb";
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    return _wfopen(path.wstring().c_str(), m);
+#else
+    const char *m;
     switch (mode)
     {
       case FileMode_ReadBinary:
@@ -808,6 +886,7 @@
     }
 
     return fopen(path.c_str(), m);
+#endif
   }
 
 
@@ -1056,16 +1135,15 @@
   }
 
 
-  std::string SystemToolbox::InterpretRelativePath(const std::string& baseDirectory,
-                                                   const std::string& relativePath)
+  boost::filesystem::path SystemToolbox::InterpretRelativePath(const boost::filesystem::path& baseDirectory,
+                                                               const std::string& relativePath)
   {
-    boost::filesystem::path base(baseDirectory);
-    boost::filesystem::path relative(relativePath);
+    boost::filesystem::path relative = SystemToolbox::PathFromUtf8(relativePath);
 
     /**
        The following lines should be equivalent to this one: 
 
-       return (base / relative).string();
+       return (base / relative);
 
        However, for some unknown reason, some versions of Boost do not
        make the proper path resolution when "baseDirectory" is an
@@ -1074,17 +1152,27 @@
 
     if (relative.is_absolute())
     {
-      return relative.string();
+      return relative;
     }
     else
     {
-      return (base / relative).string();
+      return baseDirectory / relative;
     }
   }
 
 
+  void SystemToolbox::ReadFileRange(std::string &content, 
+                                    const std::string &path,
+                                    uint64_t start, // Inclusive
+                                    uint64_t end, // Exclusive
+                                    bool throwIfOverflow)
+  {
+    ReadFileRange(content, SystemToolbox::PathFromUtf8(path), start, end, throwIfOverflow);
+  }
+
+
   void SystemToolbox::ReadFileRange(std::string& content,                              
-                                    const std::string& path,
+                                    const boost::filesystem::path& path,
                                     uint64_t start,  // Inclusive
                                     uint64_t end,    // Exclusive
                                     bool throwIfOverflow)
@@ -1097,15 +1185,15 @@
     if (!IsRegularFile(path))
     {
       throw OrthancException(ErrorCode_RegularFileExpected,
-                             "The path does not point to a regular file: " + path);
+                             "The path does not point to a regular file: " + SystemToolbox::PathToUtf8(path));
     }
 
     boost::filesystem::ifstream f;
-    f.open(PathFromUtf8(path), std::ifstream::in | std::ifstream::binary);
+    f.open(path, std::ifstream::in | std::ifstream::binary);
     if (!f.good())
     {
-      throw OrthancException(ErrorCode_InexistentFile,
-                             "File not found: " + path);
+      throw OrthancException(ErrorCode_InexistentFile, 
+                             "File not found: " + SystemToolbox::PathToUtf8(path));
     }
 
     uint64_t fileSize = static_cast<uint64_t>(GetStreamSize(f));
--- a/OrthancFramework/Sources/SystemToolbox.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/SystemToolbox.h	Tue Sep 09 15:35:30 2025 +0200
@@ -60,14 +60,28 @@
     static void ReadFile(std::string& content,
                          const std::string& path,
                          bool log);
+    
+    static void ReadFile(std::string &content, 
+                         const boost::filesystem::path& path, 
+                         bool log);
+
+    static void ReadFile(std::string &content, 
+                         const char* path);
 
     static void ReadFile(std::string& content,
                          const std::string& path);
 
+    static void ReadFile(std::string &content, 
+                         const boost::filesystem::path& path);
+
     static bool ReadHeader(std::string& header,
                            const std::string& path,
                            size_t headerSize);
 
+   static bool ReadHeader(std::string &header, 
+                          const boost::filesystem::path& path, 
+                          size_t headerSize);
+
     static void WriteFile(const void* content,
                           size_t size,
                           const std::string& path,
@@ -89,6 +103,13 @@
     static void WriteFile(const std::string& content,
                           const std::string& path);
 
+    static void WriteFile(const std::string &content, 
+                          const boost::filesystem::path& path);
+
+    static void WriteFile(const std::string &content, 
+                          const boost::filesystem::path& path, 
+                          bool callFsync);
+
     static void RemoveFile(const std::string& path);
 
     static uint64_t GetFileSize(const std::string& path);
@@ -115,6 +136,8 @@
 
     static void MakeDirectory(const std::string& path);
 
+    static void MakeDirectory(const boost::filesystem::path& path);
+
     static bool IsExistingFile(const std::string& path);
 
     static std::string GetPathToExecutable();
@@ -133,6 +156,9 @@
     static FILE* OpenFile(const std::string& path,
                           FileMode mode);
 
+    static FILE* OpenFile(const boost::filesystem::path& path, 
+                          FileMode mode);
+
     static MimeType AutodetectMimeType(const char* path)  // used only in Unit Tests
     { 
       return AutodetectMimeType(std::string(path));
@@ -160,8 +186,8 @@
 
     static void GetEnvironmentVariables(std::map<std::string, std::string>& env);
 
-    static std::string InterpretRelativePath(const std::string& baseDirectory,
-                                             const std::string& relativePath);
+    static boost::filesystem::path InterpretRelativePath(const boost::filesystem::path& baseDirectory,
+                                                         const std::string& relativePath);
 
     static void ReadFileRange(std::string& content,                              
                               const std::string& path,
@@ -169,6 +195,21 @@
                               uint64_t end,    // Exclusive
                               bool throwIfOverflow);
 
+    static void ReadFileRange(std::string &content, 
+                              const boost::filesystem::path& path,
+                              uint64_t start, // Inclusive
+                              uint64_t end, // Exclusive
+                              bool throwIfOverflow);
+
     static void GetMacAddresses(std::set<std::string>& target);
+
+#ifdef _WIN32
+    static std::wstring Utf8ToWString(const std::string &str);
+
+    static std::string WStringToUtf8(const std::wstring &wstr);
+
+    static std::wstring WStringFromCharPtr(const char *wstr);
+#endif
+
   };
 }
--- a/OrthancFramework/Sources/TemporaryFile.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/TemporaryFile.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -34,12 +34,12 @@
 
 namespace Orthanc
 {
-  static std::string CreateTemporaryPath(const char* temporaryDirectory,
-                                         const char* extension)
+  static boost::filesystem::path CreateTemporaryPath(const boost::filesystem::path& temporaryDirectory,
+                                                     const char* extension)
   {
     boost::filesystem::path dir;
 
-    if (temporaryDirectory == NULL)
+    if (temporaryDirectory.empty())
     {
 #if BOOST_HAS_FILESYSTEM_V3 == 1
       dir = boost::filesystem::temp_directory_path();
@@ -69,19 +69,19 @@
     }
 
     dir /= filename;
-    return dir.string();
+    return dir;
   }
 
 
   TemporaryFile::TemporaryFile() : 
-    path_(CreateTemporaryPath(NULL, NULL))
+    path_(CreateTemporaryPath("", NULL))
   {
   }
 
 
-  TemporaryFile::TemporaryFile(const std::string& temporaryDirectory,
+  TemporaryFile::TemporaryFile(const boost::filesystem::path& temporaryDirectory,
                                const std::string& extension) :
-    path_(CreateTemporaryPath(temporaryDirectory.c_str(), extension.c_str()))
+    path_(CreateTemporaryPath(temporaryDirectory, extension.c_str()))
   {
   }
 
@@ -91,7 +91,7 @@
     boost::filesystem::remove(path_);
   }
 
-  const std::string &TemporaryFile::GetPath() const
+  const boost::filesystem::path& TemporaryFile::GetPath() const
   {
     return path_;
   }
@@ -106,7 +106,7 @@
     catch (OrthancException& e)
     {
       throw OrthancException(e.GetErrorCode(),
-                             "Can't create temporary file \"" + path_ +
+                             "Can't create temporary file \"" + SystemToolbox::PathToUtf8(path_) +
                              "\" with " + boost::lexical_cast<std::string>(content.size()) +
                              " bytes: Check you have write access to the "
                              "temporary directory and that it is not full");
@@ -122,8 +122,8 @@
     }
     catch (OrthancException& e)
     {
-      throw OrthancException(e.GetErrorCode(),
-                             "Can't read temporary file \"" + path_ +
+      throw OrthancException(e.GetErrorCode(), 
+                             "Can't read temporary file \"" + SystemToolbox::PathToUtf8(path_) +
                              "\": Another process has corrupted the temporary directory");
     }
   }
--- a/OrthancFramework/Sources/TemporaryFile.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/Sources/TemporaryFile.h	Tue Sep 09 15:35:30 2025 +0200
@@ -35,6 +35,7 @@
 #endif
 
 #include <boost/noncopyable.hpp>
+#include <boost/filesystem.hpp>
 #include <stdint.h>
 #include <string>
 
@@ -43,17 +44,17 @@
   class ORTHANC_PUBLIC TemporaryFile : public boost::noncopyable
   {
   private:
-    std::string path_;
+    boost::filesystem::path path_;
 
   public:
     TemporaryFile();
 
-    TemporaryFile(const std::string& temporaryFolder,
+    TemporaryFile(const boost::filesystem::path& temporaryFolder,
                   const std::string& extension);
 
     ~TemporaryFile();
 
-    const std::string& GetPath() const;
+    const boost::filesystem::path& GetPath() const;
 
     void Write(const std::string& content);
 
--- a/OrthancFramework/UnitTestsSources/FileStorageTests.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/UnitTestsSources/FileStorageTests.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -108,7 +108,7 @@
   std::vector<uint8_t> data;
   StringToVector(data, Toolbox::GenerateUuid());
 
-  SystemToolbox::WriteFile("toto", "UnitTestsStorageTop/12");
+  SystemToolbox::WriteFile("toto", SystemToolbox::PathFromUtf8("UnitTestsStorageTop/12"));
   ASSERT_THROW(s.Create("12345678-1234-1234-1234-1234567890ab", &data[0], data.size(), FileContentType_Unknown), OrthancException);
   s.Clear();
 }
@@ -121,8 +121,8 @@
   std::vector<uint8_t> data;
   StringToVector(data, Toolbox::GenerateUuid());
 
-  SystemToolbox::MakeDirectory("UnitTestsStorageChild/12");
-  SystemToolbox::WriteFile("toto", "UnitTestsStorageChild/12/34");
+  SystemToolbox::MakeDirectory(SystemToolbox::PathFromUtf8("UnitTestsStorageChild/12"));
+  SystemToolbox::WriteFile("toto", SystemToolbox::PathFromUtf8("UnitTestsStorageChild/12/34"));
   ASSERT_THROW(s.Create("12345678-1234-1234-1234-1234567890ab", &data[0], data.size(), FileContentType_Unknown), OrthancException);
   s.Clear();
 }
@@ -135,12 +135,27 @@
   std::vector<uint8_t> data;
   StringToVector(data, Toolbox::GenerateUuid());
 
-  SystemToolbox::MakeDirectory("UnitTestsStorageFileAlreadyExists/12/34");
-  SystemToolbox::WriteFile("toto", "UnitTestsStorageFileAlreadyExists/12/34/12345678-1234-1234-1234-1234567890ab");
+  SystemToolbox::MakeDirectory(SystemToolbox::PathFromUtf8("UnitTestsStorageFileAlreadyExists/12/34"));
+  SystemToolbox::WriteFile("toto", SystemToolbox::PathFromUtf8("UnitTestsStorageFileAlreadyExists/12/34/12345678-1234-1234-1234-1234567890ab"));
   ASSERT_THROW(s.Create("12345678-1234-1234-1234-1234567890ab", &data[0], data.size(), FileContentType_Unknown), OrthancException);
   s.Clear();
 }
 
+#if !defined(__MINGW32__)  // non-ASCII paths are not supported when built with mingw
+TEST(FilesystemStorage, FileAlreadyExistsUtf8)
+{
+  FilesystemStorage s(SystemToolbox::PathFromUtf8("\xd0\x95UnitTestsStorageFileAlreadyExists"));
+  s.Clear();
+
+  std::vector<uint8_t> data;
+  StringToVector(data, Toolbox::GenerateUuid());
+
+  SystemToolbox::MakeDirectory(SystemToolbox::PathFromUtf8("\xd0\x95UnitTestsStorageFileAlreadyExists/12/34"));
+  SystemToolbox::WriteFile("toto", SystemToolbox::PathFromUtf8("\xd0\x95UnitTestsStorageFileAlreadyExists/12/34/12345678-1234-1234-1234-1234567890ab"));
+  ASSERT_THROW(s.Create("12345678-1234-1234-1234-1234567890ab", &data[0], data.size(), FileContentType_Unknown), OrthancException);
+  s.Clear();
+}
+#endif
 
 TEST(FilesystemStorage, EndToEnd)
 {
--- a/OrthancFramework/UnitTestsSources/FrameworkTests.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/UnitTestsSources/FrameworkTests.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -642,8 +642,8 @@
 #if defined(__linux__)
 TEST(Toolbox, AbsoluteDirectory)
 {
-  ASSERT_EQ("/tmp/hello", SystemToolbox::InterpretRelativePath("/tmp", "hello"));
-  ASSERT_EQ("/tmp", SystemToolbox::InterpretRelativePath("/tmp", "/tmp"));
+  ASSERT_EQ("/tmp/hello", SystemToolbox::PathToUtf8(SystemToolbox::InterpretRelativePath("/tmp", "hello")));
+  ASSERT_EQ("/tmp", SystemToolbox::PathToUtf8(SystemToolbox::InterpretRelativePath("/tmp", "/tmp")));
 }
 #endif
 
@@ -651,7 +651,7 @@
 #if ORTHANC_SANDBOXED != 1
 TEST(Toolbox, WriteFile)
 {
-  std::string path;
+  boost::filesystem::path path;
 
   {
     TemporaryFile tmp;
@@ -663,28 +663,28 @@
     s.append("World");
     ASSERT_EQ(11u, s.size());
 
-    SystemToolbox::WriteFile(s, path.c_str());
+    SystemToolbox::WriteFile(s, path);
 
     std::string t;
-    SystemToolbox::ReadFile(t, path.c_str());
+    SystemToolbox::ReadFile(t, path);
 
     ASSERT_EQ(11u, t.size());
     ASSERT_EQ(0, t[5]);
     ASSERT_EQ(0, memcmp(s.c_str(), t.c_str(), s.size()));
 
     std::string h;
-    ASSERT_EQ(true, SystemToolbox::ReadHeader(h, path.c_str(), 1));
+    ASSERT_EQ(true, SystemToolbox::ReadHeader(h, path, 1));
     ASSERT_EQ(1u, h.size());
     ASSERT_EQ('H', h[0]);
-    ASSERT_TRUE(SystemToolbox::ReadHeader(h, path.c_str(), 0));
+    ASSERT_TRUE(SystemToolbox::ReadHeader(h, path, 0));
     ASSERT_EQ(0u, h.size());
-    ASSERT_FALSE(SystemToolbox::ReadHeader(h, path.c_str(), 32));
+    ASSERT_FALSE(SystemToolbox::ReadHeader(h, path, 32));
     ASSERT_EQ(11u, h.size());
     ASSERT_EQ(0, memcmp(s.c_str(), h.c_str(), s.size()));
   }
 
   std::string u;
-  ASSERT_THROW(SystemToolbox::ReadFile(u, path.c_str()), OrthancException);
+  ASSERT_THROW(SystemToolbox::ReadFile(u, path), OrthancException);
 
   {
     TemporaryFile tmp;
--- a/OrthancFramework/UnitTestsSources/ImageTests.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/UnitTestsSources/ImageTests.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -310,7 +310,7 @@
 
 #if ORTHANC_SANDBOXED != 1
     Orthanc::IImageWriter::WriteToFile(w, "UnitTestsResults/hello.jpg", img);
-    Orthanc::SystemToolbox::WriteFile(s, "UnitTestsResults/hello2.jpg");
+    Orthanc::SystemToolbox::WriteFile(s, Orthanc::SystemToolbox::PathFromUtf8("UnitTestsResults/hello2.jpg"));
 
     std::string t;
     Orthanc::SystemToolbox::ReadFile(t, "UnitTestsResults/hello.jpg");
@@ -327,7 +327,7 @@
 
 #if ORTHANC_SANDBOXED != 1
     Orthanc::JpegReader r2;
-    r2.ReadFromFile("UnitTestsResults/hello.jpg");
+    r2.ReadFromFile(Orthanc::SystemToolbox::PathFromUtf8("UnitTestsResults/hello.jpg"));
     ASSERT_EQ(16u, r2.GetWidth());
     ASSERT_EQ(16u, r2.GetHeight());
 #endif
--- a/OrthancFramework/UnitTestsSources/RestApiTests.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/UnitTestsSources/RestApiTests.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -117,7 +117,7 @@
 
 TEST(HttpClient, Ssl)
 {
-  SystemToolbox::WriteFile(GITHUB_CERTIFICATES, "UnitTestsResults/github.cert");
+  SystemToolbox::WriteFile(GITHUB_CERTIFICATES, SystemToolbox::PathFromUtf8("UnitTestsResults/github.cert"));
 
   /*{
     std::string s;
--- a/OrthancFramework/UnitTestsSources/ZipTests.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancFramework/UnitTestsSources/ZipTests.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -194,7 +194,7 @@
     Orthanc::ZipWriter w;
     ASSERT_EQ(0u, w.GetArchiveSize());
 
-    w.SetOutputPath(f.GetPath().c_str());
+    w.SetOutputPath(f.GetPath());
     w.Open();
     w.OpenFile("world/hello");
     w.Write("Hello world");
@@ -207,7 +207,7 @@
   std::unique_ptr<ZipReader> reader(ZipReader::CreateFromFile(f.GetPath()));
 
   ASSERT_EQ(1u, reader->GetFilesCount());
-  
+
   std::string filename, content;
   ASSERT_TRUE(reader->ReadNextFile(filename, content));
   ASSERT_EQ("world/hello", filename);
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -1758,8 +1758,7 @@
     boost::shared_mutex auditLogHandlersMutex_;        // New in Orthanc 1.12.9
 
     Properties properties_;
-    int argc_;
-    char** argv_;
+    std::vector<std::string> arguments_;
     std::unique_ptr<OrthancPluginDatabase>    database_;
     std::unique_ptr<OrthancPluginDatabaseV3>  databaseV3_;  // New in Orthanc 1.9.2
     std::unique_ptr<OrthancPluginDatabaseV4>  databaseV4_;  // New in Orthanc 1.12.0
@@ -1775,8 +1774,6 @@
       worklistCallback_(NULL),
       receivedInstanceCallback_(NULL),
       httpAuthentication_(NULL),
-      argc_(1),
-      argv_(NULL),
       databaseServerIdentifier_(databaseServerIdentifier),
       maxDatabaseRetries_(0),
       hasStorageAreaCustomData_(false)
@@ -5223,7 +5220,7 @@
           *reinterpret_cast<const _OrthancPluginReadFile*>(parameters);
 
         std::string content;
-        SystemToolbox::ReadFile(content, p.path);
+        SystemToolbox::ReadFile(content, SystemToolbox::PathFromUtf8(p.path));
         CopyToMemoryBuffer(p.target, content);
 
         return true;
@@ -6084,7 +6081,7 @@
       {
         const _OrthancPluginReturnSingleValue& p =
           *reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters);
-        *(p.resultUint32) = pimpl_->argc_ - 1;
+        *(p.resultUint32) = static_cast<uint32_t>(pimpl_->arguments_.size()) - 1;
         return true;
       }
 
@@ -6093,14 +6090,13 @@
         const _OrthancPluginGlobalProperty& p =
           *reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters);
         
-        if (p.property + 1 > pimpl_->argc_)
+        if (p.property + 1 > static_cast<int32_t>(pimpl_->arguments_.size()))
         {
           return false;
         }
         else
         {
-          std::string arg = std::string(pimpl_->argv_[p.property + 1]);
-          *(p.result) = CopyString(arg);
+          *(p.result) = CopyString(pimpl_->arguments_[p.property + 1]);
           return true;
         }
       }
@@ -6430,15 +6426,14 @@
   }
 
 
-  void OrthancPlugins::SetCommandLineArguments(int argc, char* argv[])
-  {
-    if (argc < 1 || argv == NULL)
+  void OrthancPlugins::SetCommandLineArguments(const std::vector<std::string>& arguments)
+  {
+    if (arguments.size() == 0)
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    pimpl_->argc_ = argc;
-    pimpl_->argv_ = argv;
+    pimpl_->arguments_ = arguments;
   }
 
 
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h	Tue Sep 09 15:35:30 2025 +0200
@@ -338,7 +338,7 @@
     const char* GetProperty(const char* plugin,
                             _OrthancPluginProperty property) const;
 
-    void SetCommandLineArguments(int argc, char* argv[]);
+    void SetCommandLineArguments(const std::vector<std::string>& arguments);
 
     PluginsManager& GetManager();
 
--- a/OrthancServer/Plugins/Engine/PluginsManager.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsManager.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -32,6 +32,7 @@
 #include "../../../OrthancFramework/Sources/Logging.h"
 #include "../../../OrthancFramework/Sources/OrthancException.h"
 #include "../../../OrthancFramework/Sources/Toolbox.h"
+#include "../../../OrthancFramework/Sources/SystemToolbox.h"
 
 #include <cassert>
 #include <memory>
@@ -51,7 +52,7 @@
 namespace Orthanc
 {
   PluginsManager::Plugin::Plugin(PluginsManager& pluginManager,
-                                 const std::string& path) : 
+                                 const boost::filesystem::path& path) : 
     library_(path),
     pluginManager_(pluginManager)
   {
@@ -240,23 +241,22 @@
   }
 
   
-  void PluginsManager::RegisterPlugin(const std::string& path)
+  void PluginsManager::RegisterPlugin(const boost::filesystem::path& path)
   {
     if (!boost::filesystem::exists(path))
     {
-      boost::filesystem::path p(path);
-      std::string extension = p.extension().string();
+      std::string extension = path.extension().string();
       Toolbox::ToLowerCase(extension);
 
       if (extension == PLUGIN_EXTENSION)
       { 
         // if this is a plugin path, fail to start
-        throw OrthancException(ErrorCode_SharedLibrary, "Inexistent path to plugin: " + path);
+        throw OrthancException(ErrorCode_SharedLibrary, "Inexistent path to plugin: " + SystemToolbox::PathToUtf8(path));
       }
       else
       { 
         // it might be a directory -> just log a warning
-        LOG(WARNING) << "Inexistent path to plugins: " << path;
+        LOG(WARNING) << "Inexistent path to plugins: " << SystemToolbox::PathToUtf8(path);
         return;
       }
     }
@@ -293,7 +293,7 @@
   }
 
 
-  void PluginsManager::ScanFolderForPlugins(const std::string& folder,
+  void PluginsManager::ScanFolderForPlugins(const boost::filesystem::path& folder,
                                             bool isRecursive)
   {
     using namespace boost::filesystem;
@@ -303,14 +303,14 @@
       return;
     }
 
-    CLOG(INFO, PLUGINS) << "Scanning folder " << folder << " for plugins";
+    CLOG(INFO, PLUGINS) << "Scanning folder " << SystemToolbox::PathToUtf8(folder) << " for plugins";
 
     directory_iterator end_it; // default construction yields past-the-end
     for (directory_iterator it(folder);
           it != end_it;
           ++it)
     {
-      std::string path = it->path().string();
+      boost::filesystem::path path = it->path();
 
       if (is_directory(it->status()))
       {
--- a/OrthancServer/Plugins/Engine/PluginsManager.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsManager.h	Tue Sep 09 15:35:30 2025 +0200
@@ -29,6 +29,7 @@
 
 #include <map>
 #include <list>
+#include <boost/filesystem.hpp>
 
 namespace Orthanc
 {
@@ -45,7 +46,7 @@
 
     public:
       Plugin(PluginsManager& pluginManager,
-             const std::string& path);
+             const boost::filesystem::path& path);
 
       SharedLibrary& GetSharedLibrary()
       {
@@ -87,9 +88,9 @@
 
     ~PluginsManager();
 
-    void RegisterPlugin(const std::string& path);
+    void RegisterPlugin(const boost::filesystem::path& path);
 
-    void ScanFolderForPlugins(const std::string& path,
+    void ScanFolderForPlugins(const boost::filesystem::path& path,
                               bool isRecursive);
 
     void RegisterServiceProvider(IPluginServiceProvider& provider)
--- a/OrthancServer/Plugins/Samples/AdoptDicomInstance/Plugin.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancServer/Plugins/Samples/AdoptDicomInstance/Plugin.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -32,7 +32,7 @@
 static boost::filesystem::path storageDirectory_;
 
 
-static std::string GetStorageDirectoryPath(const char* uuid)
+static boost::filesystem::path GetStorageDirectoryPath(const char* uuid)
 {
   if (uuid == NULL)
   {
@@ -40,7 +40,7 @@
   }
   else
   {
-    return (storageDirectory_ / std::string(uuid)).string();
+    return (storageDirectory_ / std::string(uuid));
   }
 }
 
--- a/OrthancServer/Resources/Samples/Tools/RecoverCompressedFile.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancServer/Resources/Samples/Tools/RecoverCompressedFile.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -27,12 +27,43 @@
 
 #include <stdio.h>
 
-int main(int argc, const char* argv[])
+#if defined(_WIN32) || defined(__CYGWIN__)
+#include <windows.h>
+#endif
+
+
+#if defined(_WIN32) && !defined(__MINGW32__)
+// arguments are passed as UTF-16 on Windows
+int wmain(int argc, wchar_t *argv[])
 {
-  if (argc != 2 && argc != 3)
+  // Set Windows console output to UTF-8 (otherwise, strings are considered to be in UTF-16.  For example, Cyrillic UTF-8 strings appear as garbage without that config)
+  SetConsoleOutputCP(CP_UTF8);
+
+  // Transform the UTF-16 arguments into UTF-8 arguments
+  std::vector<std::string> arguments; // UTF-8 arguments
+
+  for (int i = 0; i < argc; i++)
+  {
+    std::wstring argument(argv[i]);
+    arguments.push_back(Orthanc::SystemToolbox::WStringToUtf8(argument));
+  }
+
+#else
+int main(int argc, char *argv[])
+{
+  std::vector<std::string> arguments; // UTF-8 arguments
+
+  // the arguments are assumed to be directly in UTF-8
+  for (int i = 0; i < argc; i++)
+  {
+    arguments.push_back(argv[i]);
+  }
+#endif
+
+  if (arguments.size() != 2 && arguments.size() != 3)
   {
     fprintf(stderr, "Maintenance tool to recover a DICOM file that was compressed by Orthanc.\n\n");
-    fprintf(stderr, "Usage: %s <input> [output]\n", argv[0]);
+    fprintf(stderr, "Usage: %s <input> [output]\n", Orthanc::SystemToolbox::PathToUtf8(arguments[0]).c_str());
     fprintf(stderr, "If \"output\" is not given, the data will be output to stdout\n");
     return -1;
   }
@@ -43,7 +74,7 @@
     fflush(stderr);
 
     std::string content;
-    Orthanc::SystemToolbox::ReadFile(content, argv[1]);
+    Orthanc::SystemToolbox::ReadFile(content, Orthanc::SystemToolbox::PathFromUtf8(arguments[1]));
 
     fprintf(stderr, "Decompressing the content of the file...\n");
     fflush(stderr);
@@ -59,7 +90,7 @@
 
     if (argc == 3)
     {
-      Orthanc::SystemToolbox::WriteFile(uncompressed, argv[2]);
+      Orthanc::SystemToolbox::WriteFile(uncompressed, Orthanc::SystemToolbox::PathFromUtf8(arguments[2]));
     }
     else
     {
--- a/OrthancServer/Sources/LuaScripting.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancServer/Sources/LuaScripting.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -1096,8 +1096,8 @@
     for (std::list<std::string>::const_iterator
            it = luaScripts.begin(); it != luaScripts.end(); ++it)
     {
-      std::string path = configLock.GetConfiguration().InterpretStringParameterAsPath(*it);
-      LOG(INFO) << "Installing the Lua scripts from: " << path;
+      boost::filesystem::path path = configLock.GetConfiguration().InterpretStringParameterAsPath(*it);
+      LOG(INFO) << "Installing the Lua scripts from: " << SystemToolbox::PathToUtf8(path);
       std::string script;
       SystemToolbox::ReadFile(script, path);
 
--- a/OrthancServer/Sources/OrthancConfiguration.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancServer/Sources/OrthancConfiguration.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -56,13 +56,13 @@
     std::map<std::string, std::string> env;
     SystemToolbox::GetEnvironmentVariables(env);
     
-    LOG(WARNING) << "Reading the configuration from: " << path;
+    LOG(WARNING) << "Reading the configuration from: " << SystemToolbox::PathToUtf8(path);
 
     Json::Value config;
 
     {
       std::string content;
-      SystemToolbox::ReadFile(content, path.string());
+      SystemToolbox::ReadFile(content, path);
 
       content = Toolbox::SubstituteVariables(content, env);
 
@@ -71,7 +71,7 @@
           tmp.type() != Json::objectValue)
       {
         throw OrthancException(ErrorCode_BadJson,
-                               "The configuration file does not follow the JSON syntax: " + path.string());
+                               "The configuration file does not follow the JSON syntax: " + SystemToolbox::PathToUtf8(path));
       }
 
       Toolbox::CopyJsonWithoutComments(config, tmp);
@@ -103,11 +103,11 @@
 
     
   static void ScanFolderForConfiguration(Json::Value& target,
-                                         const char* folder)
+                                         const boost::filesystem::path& folder)
   {
     using namespace boost::filesystem;
 
-    LOG(WARNING) << "Scanning folder \"" << folder << "\" for configuration files";
+    LOG(WARNING) << "Scanning folder \"" << Orthanc::SystemToolbox::PathToUtf8(folder) << "\" for configuration files";
 
     directory_iterator end_it; // default construction yields past-the-end
     for (directory_iterator it(folder);
@@ -121,25 +121,25 @@
 
         if (extension == ".json")
         {
-          AddFileToConfiguration(target, it->path().string());
+          AddFileToConfiguration(target, it->path());
         }
       }
     }
   }
 
     
-  static void ReadConfiguration(Json::Value& target,
-                                const char* configurationFile)
+  static void ReadConfiguration(Json::Value& target, 
+                                const boost::filesystem::path &configurationFile)
   {
     target = Json::objectValue;
 
-    if (configurationFile != NULL)
+    if (!configurationFile.empty())
     {
       if (!boost::filesystem::exists(configurationFile))
       {
         throw OrthancException(ErrorCode_InexistentFile,
                                "Inexistent path to configuration: " +
-                               std::string(configurationFile));
+                               SystemToolbox::PathToUtf8(configurationFile));
       }
       
       if (boost::filesystem::is_directory(configurationFile))
@@ -583,7 +583,7 @@
   }
 
 
-  void OrthancConfiguration::Read(const char* configurationFile)
+  void OrthancConfiguration::Read(const boost::filesystem::path& configurationFile)
   {
     // Read the content of the configuration
     configurationFileArg_ = configurationFile;
@@ -593,11 +593,11 @@
     defaultDirectory_ = boost::filesystem::current_path();
     configurationAbsolutePath_ = "";
 
-    if (configurationFile)
+    if (configurationFile.empty())
     {
       if (boost::filesystem::is_directory(configurationFile))
       {
-        defaultDirectory_ = boost::filesystem::path(configurationFile);
+        defaultDirectory_ = configurationFile;
         configurationAbsolutePath_ = boost::filesystem::absolute(configurationFile).parent_path().string();
       }
       else
@@ -750,10 +750,10 @@
   }
     
 
-  std::string OrthancConfiguration::InterpretStringParameterAsPath(
+  boost::filesystem::path OrthancConfiguration::InterpretStringParameterAsPath(
     const std::string& parameter) const
   {
-    return SystemToolbox::InterpretRelativePath(defaultDirectory_.string(), parameter);
+    return SystemToolbox::InterpretRelativePath(defaultDirectory_, parameter);
   }
 
     
--- a/OrthancServer/Sources/OrthancConfiguration.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancServer/Sources/OrthancConfiguration.h	Tue Sep 09 15:35:30 2025 +0200
@@ -67,7 +67,7 @@
     boost::filesystem::path  defaultDirectory_;
     std::string              configurationAbsolutePath_;
     FontRegistry             fontRegistry_;
-    const char*              configurationFileArg_;
+    boost::filesystem::path  configurationFileArg_;
     Modalities               modalities_;
     Peers                    peers_;
     JobsEngineThreadsCount   jobsEngineThreadsCount_;
@@ -75,7 +75,6 @@
     std::set<Warnings>       disabledWarnings_;
 
     OrthancConfiguration() :
-      configurationFileArg_(NULL),
       serverIndex_(NULL)
     {
     }
@@ -164,7 +163,7 @@
       return fontRegistry_;
     }
 
-    void Read(const char* configurationFile);
+    void Read(const boost::filesystem::path &configurationFile);
 
     // "SetServerIndex()" must have been called
     void LoadModalitiesAndPeers();
@@ -209,7 +208,7 @@
 
     RegisteredUsersStatus SetupRegisteredUsers(HttpServer& httpServer) const;
 
-    std::string InterpretStringParameterAsPath(const std::string& parameter) const;
+    boost::filesystem::path InterpretStringParameterAsPath(const std::string& parameter) const;
     
     void GetListOfStringsParameter(std::list<std::string>& target,
                                    const std::string& key) const;
--- a/OrthancServer/Sources/OrthancInitialization.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancServer/Sources/OrthancInitialization.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -334,7 +334,7 @@
 
 
 
-  void OrthancInitialize(const char* configurationFile)
+  void OrthancInitialize(const boost::filesystem::path& configurationFile)
   {
     static const char* const LOCALE = "Locale";
     static const char* const PKCS11 = "Pkcs11";
@@ -442,7 +442,7 @@
     boost::filesystem::path indexDirectory = lock.GetConfiguration().InterpretStringParameterAsPath(
       lock.GetConfiguration().GetStringParameter("IndexDirectory", storageDirectoryStr));
 
-    LOG(WARNING) << "SQLite index directory: " << indexDirectory;
+    LOG(WARNING) << "SQLite index directory: " << SystemToolbox::PathToUtf8(indexDirectory);
 
     try
     {
@@ -452,7 +452,7 @@
     {
     }
 
-    return new SQLiteDatabaseWrapper(indexDirectory.string() + "/index");
+    return new SQLiteDatabaseWrapper(Orthanc::SystemToolbox::PathToUtf8(indexDirectory) + "/index");
   }
 
 
@@ -466,7 +466,7 @@
       FilesystemStorage storage_;
 
     public:
-      FilesystemStorageWithoutDicom(const std::string& path,
+      FilesystemStorageWithoutDicom(const boost::filesystem::path& path,
                                     bool fsyncOnWrite) :
         storage_(path, fsyncOnWrite)
       {
@@ -528,19 +528,19 @@
     boost::filesystem::path storageDirectory = 
       lock.GetConfiguration().InterpretStringParameterAsPath(storageDirectoryStr);
 
-    LOG(WARNING) << "Storage directory: " << storageDirectory;
+    LOG(WARNING) << "Storage directory: " << SystemToolbox::PathToUtf8(storageDirectory);
 
     // New in Orthanc 1.7.4
     bool fsyncOnWrite = lock.GetConfiguration().GetBooleanParameter(SYNC_STORAGE_AREA, true);
 
     if (lock.GetConfiguration().GetBooleanParameter(STORE_DICOM, true))
     {
-      return new PluginStorageAreaAdapter(new FilesystemStorage(storageDirectory.string(), fsyncOnWrite));
+      return new PluginStorageAreaAdapter(new FilesystemStorage(storageDirectory, fsyncOnWrite));
     }
     else
     {
       LOG(WARNING) << "The DICOM files will not be stored, Orthanc running in index-only mode";
-      return new PluginStorageAreaAdapter(new FilesystemStorageWithoutDicom(storageDirectory.string(), fsyncOnWrite));
+      return new PluginStorageAreaAdapter(new FilesystemStorageWithoutDicom(storageDirectory, fsyncOnWrite));
     }
   }
 
--- a/OrthancServer/Sources/OrthancInitialization.h	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancServer/Sources/OrthancInitialization.h	Tue Sep 09 15:35:30 2025 +0200
@@ -29,7 +29,7 @@
 
 namespace Orthanc
 {
-  void OrthancInitialize(const char* configurationFile = NULL);
+  void OrthancInitialize(const boost::filesystem::path& configurationFile);
 
   void OrthancFinalize();
 
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -190,14 +190,12 @@
 
     if (plugins.HasStorageArea())
     {
-      std::string p = plugins.GetStorageAreaLibrary().GetPath();
-      result[STORAGE_AREA_PLUGIN] = boost::filesystem::canonical(p).string();
+      result[STORAGE_AREA_PLUGIN] = SystemToolbox::PathToUtf8(plugins.GetStorageAreaLibrary().GetPath());
     }
 
     if (plugins.HasDatabaseBackend())
     {
-      std::string p = plugins.GetDatabaseBackendLibrary().GetPath();
-      result[DATABASE_BACKEND_PLUGIN] = boost::filesystem::canonical(p).string();     
+      result[DATABASE_BACKEND_PLUGIN] = SystemToolbox::PathToUtf8(plugins.GetDatabaseBackendLibrary().GetPath());
     }
 #else
     result[PLUGINS_ENABLED] = false;
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -1114,11 +1114,11 @@
       }
     }
 
-    void SetOutputFile(const std::string& path)
+    void SetOutputFile(const boost::filesystem::path& path)
     {
       if (zip_.get() == NULL)
       {
-        zip_.reset(new HierarchicalZipWriter(path.c_str()));
+        zip_.reset(new HierarchicalZipWriter(path));
         zip_->SetZip64(commands_.IsZip64());
         isStream_ = false;
       }
--- a/OrthancServer/Sources/ServerJobs/Operations/SystemCallOperation.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancServer/Sources/ServerJobs/Operations/SystemCallOperation.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -89,7 +89,7 @@
 
         tmp->Write(dicom);
         
-        arguments.push_back(tmp->GetPath());
+        arguments.push_back(SystemToolbox::PathToUtf8(tmp->GetPath()));
         break;
       }
 
--- a/OrthancServer/Sources/main.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancServer/Sources/main.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -711,10 +711,10 @@
 };
 
 
-static void PrintHelp(const char* path)
+static void PrintHelp(const boost::filesystem::path& path)
 {
   std::cout 
-    << "Usage: " << path << " [OPTION]... [CONFIGURATION]" << std::endl
+    << "Usage: " << SystemToolbox::PathToUtf8(path) << " [OPTION]... [CONFIGURATION]" << std::endl
     << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." << std::endl
     << std::endl
     << "The \"CONFIGURATION\" argument can be a single file or a directory. In the " << std::endl
@@ -776,10 +776,10 @@
 }
 
 
-static void PrintVersion(const char* path)
+static void PrintVersion(const boost::filesystem::path &path)
 {
   std::cout
-    << path << " " << ORTHANC_VERSION << std::endl
+    << SystemToolbox::PathToUtf8(path) << " " << ORTHANC_VERSION << std::endl
     << "Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics Department, University Hospital of Liege (Belgium)" << std::endl
     << "Copyright (C) 2017-2023 Osimis S.A. (Belgium)" << std::endl
     << "Copyright (C) 2024-2025 Orthanc Team SRL (Belgium)" << std::endl
@@ -801,10 +801,10 @@
 }
 
 
-static void PrintErrors(const char* path)
+static void PrintErrors(const boost::filesystem::path& path)
 {
   std::cout
-    << path << " " << ORTHANC_VERSION << std::endl
+    << SystemToolbox::PathToUtf8(path) << " " << ORTHANC_VERSION << std::endl
     << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." 
     << std::endl << std::endl
     << "List of error codes that could be returned by Orthanc:" 
@@ -945,7 +945,7 @@
   for (std::list<std::string>::const_iterator
          it = pathList.begin(); it != pathList.end(); ++it)
   {
-    std::string path;
+    boost::filesystem::path path;
 
     {
       OrthancConfiguration::ReaderLock lock;
@@ -1150,7 +1150,7 @@
       
       if (lock.GetConfiguration().GetBooleanParameter("SslEnabled", false))
       {
-        std::string certificate = lock.GetConfiguration().InterpretStringParameterAsPath(
+        boost::filesystem::path certificate = lock.GetConfiguration().InterpretStringParameterAsPath(
           lock.GetConfiguration().GetStringParameter("SslCertificate", "certificate.pem"));
         httpServer.SetSslEnabled(true);
         httpServer.SetSslCertificate(certificate.c_str());
@@ -1199,10 +1199,10 @@
 
       if (lock.GetConfiguration().GetBooleanParameter("SslVerifyPeers", false))
       {
-        std::string trustedClientCertificates = lock.GetConfiguration().InterpretStringParameterAsPath(
+        boost::filesystem::path trustedClientCertificates = lock.GetConfiguration().InterpretStringParameterAsPath(
           lock.GetConfiguration().GetStringParameter("SslTrustedClientCertificates", "trustedCertificates.pem"));
         httpServer.SetSslVerifyPeers(true);
-        httpServer.SetSslTrustedClientCertificates(trustedClientCertificates.c_str());
+        httpServer.SetSslTrustedClientCertificates(trustedClientCertificates);
       }
       else
       {
@@ -1573,10 +1573,11 @@
     // ServerContext, otherwise the possible Lua scripts will not be
     // able to properly issue HTTP/HTTPS queries
 
-    std::string httpsCaCertificates = lock.GetConfiguration().GetStringParameter("HttpsCACertificates", "");
-    if (!httpsCaCertificates.empty())
+    std::string httpsCaCertificatesStr = lock.GetConfiguration().GetStringParameter("HttpsCACertificates", "");
+    boost::filesystem::path httpsCaCertificates;
+    if (!httpsCaCertificatesStr.empty())
     {
-      httpsCaCertificates = lock.GetConfiguration().InterpretStringParameterAsPath(httpsCaCertificates);
+      httpsCaCertificates = lock.GetConfiguration().InterpretStringParameterAsPath(httpsCaCertificatesStr);
     }
 
     HttpClient::ConfigureSsl(lock.GetConfiguration().GetBooleanParameter("HttpsVerifyPeers", true),
@@ -1769,8 +1770,7 @@
 }
 
 
-static bool ConfigurePlugins(int argc, 
-                             char* argv[],
+static bool ConfigurePlugins(const std::vector<std::string> arguments,
                              bool upgradeDatabase,
                              bool loadJobsFromDatabase)
 {
@@ -1785,7 +1785,7 @@
   }
   
   OrthancPlugins plugins(databaseServerIdentifier);
-  plugins.SetCommandLineArguments(argc, argv);
+  plugins.SetCommandLineArguments(arguments);
   LoadPlugins(plugins);
 
   IDatabaseWrapper* database = NULL;
@@ -1834,12 +1834,11 @@
 }
 
 
-static bool StartOrthanc(int argc, 
-                         char* argv[],
+static bool StartOrthanc(const std::vector<std::string>& arguments,
                          bool upgradeDatabase,
                          bool loadJobsFromDatabase)
 {
-  return ConfigurePlugins(argc, argv, upgradeDatabase, loadJobsFromDatabase);
+  return ConfigurePlugins(arguments, upgradeDatabase, loadJobsFromDatabase);
 }
 
 
@@ -1867,11 +1866,32 @@
 }
 
 
-int main(int argc, char* argv[]) 
+#if defined(_WIN32) && !defined(__MINGW32__)
+// arguments are passed as UTF-16 on Windows
+int wmain(int argc, wchar_t *argv[])
 {
-#if defined(_WIN32) || defined(__CYGWIN__)
   // Set Windows console output to UTF-8 (otherwise, strings are considered to be in UTF-16.  For example, Cyrillic UTF-8 strings appear as garbage without that config)
   SetConsoleOutputCP(CP_UTF8);
+
+  // Transform the UTF-16 arguments into UTF-8 arguments
+  std::vector<std::string> arguments; // UTF-8 arguments
+
+  for (int i = 0; i < argc; i++)
+  {
+    std::wstring argument(argv[i]);
+    arguments.push_back(SystemToolbox::WStringToUtf8(argument));
+  }
+
+#else
+int main(int argc, char* argv[])
+{
+  std::vector<std::string> arguments; // UTF-8 arguments
+
+  // the arguments are assumed to be directly in UTF-8
+  for (int i = 0; i < argc; i++)
+  {
+    arguments.push_back(argv[i]);
+  }
 #endif
 
   Logging::Initialize();
@@ -1880,16 +1900,15 @@
 
   bool upgradeDatabase = false;
   bool loadJobsFromDatabase = true;
-  const char* configurationFile = NULL;
-
+  boost::filesystem::path configurationFile;
 
   /**
    * Parse the command-line options.
    **/ 
 
-  for (int i = 1; i < argc; i++)
+  for (size_t i = 1; i < arguments.size(); i++)
   {
-    std::string argument(argv[i]); 
+    const std::string& argument = arguments[i];    
 
     if (argument.empty())
     {
@@ -1897,7 +1916,7 @@
     }
     else if (argument[0] != '-')
     {
-      if (configurationFile != NULL)
+      if (!configurationFile.empty())
       {
         LOG(ERROR) << "More than one configuration path were provided on the command line, aborting";
         return -1;
@@ -1907,23 +1926,28 @@
         // Use the first argument that does not start with a "-" as
         // the configuration file
 
-        // TODO WHAT IS THE ENCODING?
-        configurationFile = argv[i];
+        configurationFile = SystemToolbox::PathFromUtf8(argument);
+//        // TODO WHAT IS THE ENCODING?
+//#if defined(_WIN32)
+//        //configurationFileUtf8Str = SystemToolbox::WStringToUtf8(SystemToolbox::WStringFromCharPtr(argv[i]));
+//#else
+//        configurationFileUtf8Str = std::string(argv[i]);
+//#endif
       }
     }
     else if (argument == "--errors")
     {
-      PrintErrors(argv[0]);
+      PrintErrors(SystemToolbox::PathFromUtf8(arguments[0]));
       return 0;
     }
     else if (argument == "--help")
     {
-      PrintHelp(argv[0]);
+      PrintHelp(SystemToolbox::PathFromUtf8(arguments[0]));
       return 0;
     }
     else if (argument == "--version")
     {
-      PrintVersion(argv[0]);
+      PrintVersion(SystemToolbox::PathFromUtf8(arguments[0]));
       return 0;
     }
     else if (argument == "--verbose")
@@ -2175,7 +2199,7 @@
     {
       OrthancInitialize(configurationFile);
 
-      bool restart = StartOrthanc(argc, argv, upgradeDatabase, loadJobsFromDatabase);
+      bool restart = StartOrthanc(arguments, upgradeDatabase, loadJobsFromDatabase);
       if (restart)
       {
         OrthancFinalize();
--- a/OrthancServer/UnitTestsSources/UnitTestsMain.cpp	Tue Sep 09 14:26:51 2025 +0200
+++ b/OrthancServer/UnitTestsSources/UnitTestsMain.cpp	Tue Sep 09 15:35:30 2025 +0200
@@ -47,6 +47,9 @@
 
 #include <dcmtk/dcmdata/dcdeftag.h>
 
+#if defined(_WIN32) || defined(__CYGWIN__)
+#include <windows.h>
+#endif
 
 using namespace Orthanc;
 
@@ -515,11 +518,16 @@
 
 int main(int argc, char **argv)
 {
+#if defined(_WIN32) && !defined(__MINGW32__)
+  // Set Windows console output to UTF-8 (otherwise, strings are considered to be in UTF-16.  For example, Cyrillic UTF-8 strings appear as garbage without that config)
+  SetConsoleOutputCP(CP_UTF8);
+#endif
+
   Logging::Initialize();
   SetGlobalVerbosity(Verbosity_Verbose);
   Toolbox::DetectEndianness();
-  SystemToolbox::MakeDirectory("UnitTestsResults");
-  OrthancInitialize();
+  SystemToolbox::MakeDirectory(SystemToolbox::PathFromUtf8("UnitTestsResults"));
+  OrthancInitialize("");
 
   ::testing::InitGoogleTest(&argc, argv);
   int result = RUN_ALL_TESTS();