Browse Source

CPack: Support arbitrary component name when packaging

CPack no longer blindly tries to create temporary packaging
(sub)directories that contain the verbatim name of a component, which
might contain characters that are not supported on the platform /
filesystem.
Instead, if the component's name contains a (possibly) problematic
character its MD5 hash will be used for that temporary packaging
(sub)directory.

Likewise, if the component's name resembles a reserved device name (e.g.
"COM1" on Windows) then the temporary packaging (sub)directory will get
this name prefixed with an underscore.
Similar, if it ends in a dot (on Windows) then the temporary packaging
(sub)directory will get this name suffixed with an underscore.

Fixes: #23612
Deniz Bahadir 1 year ago
parent
commit
a1af593291

+ 1 - 1
Modules/Internal/CPack/CPackRPM.cmake

@@ -916,7 +916,7 @@ function(cpack_rpm_generate_package)
       CPACK_RPM_MAIN_COMPONENT_UPPER)
 
     if(NOT CPACK_RPM_MAIN_COMPONENT_UPPER STREQUAL CPACK_RPM_PACKAGE_COMPONENT_UPPER)
-      string(APPEND CPACK_RPM_PACKAGE_NAME "-${CPACK_RPM_PACKAGE_COMPONENT}")
+      string(APPEND CPACK_RPM_PACKAGE_NAME "-${CPACK_RPM_PACKAGE_COMPONENT_PART_NAME}")
 
       cpack_rpm_variable_fallback("CPACK_RPM_PACKAGE_NAME"
         "CPACK_RPM_${CPACK_RPM_PACKAGE_COMPONENT}_PACKAGE_NAME"

+ 17 - 1
Source/CPack/IFW/cmCPackIFWGenerator.cxx

@@ -461,7 +461,7 @@ int cmCPackIFWGenerator::InitializeInternal()
   return this->Superclass::InitializeInternal();
 }
 
-std::string cmCPackIFWGenerator::GetComponentInstallDirNameSuffix(
+std::string cmCPackIFWGenerator::GetComponentInstallSuffix(
   const std::string& componentName)
 {
   const std::string prefix = "packages/";
@@ -475,6 +475,22 @@ std::string cmCPackIFWGenerator::GetComponentInstallDirNameSuffix(
     this->GetComponentPackageName(&this->Components[componentName]) + suffix;
 }
 
+std::string cmCPackIFWGenerator::GetComponentInstallDirNameSuffix(
+  const std::string& componentName)
+{
+  const std::string prefix = "packages/";
+  const std::string suffix = "/data";
+
+  if (this->componentPackageMethod == this->ONE_PACKAGE) {
+    return cmStrCat(prefix, this->GetRootPackageName(), suffix);
+  }
+
+  return prefix +
+    this->GetSanitizedDirOrFileName(
+      this->GetComponentPackageName(&this->Components[componentName])) +
+    suffix;
+}
+
 cmCPackComponent* cmCPackIFWGenerator::GetComponent(
   const std::string& projectName, const std::string& componentName)
 {

+ 2 - 0
Source/CPack/IFW/cmCPackIFWGenerator.h

@@ -67,6 +67,8 @@ protected:
    */
   const char* GetOutputExtension() override;
 
+  std::string GetComponentInstallSuffix(
+    const std::string& componentName) override;
   std::string GetComponentInstallDirNameSuffix(
     const std::string& componentName) override;
 

+ 1 - 8
Source/CPack/cmCPackArchiveGenerator.cxx

@@ -234,7 +234,7 @@ int cmCPackArchiveGenerator::addOneComponentToArchive(
                 "   - packaging component: " << component->Name << std::endl);
   // Add the files of this component to the archive
   std::string localToplevel(this->GetOption("CPACK_TEMPORARY_DIRECTORY"));
-  localToplevel += "/" + component->Name;
+  localToplevel += "/" + this->GetSanitizedDirOrFileName(component->Name);
   // Change to local toplevel
   cmWorkingDirectory workdir(localToplevel);
   if (workdir.Failed()) {
@@ -357,11 +357,7 @@ int cmCPackArchiveGenerator::PackageComponents(bool ignoreGroup)
             << comp.second.Name
             << "> does not belong to any group, package it separately."
             << std::endl);
-        std::string localToplevel(
-          this->GetOption("CPACK_TEMPORARY_DIRECTORY"));
         std::string packageFileName = std::string(this->toplevel);
-
-        localToplevel += "/" + comp.first;
         packageFileName +=
           "/" + this->GetArchiveComponentFileName(comp.first, false);
 
@@ -379,10 +375,7 @@ int cmCPackArchiveGenerator::PackageComponents(bool ignoreGroup)
   // We build 1 package per component
   else {
     for (auto& comp : this->Components) {
-      std::string localToplevel(this->GetOption("CPACK_TEMPORARY_DIRECTORY"));
       std::string packageFileName = std::string(this->toplevel);
-
-      localToplevel += "/" + comp.first;
       packageFileName +=
         "/" + this->GetArchiveComponentFileName(comp.first, false);
 

+ 15 - 3
Source/CPack/cmCPackDebGenerator.cxx

@@ -536,6 +536,11 @@ int cmCPackDebGenerator::InitializeInternal()
 int cmCPackDebGenerator::PackageOnePack(std::string const& initialTopLevel,
                                         std::string const& packageName)
 {
+  // Determine the sanitized packaging directory-name that can be used on the
+  // file-system.
+  std::string sanitizedPkgDirName =
+    this->GetSanitizedDirOrFileName(packageName);
+
   // Begin the archive for this pack
   std::string localToplevel(initialTopLevel);
   std::string packageFileName(
@@ -543,7 +548,7 @@ int cmCPackDebGenerator::PackageOnePack(std::string const& initialTopLevel,
   std::string outputFileName(*this->GetOption("CPACK_PACKAGE_FILE_NAME") +
                              "-" + packageName + this->GetOutputExtension());
 
-  localToplevel += "/" + packageName;
+  localToplevel += "/" + sanitizedPkgDirName;
   /* replace the TEMP DIRECTORY with the component one */
   this->SetOption("CPACK_TEMPORARY_DIRECTORY", localToplevel);
   packageFileName += "/" + outputFileName;
@@ -554,7 +559,7 @@ int cmCPackDebGenerator::PackageOnePack(std::string const& initialTopLevel,
   // Tell CPackDeb.cmake the name of the component GROUP.
   this->SetOption("CPACK_DEB_PACKAGE_COMPONENT", packageName);
   // Tell CPackDeb.cmake the path where the component is.
-  std::string component_path = cmStrCat('/', packageName);
+  std::string component_path = cmStrCat('/', sanitizedPkgDirName);
   this->SetOption("CPACK_DEB_PACKAGE_COMPONENT_PART_PATH", component_path);
   if (!this->ReadListFile("Internal/CPack/CPackDeb.cmake")) {
     cmCPackLogger(cmCPackLog::LOG_ERROR,
@@ -894,7 +899,7 @@ bool cmCPackDebGenerator::SupportsComponentInstallation() const
   return this->IsOn("CPACK_DEB_COMPONENT_INSTALL");
 }
 
-std::string cmCPackDebGenerator::GetComponentInstallDirNameSuffix(
+std::string cmCPackDebGenerator::GetComponentInstallSuffix(
   const std::string& componentName)
 {
   if (this->componentPackageMethod == ONE_PACKAGE_PER_COMPONENT) {
@@ -913,3 +918,10 @@ std::string cmCPackDebGenerator::GetComponentInstallDirNameSuffix(
   }
   return componentName;
 }
+
+std::string cmCPackDebGenerator::GetComponentInstallDirNameSuffix(
+  const std::string& componentName)
+{
+  return this->GetSanitizedDirOrFileName(
+    this->GetComponentInstallSuffix(componentName));
+}

+ 2 - 0
Source/CPack/cmCPackDebGenerator.h

@@ -59,6 +59,8 @@ protected:
   int PackageFiles() override;
   const char* GetOutputExtension() override { return ".deb"; }
   bool SupportsComponentInstallation() const override;
+  std::string GetComponentInstallSuffix(
+    const std::string& componentName) override;
   std::string GetComponentInstallDirNameSuffix(
     const std::string& componentName) override;
 

+ 15 - 7
Source/CPack/cmCPackDragNDropGenerator.cxx

@@ -182,13 +182,14 @@ int cmCPackDragNDropGenerator::PackageFiles()
   }
 
   // component install
-  std::vector<std::string> package_files;
+  std::vector<std::pair<std::string, std::string>> package_files;
 
   std::map<std::string, cmCPackComponent>::iterator compIt;
   for (compIt = this->Components.begin(); compIt != this->Components.end();
        ++compIt) {
-    std::string name = GetComponentInstallDirNameSuffix(compIt->first);
-    package_files.push_back(name);
+    std::string dirName = GetComponentInstallDirNameSuffix(compIt->first);
+    std::string fileName = GetComponentInstallSuffix(compIt->first);
+    package_files.emplace_back(fileName, dirName);
   }
   std::sort(package_files.begin(), package_files.end());
   package_files.erase(std::unique(package_files.begin(), package_files.end()),
@@ -198,15 +199,15 @@ int cmCPackDragNDropGenerator::PackageFiles()
   packageFileNames.clear();
   for (auto const& package_file : package_files) {
     std::string full_package_name = cmStrCat(toplevel, '/');
-    if (package_file == "ALL_IN_ONE"_s) {
+    if (package_file.first == "ALL_IN_ONE"_s) {
       full_package_name += this->GetOption("CPACK_PACKAGE_FILE_NAME");
     } else {
-      full_package_name += package_file;
+      full_package_name += package_file.first;
     }
     full_package_name += GetOutputExtension();
     packageFileNames.push_back(full_package_name);
 
-    std::string src_dir = cmStrCat(toplevel, '/', package_file);
+    std::string src_dir = cmStrCat(toplevel, '/', package_file.second);
 
     if (0 == this->CreateDMG(src_dir, full_package_name)) {
       return 0;
@@ -707,7 +708,7 @@ bool cmCPackDragNDropGenerator::SupportsComponentInstallation() const
   return true;
 }
 
-std::string cmCPackDragNDropGenerator::GetComponentInstallDirNameSuffix(
+std::string cmCPackDragNDropGenerator::GetComponentInstallSuffix(
   const std::string& componentName)
 {
   // we want to group components together that go in the same dmg package
@@ -747,6 +748,13 @@ std::string cmCPackDragNDropGenerator::GetComponentInstallDirNameSuffix(
   return GetComponentPackageFileName(package_file_name, componentName, false);
 }
 
+std::string cmCPackDragNDropGenerator::GetComponentInstallDirNameSuffix(
+  const std::string& componentName)
+{
+  return this->GetSanitizedDirOrFileName(
+    this->GetComponentInstallSuffix(componentName));
+}
+
 void cmCPackDragNDropGenerator::WriteRezXML(std::string const& file,
                                             RezDoc const& rez)
 {

+ 2 - 0
Source/CPack/cmCPackDragNDropGenerator.h

@@ -35,6 +35,8 @@ protected:
   bool CreateEmptyFile(std::ostringstream& target, size_t size);
   bool RunCommand(std::string const& command, std::string* output = nullptr);
 
+  std::string GetComponentInstallSuffix(
+    const std::string& componentName) override;
   std::string GetComponentInstallDirNameSuffix(
     const std::string& componentName) override;
 

+ 54 - 2
Source/CPack/cmCPackGenerator.cxx

@@ -761,6 +761,7 @@ int cmCPackGenerator::InstallCMakeProject(
     // instead of the default
     //  one install directory for each component.
     tempInstallDirectory += this->GetComponentInstallDirNameSuffix(component);
+
     if (this->IsOn("CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY")) {
       tempInstallDirectory += "/";
       tempInstallDirectory += *this->GetOption("CPACK_PACKAGE_FILE_NAME");
@@ -965,7 +966,7 @@ int cmCPackGenerator::InstallCMakeProject(
     if (componentInstall) {
       std::string absoluteDestFileComponent =
         std::string("CPACK_ABSOLUTE_DESTINATION_FILES") + "_" +
-        this->GetComponentInstallDirNameSuffix(component);
+        this->GetComponentInstallSuffix(component);
       if (nullptr != this->GetOption(absoluteDestFileComponent)) {
         std::string absoluteDestFilesListComponent =
           cmStrCat(this->GetOption(absoluteDestFileComponent), ';', *d);
@@ -1544,11 +1545,62 @@ int cmCPackGenerator::PrepareGroupingKind()
   return 1;
 }
 
-std::string cmCPackGenerator::GetComponentInstallDirNameSuffix(
+std::string cmCPackGenerator::GetSanitizedDirOrFileName(
+  const std::string& name, bool isFullName) const
+{
+  if (isFullName) {
+#ifdef _WIN32
+    // Given name matches a reserved name (on Windows)?
+    // Then return it prepended with an underscore.
+    cmsys::RegularExpression reserved_pattern("^("
+                                              "[Cc][Oo][Nn]|"
+                                              "[Pp][Rr][Nn]|"
+                                              "[Aa][Uu][Xx]|"
+                                              "[Nn][Uu][Ll]|"
+                                              "[Cc][Oo][Mm][1-9]|"
+                                              "[Ll][Pp][Tt][1-9]"
+                                              ")([.].+)?");
+    if (reserved_pattern.find(name)) {
+      return "_" + name;
+    }
+    // Given name ends in a dot (on Windows)?
+    // Then return it appended with an underscore.
+    if (name.back() == '.') {
+      return name + '_';
+    }
+#endif
+  }
+
+#ifndef _WIN32
+  constexpr const char* prohibited_chars = "<>\"/\\|?*`";
+#else
+  // Note: Windows also excludes the colon.
+  constexpr const char* prohibited_chars = "<>\"/\\|?*`:";
+#endif
+  // Given name contains non-supported character?
+  // Then return its MD5 hash.
+  if (name.find_first_of(prohibited_chars) != std::string::npos) {
+    cmCryptoHash hasher(cmCryptoHash::AlgoMD5);
+    return hasher.HashString(name);
+  }
+
+  // Otherwise return unmodified.
+  return name;
+}
+
+std::string cmCPackGenerator::GetComponentInstallSuffix(
   const std::string& componentName)
 {
   return componentName;
 }
+
+std::string cmCPackGenerator::GetComponentInstallDirNameSuffix(
+  const std::string& componentName)
+{
+  return this->GetSanitizedDirOrFileName(
+    this->GetComponentInstallSuffix(componentName));
+}
+
 std::string cmCPackGenerator::GetComponentPackageFileName(
   const std::string& initialPackageFileName,
   const std::string& groupOrComponentName, bool isGroupName)

+ 22 - 0
Source/CPack/cmCPackGenerator.h

@@ -144,6 +144,18 @@ protected:
    */
   virtual int PrepareGroupingKind();
 
+  /**
+   * Ensures that the given name only contains characters that can cleanly be
+   * used as directory or file name and returns this sanitized name. Possibly,
+   * this name might be replaced by its hash.
+   * @param[in] name the name for a directory or file that shall be sanitized.
+   * @param[in] isFullName true if the result is used as the full name for a
+   *            directory or file. (Defaults to true.)
+   * @return the sanitized name.
+   */
+  virtual std::string GetSanitizedDirOrFileName(const std::string& name,
+                                                bool isFullName = true) const;
+
   /**
    * Some CPack generators may prefer to have
    * CPack install all components belonging to the same
@@ -154,6 +166,16 @@ protected:
    * @return the name suffix the generator wants for the specified component
    *         default is "componentName"
    */
+  virtual std::string GetComponentInstallSuffix(
+    const std::string& componentName);
+
+  /**
+   * The value that GetComponentInstallSuffix returns, but sanitized.
+   * @param[in] componentName the name of the component to be installed
+   * @return the name suffix the generator wants for the specified component
+   *         (but sanitized, so that it can be used on the file-system).
+   *         default is "componentName".
+   */
   virtual std::string GetComponentInstallDirNameSuffix(
     const std::string& componentName);
 

+ 41 - 7
Source/CPack/cmCPackRPMGenerator.cxx

@@ -12,6 +12,7 @@
 #include "cmCPackComponentGroup.h"
 #include "cmCPackGenerator.h"
 #include "cmCPackLog.h"
+#include "cmCryptoHash.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
@@ -62,7 +63,15 @@ void cmCPackRPMGenerator::AddGeneratedPackageNames()
 int cmCPackRPMGenerator::PackageOnePack(std::string const& initialToplevel,
                                         std::string const& packageName)
 {
-  int retval = 1;
+  // Determine the sanitized package name that can be used in file-names on
+  // the file-system.
+  std::string sanitizedPkgNameSuffix =
+    this->GetSanitizedDirOrFileName(packageName, false);
+  // Determine the sanitized packaging directory-name that can be used on the
+  // file-system.
+  std::string sanitizedPkgDirName =
+    this->GetSanitizedDirOrFileName(packageName);
+
   // Begin the archive for this pack
   std::string localToplevel(initialToplevel);
   std::string packageFileName(
@@ -72,7 +81,7 @@ int cmCPackRPMGenerator::PackageOnePack(std::string const& initialToplevel,
       this->GetOption("CPACK_PACKAGE_FILE_NAME"), packageName, true) +
     this->GetOutputExtension());
 
-  localToplevel += "/" + packageName;
+  localToplevel += "/" + sanitizedPkgDirName;
   /* replace the TEMP DIRECTORY with the component one */
   this->SetOption("CPACK_TEMPORARY_DIRECTORY", localToplevel);
   packageFileName += "/" + outputFileName;
@@ -82,16 +91,34 @@ int cmCPackRPMGenerator::PackageOnePack(std::string const& initialToplevel,
   this->SetOption("CPACK_TEMPORARY_PACKAGE_FILE_NAME", packageFileName);
   // Tell CPackRPM.cmake the name of the component NAME.
   this->SetOption("CPACK_RPM_PACKAGE_COMPONENT", packageName);
+  // Tell CPackRPM.cmake the suffix for the component NAME.
+  this->SetOption("CPACK_RPM_PACKAGE_COMPONENT_PART_NAME",
+                  sanitizedPkgNameSuffix);
   // Tell CPackRPM.cmake the path where the component is.
-  std::string component_path = cmStrCat('/', packageName);
+  std::string component_path = cmStrCat('/', sanitizedPkgDirName);
   this->SetOption("CPACK_RPM_PACKAGE_COMPONENT_PART_PATH", component_path);
   if (!this->ReadListFile("Internal/CPack/CPackRPM.cmake")) {
     cmCPackLogger(cmCPackLog::LOG_ERROR,
                   "Error while execution CPackRPM.cmake" << std::endl);
-    retval = 0;
+    return 0;
   }
 
-  return retval;
+  return 1;
+}
+
+std::string cmCPackRPMGenerator::GetSanitizedDirOrFileName(
+  const std::string& name, bool isFullName) const
+{
+  auto sanitizedName =
+    this->cmCPackGenerator::GetSanitizedDirOrFileName(name, isFullName);
+  if (sanitizedName == name && !isFullName) {
+    // Make sure to also sanitize if name contains a colon (':').
+    if (name.find_first_of(':') != std::string::npos) {
+      cmCryptoHash hasher(cmCryptoHash::AlgoMD5);
+      return hasher.HashString(name);
+    }
+  }
+  return sanitizedName;
 }
 
 int cmCPackRPMGenerator::PackageComponents(bool ignoreGroup)
@@ -417,7 +444,7 @@ bool cmCPackRPMGenerator::SupportsComponentInstallation() const
   return this->IsOn("CPACK_RPM_COMPONENT_INSTALL");
 }
 
-std::string cmCPackRPMGenerator::GetComponentInstallDirNameSuffix(
+std::string cmCPackRPMGenerator::GetComponentInstallSuffix(
   const std::string& componentName)
 {
   if (this->componentPackageMethod == ONE_PACKAGE_PER_COMPONENT) {
@@ -432,7 +459,14 @@ std::string cmCPackRPMGenerator::GetComponentInstallDirNameSuffix(
   std::string groupVar =
     "CPACK_COMPONENT_" + cmSystemTools::UpperCase(componentName) + "_GROUP";
   if (nullptr != this->GetOption(groupVar)) {
-    return std::string(this->GetOption(groupVar));
+    return *this->GetOption(groupVar);
   }
   return componentName;
 }
+
+std::string cmCPackRPMGenerator::GetComponentInstallDirNameSuffix(
+  const std::string& componentName)
+{
+  return this->GetSanitizedDirOrFileName(
+    this->GetComponentInstallSuffix(componentName));
+}

+ 4 - 0
Source/CPack/cmCPackRPMGenerator.h

@@ -61,7 +61,11 @@ protected:
    */
   int PackageComponentsAllInOne(const std::string& compInstDirName);
   const char* GetOutputExtension() override { return ".rpm"; }
+  std::string GetSanitizedDirOrFileName(const std::string& name,
+                                        bool isFullName = true) const override;
   bool SupportsComponentInstallation() const override;
+  std::string GetComponentInstallSuffix(
+    const std::string& componentName) override;
   std::string GetComponentInstallDirNameSuffix(
     const std::string& componentName) override;