Skip to content

Define and enforce one portable Unicode path policy #10

Description

@tuki0918

Summary

Swift and C# do not currently implement the same definition of a portable output path:

  • Swift uses NFC followed by Foundation full case folding.
  • C# uses NFC followed by StringComparer.OrdinalIgnoreCase.
  • The rejected control and format-character sets differ.
  • Superscript Windows device names such as COM¹ and LPT² are missing.
  • There is no shared component or full-path length contract.

This conflicts with the format claim that both implementations enforce the same portability rules.

Impact

One platform can create or accept a package that the other rejects or cannot extract. Some accepted names are not creatable on Windows, and oversized components can pass inspection but fail at extraction time.

Evidence

  • Docs/FORMAT.md:55-63,122-126
  • macOS/BundlePack/Shared/ZipArchiveInspector.Validation.swift:228-277
  • macOS/BundlePack/App/PackageBuilder.IO.swift:196-215,386-410
  • Windows/BundlePack.Core/FileHelpers.cs:8-31
  • Windows/BundlePack.Core/BundlePackArchive.Validation.cs:156-176,245-277
  • Windows/BundlePack.Core/BundlePackService.IO.cs:288-304

Reproduction cases

  • payload/straße.txt and payload/STRASSE.txt
  • final sigma, Turkish I, and NFC versus NFD pairs
  • U+007F, U+0085, bidi controls, ZWJ, and ZWNJ
  • COM¹.txt, COM².txt, COM³.txt, and the corresponding LPT names
  • components at the UTF-8 and UTF-16 255/256 boundaries

The Swift full-fold behavior was measured locally. Windows runtime confirmation is still required for the exact OrdinalIgnoreCase differential cases.

Proposed change

  • Define a normative canonical output-path key in Docs/FORMAT.md.
  • Pin or explicitly version the Unicode behavior used by the contract.
  • Specify the exact rejected scalar categories and exceptions.
  • Complete the Windows device-name set.
  • Define conservative UTF-8 byte and UTF-16 code-unit component limits plus a full relative-path limit.
  • Use the same vectors in reader, writer, and extraction-name tests on both platforms.

Acceptance criteria

  • A language-neutral fixture lists each path and its expected canonical key or rejection code.
  • Swift and C# produce identical results for the complete fixture.
  • Writers reject collisions before copying input data.
  • Readers continue to reject absolute paths, .., ADS-like names, trailing spaces and periods, and case-insensitive or NFC collisions.
  • Extraction failures caused solely by an accepted component length are covered by tests and eliminated.

Compatibility constraints

Keep UTF-8 entry names, / separators, NFC collision protection, existing traversal defenses, and the rule that readers never silently rename an accepted stored path. Any intentionally incompatible rule needs a coordinated format-version decision.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions