11using System ;
22using System . Collections . Generic ;
33using System . Security . Cryptography ;
4+ using System . Text ;
45using System . Threading ;
56using System . Threading . Tasks ;
67using OwlCore . Storage ;
@@ -69,16 +70,37 @@ public async Task<IDisposable> FinalizeAsync(CancellationToken cancellationToken
6970 string ? foundNameCrypt = null ;
7071 string ? foundEncoding = null ;
7172 var noExtensions = 0 ;
72- var shorteningThreshold = 0 ;
73+ var minSidecarContentLength = int . MaxValue ;
74+ var hasShortenedNames = false ;
7375
7476 var folderScanner = new DeepFolderScanner ( contentFolder , StorableType . File ) ;
7577 await foreach ( var item in folderScanner . ScanFolderAsync ( cancellationToken ) )
7678 {
7779 if ( item is not IFile file )
7880 continue ;
7981
80- if ( shorteningThreshold == 0 && item . Name . EndsWith ( FileSystem . Constants . Names . SHORTENED_FILE_EXTENSION , StringComparison . OrdinalIgnoreCase ) )
81- shorteningThreshold = 220 ; // Arbitrary threshold typical for shortened names
82+ // Read sidecar files to determine the shortening threshold from the actual ciphertext name length
83+ if ( item . Name . EndsWith ( FileSystem . Constants . Names . SIDECAR_FILE_EXTENSION , StringComparison . OrdinalIgnoreCase ) )
84+ {
85+ try
86+ {
87+ await using var sidecarStream = await file . OpenReadAsync ( cancellationToken ) ;
88+ var buffer = new byte [ 4097 ] ;
89+ var bytesRead = await sidecarStream . ReadAsync ( buffer . AsMemory ( 0 , buffer . Length ) , cancellationToken ) ;
90+ if ( bytesRead is > 0 and <= 4096 )
91+ minSidecarContentLength = Math . Min ( minSidecarContentLength , Encoding . UTF8 . GetString ( buffer , 0 , bytesRead ) . Length ) ;
92+ }
93+ catch { }
94+
95+ continue ;
96+ }
97+
98+ // Shortened files are hashes that can't be decrypted directly
99+ if ( item . Name . EndsWith ( FileSystem . Constants . Names . SHORTENED_FILE_EXTENSION , StringComparison . OrdinalIgnoreCase ) )
100+ {
101+ hasShortenedNames = true ;
102+ continue ;
103+ }
82104
83105 if ( ! item . Name . EndsWith ( FileSystem . Constants . Names . ENCRYPTED_FILE_EXTENSION , StringComparison . OrdinalIgnoreCase ) )
84106 noExtensions ++ ;
@@ -102,6 +124,11 @@ public async Task<IDisposable> FinalizeAsync(CancellationToken cancellationToken
102124 if ( foundNameCrypt is null || foundEncoding is null || foundContentCrypt is null )
103125 throw new InvalidOperationException ( "Could not find all required cryptographic components." ) ;
104126
127+ // Determine shortening threshold from sidecar content, with fallback for missing sidecars
128+ var shorteningThreshold = minSidecarContentLength < int . MaxValue
129+ ? minSidecarContentLength
130+ : hasShortenedNames ? 220 : 0 ;
131+
105132 // Regenerate config
106133 var configDataModel = new V4VaultConfigurationDataModel ( )
107134 {
0 commit comments