changeset 6607:f30191e9ffbf

dealing with slashes in Toolbox::NormalizePath()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sun, 01 Mar 2026 11:21:54 +0100
parents 29388610f3e2
children 9722dc04e6a6
files OrthancFramework/Sources/Compression/HierarchicalZipWriter.cpp OrthancFramework/Sources/Compression/ZipWriter.cpp OrthancFramework/Sources/Toolbox.cpp OrthancFramework/Sources/Toolbox.h OrthancFramework/UnitTestsSources/ZipTests.cpp
diffstat 5 files changed, 47 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Sources/Compression/HierarchicalZipWriter.cpp	Fri Feb 27 18:37:52 2026 +0100
+++ b/OrthancFramework/Sources/Compression/HierarchicalZipWriter.cpp	Sun Mar 01 11:21:54 2026 +0100
@@ -51,12 +51,9 @@
   std::string HierarchicalZipWriter::Index::EnsureUniqueFilename(const std::string& filename,
                                                                  bool allowUtf8)
   {
-    const std::string standardized = Toolbox::NormalizePath(filename, allowUtf8);
+    const std::string standardized = Toolbox::NormalizePath(filename, allowUtf8, false /* replace (back)slashes by spaces */);
 
-    if (standardized.find('/') != std::string::npos)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, "Your filename must not contain slashes or backslashes");
-    }
+    assert(standardized.find('\\') == std::string::npos);
 
     Directory& d = *stack_.back();
     Directory::Content::iterator it = d.content_.find(standardized);
--- a/OrthancFramework/Sources/Compression/ZipWriter.cpp	Fri Feb 27 18:37:52 2026 +0100
+++ b/OrthancFramework/Sources/Compression/ZipWriter.cpp	Sun Mar 01 11:21:54 2026 +0100
@@ -609,7 +609,7 @@
   {
     Open();
 
-    const std::string normalized = Toolbox::NormalizePath(filename, allowUtf8_);
+    const std::string normalized = Toolbox::NormalizePath(filename, allowUtf8_, true /* allow slashes, necessary for subdirectories */);
 
     zip_fileinfo zfi;
     PrepareFileInfo(zfi);
--- a/OrthancFramework/Sources/Toolbox.cpp	Fri Feb 27 18:37:52 2026 +0100
+++ b/OrthancFramework/Sources/Toolbox.cpp	Sun Mar 01 11:21:54 2026 +0100
@@ -2959,7 +2959,8 @@
 
 
   std::string Toolbox::NormalizePath(const std::string& utf8,
-                                     bool allowUtf8)
+                                     bool allowUtf8,
+                                     bool allowSlashes)
   {
     std::string converted;
 
@@ -2973,14 +2974,13 @@
       converted = Toolbox::ConvertToAscii(utf8);
     }
 
-    bool previousSpace = true;
-
     std::string result;
     result.reserve(converted.size());
 
     for (size_t i = 0; i < converted.size(); i++)
     {
       unsigned char c = converted[i];
+      bool allowRepetitions = true;
 
       if (isspace(c) ||
           c <= 31 ||  // Control characters
@@ -2994,21 +2994,33 @@
           c == '>' ||
           c == '|')
       {
-        if (!previousSpace)
+        // This is a space character: Avoid their repetition
+        allowRepetitions = false;
+        c = ' ';
+      }
+      else if (c == '/' ||
+               c == '\\')
+      {
+        // This is a slash or backslash: Prevent their repetition
+        allowRepetitions = false;
+        if (allowSlashes)
         {
-          previousSpace = true;
-          result.push_back(' ');
+          c = '/';  // Normalize backslashes as forward slashes
+        }
+        else
+        {
+          c = ' ';  // Slashes are not allowed: Replace by spaces
         }
       }
+
+      if (allowRepetitions)
+      {
+        result.push_back(c);
+      }
       else
       {
-        previousSpace = false;
-
-        if (c == '\\')
-        {
-          result.push_back('/');  // Replace backslashes by forward slashes
-        }
-        else
+        if (result.empty() ||
+            result.back() != c)
         {
           result.push_back(c);
         }
--- a/OrthancFramework/Sources/Toolbox.h	Fri Feb 27 18:37:52 2026 +0100
+++ b/OrthancFramework/Sources/Toolbox.h	Sun Mar 01 11:21:54 2026 +0100
@@ -408,7 +408,8 @@
     static bool IsValidUtf8(const std::string& s);
 
     static std::string NormalizePath(const std::string& utf8,
-                                     bool allowUtf8);
+                                     bool allowUtf8,
+                                     bool allowSlashes);
 
     class ORTHANC_PUBLIC ElapsedTimer : public boost::noncopyable
     {
--- a/OrthancFramework/UnitTestsSources/ZipTests.cpp	Fri Feb 27 18:37:52 2026 +0100
+++ b/OrthancFramework/UnitTestsSources/ZipTests.cpp	Sun Mar 01 11:21:54 2026 +0100
@@ -111,23 +111,25 @@
     ASSERT_EQ("hello-3", i.OpenFile("hello", true));
     ASSERT_EQ("trE hell", i.OpenFile("    ÊtrE hellô  ", false));
     ASSERT_EQ("ÊtrE hellô", i.OpenFile("    ÊtrE hellô  ", true));
-    ASSERT_THROW(i.OpenFile("hel/lo", true), OrthancException);
-    ASSERT_THROW(i.OpenFile("hel\\lo", true), OrthancException);
+    ASSERT_EQ("hel^lo", i.OpenFile("hel^lo", true));
+    ASSERT_EQ("hel lo", i.OpenFile("hel////lo", true));
+    ASSERT_EQ("hel lo-2", i.OpenFile("hel\\\\/\\/\\lo", true));
 
     i.OpenDirectory("coucou", true);
 
     ASSERT_EQ("coucou-2/world", i.OpenFile("world", true));
     ASSERT_EQ("coucou-2/world-2", i.OpenFile("world", true));
 
-    ASSERT_THROW(i.OpenDirectory("hel/lo", true), OrthancException);
-    ASSERT_THROW(i.OpenDirectory("hel\\lo", true), OrthancException);
+    i.OpenDirectory("world", true);
+    i.OpenDirectory("hel///lo", true);
+    i.OpenDirectory("hel\\\\/\\/\\lo", true);
 
-    i.OpenDirectory("world", true);
-  
-    ASSERT_EQ("coucou-2/world-3/hello", i.OpenFile("hello", true));
-    ASSERT_EQ("coucou-2/world-3/hello-2", i.OpenFile("hello", true));
+    ASSERT_EQ("coucou-2/world-3/hel lo/hel lo/hello", i.OpenFile("hello", true));
+    ASSERT_EQ("coucou-2/world-3/hel lo/hel lo/hello-2", i.OpenFile("hello", true));
 
     i.CloseDirectory();
+    i.CloseDirectory();
+    i.CloseDirectory();
 
     ASSERT_EQ("coucou-2/world-4", i.OpenFile("world", true));
 
@@ -142,9 +144,13 @@
 
 TEST(HierarchicalZipWriter, Filenames)
 {
-  ASSERT_EQ("trE hell", Toolbox::NormalizePath("    ÊtrE hellô  ", false));
-  ASSERT_EQ("ÊtrE hellô", Toolbox::NormalizePath("    ÊtrE hellô  ", true));
-  ASSERT_EQ("Hel^^ ^ ^^lo world", Toolbox::NormalizePath("    Hel^^   ^\r\n\t^^lo  \t  <world>  ", true));
+  ASSERT_EQ("trE h/ell", Toolbox::NormalizePath("    ÊtrE h\\\\/\\/\\ellô  ", false, true));
+  ASSERT_EQ("ÊtrE h/ellô", Toolbox::NormalizePath("    ÊtrE h\\\\/\\/\\ellô  ", true, true));
+  ASSERT_EQ("H/el^^ ^ ^^lo world", Toolbox::NormalizePath("    H\\\\/\\/\\el^^   ^\r\n\t^^lo  \t  <world>  ", true, true));
+
+  ASSERT_EQ("trE h ell", Toolbox::NormalizePath("    ÊtrE h\\\\/\\/\\ellô  ", false, false));
+  ASSERT_EQ("ÊtrE h ellô", Toolbox::NormalizePath("    ÊtrE h\\\\/\\/\\ellô  ", true, false));
+  ASSERT_EQ("H el^^ ^ ^^lo world", Toolbox::NormalizePath("    H\\\\/\\/\\el^^   ^\r\n\t^^lo  \t  <world>  ", true, false));
 }