@@ -5,46 +5,184 @@ namespace SecureFolderFS.Core.FileSystem.Helpers.Paths.Native
55{
66 public static partial class NativePathHelpers
77 {
8- public static string EncryptName ( string plaintextName , string plaintextParentFolder ,
8+ #region Encrypt Name Non-Materialized
9+
10+ /// <inheritdoc cref="EncryptName(string,string,FileSystemSpecifics,Span{byte})"/>
11+ public static string EncryptName ( string plaintextName , string ciphertextParentFolder ,
912 FileSystemSpecifics specifics )
1013 {
1114 if ( specifics . Security . NameCrypt is null )
1215 return plaintextName ;
1316
1417 var directoryId = AbstractPathHelpers . AllocateDirectoryId ( specifics . Security , plaintextName ) ;
15- return EncryptName ( plaintextName , plaintextParentFolder , specifics , directoryId ) ;
18+ return EncryptName ( plaintextName , ciphertextParentFolder , specifics , directoryId ) ;
1619 }
1720
18- public static string EncryptName ( string plaintextName , string plaintextParentFolder ,
21+ /// <summary>
22+ /// Encrypts the provided <paramref name="plaintextName"/>.
23+ /// </summary>
24+ /// <param name="plaintextName">The name to encrypt.</param>
25+ /// <param name="ciphertextParentFolder">The ciphertext parent folder path.</param>
26+ /// <param name="specifics">The <see cref="FileSystemSpecifics"/> instance associated with the item.</param>
27+ /// <param name="expendableDirectoryId">A <see cref="Span{T}"/> of size <see cref="Constants.DIRECTORY_ID_SIZE"/> which will be used to hold the Directory ID data.</param>
28+ /// <returns>An encrypted name with the appropriate file extension appended.</returns>
29+ public static string EncryptName ( string plaintextName , string ciphertextParentFolder ,
1930 FileSystemSpecifics specifics , Span < byte > expendableDirectoryId )
2031 {
2132 if ( specifics . Security . NameCrypt is null )
2233 return plaintextName ;
2334
24- var result = GetDirectoryId ( plaintextParentFolder , specifics , expendableDirectoryId ) ;
35+ var result = GetDirectoryId ( ciphertextParentFolder , specifics , expendableDirectoryId ) ;
2536 return specifics . Security . NameCrypt . EncryptName ( plaintextName , result ? expendableDirectoryId : ReadOnlySpan < byte > . Empty ) + Constants . Names . ENCRYPTED_FILE_EXTENSION ;
2637 }
2738
39+ #endregion
40+
41+ #region Encrypt Name Materialized
42+
43+ /// <inheritdoc cref="EncryptNameForUse(string,string,FileSystemSpecifics,Span{byte})"/>
44+ public static string EncryptNameForUse ( string plaintextName , string ciphertextParentFolder ,
45+ FileSystemSpecifics specifics )
46+ {
47+ if ( specifics . Security . NameCrypt is null )
48+ return plaintextName ;
49+
50+ var directoryId = AbstractPathHelpers . AllocateDirectoryId ( specifics . Security , plaintextName ) ;
51+ return EncryptNameForUse ( plaintextName , ciphertextParentFolder , specifics , directoryId ) ;
52+ }
53+
54+ /// <summary>
55+ /// Encrypts the provided <paramref name="plaintextName"/> and materializes it.
56+ /// If the encrypted name exceeds the shortening threshold, a sidecar file is written and the shortened name is returned.
57+ /// </summary>
58+ /// <param name="plaintextName">The name to encrypt.</param>
59+ /// <param name="ciphertextParentFolder">The ciphertext parent folder path.</param>
60+ /// <param name="specifics">The <see cref="FileSystemSpecifics"/> instance associated with the item.</param>
61+ /// <param name="expendableDirectoryId">A <see cref="Span{T}"/> of size <see cref="Constants.DIRECTORY_ID_SIZE"/> which will be used to hold the Directory ID data.</param>
62+ /// <returns>An encrypted name with the appropriate file extension appended.</returns>
63+ public static string EncryptNameForUse ( string plaintextName , string ciphertextParentFolder ,
64+ FileSystemSpecifics specifics , Span < byte > expendableDirectoryId )
65+ {
66+ if ( specifics . Security . NameCrypt is null )
67+ return plaintextName ;
68+
69+ var result = GetDirectoryId ( ciphertextParentFolder , specifics , expendableDirectoryId ) ;
70+ var encryptedName = specifics . Security . NameCrypt . EncryptName ( plaintextName , result ? expendableDirectoryId : ReadOnlySpan < byte > . Empty ) + Constants . Names . ENCRYPTED_FILE_EXTENSION ;
71+
72+ if ( specifics . Options . ShorteningThreshold > 0 && encryptedName . Length >= specifics . Options . ShorteningThreshold )
73+ {
74+ var shortenedBase = AbstractPathHelpers . ComputeShortenedNameBase ( encryptedName ) ;
75+ WriteSidecar ( ciphertextParentFolder , shortenedBase , encryptedName ) ;
76+ return shortenedBase + Constants . Names . SHORTENED_FILE_EXTENSION ;
77+ }
78+
79+ return encryptedName ;
80+ }
81+
82+ #endregion
83+
84+ #region Encrypt Name Discoverability
85+
86+ /// <inheritdoc cref="EncryptNameForDiscovery(string,string,FileSystemSpecifics,Span{byte})"/>
87+ public static string EncryptNameForDiscovery ( string plaintextName , string ciphertextParentFolder ,
88+ FileSystemSpecifics specifics )
89+ {
90+ if ( specifics . Security . NameCrypt is null )
91+ return plaintextName ;
92+
93+ var directoryId = AbstractPathHelpers . AllocateDirectoryId ( specifics . Security , plaintextName ) ;
94+ return EncryptNameForDiscovery ( plaintextName , ciphertextParentFolder , specifics , directoryId ) ;
95+ }
96+
97+ /// <summary>
98+ /// Encrypts the provided <paramref name="plaintextName"/> and discovers potential shortening branch.
99+ /// Unlike <see cref="EncryptNameForUse(string,string,FileSystemSpecifics,Span{byte})"/>, this method does not write a sidecar file.
100+ /// </summary>
101+ /// <param name="plaintextName">The name to encrypt.</param>
102+ /// <param name="ciphertextParentFolder">The ciphertext parent folder path.</param>
103+ /// <param name="specifics">The <see cref="FileSystemSpecifics"/> instance associated with the item.</param>
104+ /// <param name="expendableDirectoryId">A <see cref="Span{T}"/> of size <see cref="Constants.DIRECTORY_ID_SIZE"/> which will be used to hold the Directory ID data.</param>
105+ /// <returns>An encrypted name with the appropriate file extension appended.</returns>
106+ public static string EncryptNameForDiscovery ( string plaintextName , string ciphertextParentFolder ,
107+ FileSystemSpecifics specifics , Span < byte > expendableDirectoryId )
108+ {
109+ if ( specifics . Security . NameCrypt is null )
110+ return plaintextName ;
111+
112+ var result = GetDirectoryId ( ciphertextParentFolder , specifics , expendableDirectoryId ) ;
113+ var encryptedName = specifics . Security . NameCrypt . EncryptName ( plaintextName , result ? expendableDirectoryId : ReadOnlySpan < byte > . Empty ) + Constants . Names . ENCRYPTED_FILE_EXTENSION ;
114+
115+ if ( specifics . Options . ShorteningThreshold > 0 && encryptedName . Length >= specifics . Options . ShorteningThreshold )
116+ {
117+ var shortenedBase = AbstractPathHelpers . ComputeShortenedNameBase ( encryptedName ) ;
118+ return shortenedBase + Constants . Names . SHORTENED_FILE_EXTENSION ;
119+ }
120+
121+ return encryptedName ;
122+ }
123+
124+ #endregion
125+
126+ #region Decrypt Name
127+
128+ /// <inheritdoc cref="DecryptName(string,string,FileSystemSpecifics,Span{byte})"/>
28129 public static string ? DecryptName ( string ciphertextName , string ciphertextParentFolder ,
29130 FileSystemSpecifics specifics )
30131 {
31132 if ( specifics . Security . NameCrypt is null )
32133 return ciphertextName ;
33134
135+ // Sidecar files are internal bookkeeping - they have no plaintext name
136+ if ( AbstractPathHelpers . IsSidecarName ( ciphertextName ) )
137+ return null ;
138+
34139 var directoryId = AbstractPathHelpers . AllocateDirectoryId ( specifics . Security , ciphertextName ) ;
35140 return DecryptName ( ciphertextName , ciphertextParentFolder , specifics , directoryId ) ;
36141 }
37142
143+ /// <summary>
144+ /// Decrypts the provided <paramref name="ciphertextName"/>.
145+ /// Resolves shortened names (<c>.sffsn</c>) to their full ciphertext name via the paired sidecar before decrypting.
146+ /// </summary>
147+ /// <param name="ciphertextName">The name to decrypt.</param>
148+ /// <param name="ciphertextParentFolder">The ciphertext parent folder path.</param>
149+ /// <param name="specifics">The <see cref="FileSystemSpecifics"/> instance associated with the item.</param>
150+ /// <param name="expendableDirectoryId">A <see cref="Span{T}"/> of size <see cref="Constants.DIRECTORY_ID_SIZE"/> which will be used to hold the Directory ID data.</param>
151+ /// <returns>A decrypted name, or <see langword="null"/> if decryption fails.</returns>
38152 public static string ? DecryptName ( string ciphertextName , string ciphertextParentFolder ,
39153 FileSystemSpecifics specifics , Span < byte > expendableDirectoryId )
40154 {
41155 if ( specifics . Security . NameCrypt is null )
42156 return ciphertextName ;
43157
44- var result = GetDirectoryId ( ciphertextParentFolder , specifics , expendableDirectoryId ) ;
45- var normalizedName = AbstractPathHelpers . RemoveCiphertextExtension ( ciphertextName ) ;
158+ // Sidecar files are internal bookkeeping - they have no plaintext name
159+ if ( AbstractPathHelpers . IsSidecarName ( ciphertextName ) )
160+ return null ;
161+
162+ try
163+ {
164+ // Resolve shortened names to their full ciphertext name via the paired sidecar
165+ if ( ciphertextName . EndsWith ( Constants . Names . SHORTENED_FILE_EXTENSION , StringComparison . OrdinalIgnoreCase ) )
166+ {
167+ var shortenedBase = AbstractPathHelpers . RemoveShortenedExtension ( ciphertextName ) . ToString ( ) ;
168+ var resolvedName = ReadSidecar ( ciphertextParentFolder , shortenedBase ) ;
169+ if ( resolvedName is null )
170+ return null ;
46171
47- return specifics . Security . NameCrypt . DecryptName ( normalizedName , result ? expendableDirectoryId : ReadOnlySpan < byte > . Empty ) ;
172+ ciphertextName = resolvedName ;
173+ }
174+
175+ var result = GetDirectoryId ( ciphertextParentFolder , specifics , expendableDirectoryId ) ;
176+ var normalizedName = AbstractPathHelpers . RemoveCiphertextExtension ( ciphertextName ) ;
177+
178+ return specifics . Security . NameCrypt . DecryptName ( normalizedName , result ? expendableDirectoryId : ReadOnlySpan < byte > . Empty ) ;
179+ }
180+ catch ( Exception )
181+ {
182+ return null ;
183+ }
48184 }
185+
186+ #endregion
49187 }
50188}
0 commit comments