@@ -9,6 +9,8 @@ namespace SecureFolderFS.Core.FileSystem.Helpers.Paths.Abstract
99{
1010 public static partial class AbstractPathHelpers
1111 {
12+ #region Encrypt Name Non-Materialized
13+
1214 /// <inheritdoc cref="EncryptNameAsync(string,OwlCore.Storage.IFolder,SecureFolderFS.Core.FileSystem.FileSystemSpecifics,System.Byte[],System.Threading.CancellationToken)"/>
1315 public static async Task < string > EncryptNameAsync ( string plaintextName , IFolder ciphertextParentFolder ,
1416 FileSystemSpecifics specifics , CancellationToken cancellationToken = default )
@@ -62,6 +64,81 @@ public static async Task<string> EncryptNameAsync(string plaintextName, IFolder
6264 return security . NameCrypt . EncryptName ( plaintextName , result ? directoryId : ReadOnlySpan < byte > . Empty ) + Constants . Names . ENCRYPTED_FILE_EXTENSION ;
6365 }
6466
67+ #endregion
68+
69+ #region Encrypt Name Materialized
70+
71+ /// <inheritdoc cref="EncryptNameForUseAsync(string,OwlCore.Storage.IFolder,SecureFolderFS.Core.FileSystem.FileSystemSpecifics,System.Byte[],System.Threading.CancellationToken)"/>
72+ public static async Task < string > EncryptNameForUseAsync ( string plaintextName , IFolder ciphertextParentFolder ,
73+ FileSystemSpecifics specifics , CancellationToken cancellationToken = default )
74+ {
75+ if ( specifics . Security . NameCrypt is null )
76+ return plaintextName ;
77+
78+ var directoryId = AllocateDirectoryId ( specifics . Security , plaintextName ) ;
79+ return await EncryptNameForUseAsync ( plaintextName , ciphertextParentFolder , specifics , directoryId , cancellationToken ) ;
80+ }
81+
82+ /// <summary>
83+ /// Encrypts the provided <paramref name="plaintextName"/> and materializes it.
84+ /// </summary>
85+ /// <param name="plaintextName">The name to encrypt.</param>
86+ /// <param name="ciphertextParentFolder">The ciphertext parent folder.</param>
87+ /// <param name="specifics">The <see cref="FileSystemSpecifics"/> instance associated with the item.</param>
88+ /// <param name="expendableDirectoryId">A buffer of size <see cref="Constants.DIRECTORY_ID_SIZE"/> which will be used to hold the Directory ID data.</param>
89+ /// <param name="cancellationToken">A <see cref="CancellationToken"/> that cancels this action.</param>
90+ /// <returns>A <see cref="Task"/> that represents the asynchronous operation. Value is an encrypted name with the appropriate file extension appended.</returns>
91+ public static async Task < string > EncryptNameForUseAsync ( string plaintextName , IFolder ciphertextParentFolder ,
92+ FileSystemSpecifics specifics , byte [ ] ? expendableDirectoryId = null , CancellationToken cancellationToken = default )
93+ {
94+ if ( specifics . Security . NameCrypt is null )
95+ return plaintextName ;
96+
97+ expendableDirectoryId ??= AllocateDirectoryId ( specifics . Security , plaintextName ) ;
98+ var result = await GetDirectoryIdAsync ( ciphertextParentFolder , specifics , expendableDirectoryId , cancellationToken ) ;
99+
100+ var encryptedName = specifics . Security . NameCrypt . EncryptName ( plaintextName , result ? expendableDirectoryId : ReadOnlySpan < byte > . Empty ) + Constants . Names . ENCRYPTED_FILE_EXTENSION ;
101+ if ( SHORTENING_THRESHOLD > 0 && encryptedName . Length >= SHORTENING_THRESHOLD )
102+ {
103+ var shortenedBase = ComputeShortenedNameBase ( encryptedName ) ;
104+ await WriteSidecarAsync ( ciphertextParentFolder , shortenedBase , encryptedName , cancellationToken ) ;
105+ return shortenedBase + Constants . Names . SHORTENED_FILE_EXTENSION ;
106+ }
107+
108+ return encryptedName ;
109+ }
110+
111+ /// <summary>
112+ /// Encrypts the provided <paramref name="plaintextName"/> and materializes it.
113+ /// </summary>
114+ /// <param name="plaintextName">The name to encrypt.</param>
115+ /// <param name="ciphertextParentFolder">The ciphertext parent folder.</param>
116+ /// <param name="contentFolder">The content folder.</param>
117+ /// <param name="security">The <see cref="Security"/> instance associated with the item.</param>
118+ /// <param name="cancellationToken">A <see cref="CancellationToken"/> that cancels this action.</param>
119+ /// <returns>A <see cref="Task"/> that represents the asynchronous operation. Value is an encrypted name with the appropriate file extension appended.</returns>
120+ public static async Task < string > EncryptNameForUseAsync ( string plaintextName , IFolder ciphertextParentFolder , IFolder contentFolder ,
121+ Security security , CancellationToken cancellationToken = default )
122+ {
123+ if ( security . NameCrypt is null )
124+ return plaintextName ;
125+
126+ var directoryId = AllocateDirectoryId ( security , plaintextName ) ;
127+ var result = await GetDirectoryIdAsync ( ciphertextParentFolder , contentFolder , directoryId , cancellationToken ) ;
128+
129+ var encryptedName = security . NameCrypt . EncryptName ( plaintextName , result ? directoryId : ReadOnlySpan < byte > . Empty ) + Constants . Names . ENCRYPTED_FILE_EXTENSION ;
130+ if ( SHORTENING_THRESHOLD > 0 && encryptedName . Length >= SHORTENING_THRESHOLD )
131+ {
132+ var shortenedBase = ComputeShortenedNameBase ( encryptedName ) ;
133+ await WriteSidecarAsync ( ciphertextParentFolder , shortenedBase , encryptedName , cancellationToken ) ;
134+ return shortenedBase + Constants . Names . SHORTENED_FILE_EXTENSION ;
135+ }
136+
137+ return encryptedName ;
138+ }
139+
140+ #endregion
141+
65142 /// <summary>
66143 /// Encrypts a plaintext name using the specified Directory ID and security parameters.
67144 /// </summary>
@@ -103,8 +180,23 @@ public static string EncryptNewName(string plaintextName, byte[] newDirectoryId,
103180 if ( specifics . Security . NameCrypt is null )
104181 return ciphertextName ;
105182
183+ // Sidecar files are internal bookkeeping - they have no plaintext name
184+ if ( IsSidecarName ( ciphertextName ) )
185+ return null ;
186+
106187 try
107188 {
189+ // Resolve shortened names to their full ciphertext name via the paired sidecar
190+ if ( ciphertextName . EndsWith ( Constants . Names . SHORTENED_FILE_EXTENSION , StringComparison . OrdinalIgnoreCase ) )
191+ {
192+ var shortenedBase = RemoveShortenedExtension ( ciphertextName ) . ToString ( ) ;
193+ var resolvedName = await ReadSidecarAsync ( ciphertextParentFolder , shortenedBase , cancellationToken ) ;
194+ if ( resolvedName is null )
195+ return null ;
196+
197+ ciphertextName = resolvedName ;
198+ }
199+
108200 expendableDirectoryId ??= AllocateDirectoryId ( specifics . Security , ciphertextName ) ;
109201 var result = await GetDirectoryIdAsync ( ciphertextParentFolder , specifics , expendableDirectoryId , cancellationToken ) ;
110202
0 commit comments