Skip to content

FileSystem/DataHome/ConfigHome/Temp persistence can't store non-ASCII keys of ~83+ chars on Linux/macOS: StoreAsync throws PathTooLongException #48

Description

@matt-edmondson

What's wrong

PersistenceProviderUtilities.GetSafeFileName (Essentials/PersistenceProviderUtilities.cs:91) truncates and hashes a key only when encoded.Length > MaxEncodedLength (100). That limit counts UTF-16 chars, but Linux and macOS limit a file-name component to 255 bytes.

FileSystemPersistenceProvider then adds more to the name:

  • GetFilePath (line 233) appends the serializer's FileExtension.
  • StoreAsync (line 72) appends ".tmp" on top of that.

A key of multibyte characters can therefore stay under 100 chars, avoid truncation, and still produce a file name longer than 255 bytes.

Repro

This was run on Linux (ext4/tmpfs) with a console program built against current main.

var key = new string('日', 90);   // 90 chars, 270 UTF-8 bytes
PersistenceProviderUtilities.GetSafeFileName(key); // returned unchanged (90 chars)
await provider.StoreAsync(key, "v");
// PersistenceProviderException
//   ---> PathTooLongException: The path '/tmp/.../日日…日.json.tmp' is too long,
//        or a component of the specified path is too long.
  • After that, RetrieveAsync and ExistsAsync quietly report the key as missing.
  • A 300-char ASCII key works, because it gets truncated and hashed.
  • The threshold is about 83 CJK characters, or fewer for emoji and other 4-byte characters.

DataHome, ConfigHome and Temp persistence all delegate storage to the file-system provider, so they fail the same way.

Why it matters

Keys derived from user content such as titles, file names or search terms routinely contain non-ASCII text. On Linux and macOS these keys can't be stored even though an equally long English key can, and the failure only shows up at runtime. Windows is unaffected because NTFS counts UTF-16 units.

Suggested fix / acceptance criteria

  • Base the truncation decision on the byte length as well. For example, truncate when Encoding.UTF8.GetByteCount(encoded) exceeds a budget of 255 minus the longest suffix that gets appended (extension plus .tmp), rounded down to something like 200 for headroom.
  • When truncating, cut the prefix at a character boundary so that no surrogate pair is split.
  • Make sure IsTruncatedName still recognises the new shape.
  • Add tests that store and retrieve CJK and emoji keys long enough to exceed 255 bytes, and assert round-trip success.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    readyFully specified; implement as written

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions