Skip to content

Commit a25e59d

Browse files
committed
Added name shortening vault option
1 parent 9c791a5 commit a25e59d

26 files changed

Lines changed: 138 additions & 85 deletions

File tree

‎src/Core/SecureFolderFS.Core.Dokany/AppModels/DokanyOptions.cs‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
using SecureFolderFS.Core.FileSystem.AppModels;
1+
using System;
2+
using System.Collections.Generic;
3+
using SecureFolderFS.Core.FileSystem.AppModels;
24
using SecureFolderFS.Shared.Extensions;
35
using SecureFolderFS.Storage.VirtualFileSystem;
4-
using System;
5-
using System.Collections.Generic;
66

77
namespace SecureFolderFS.Core.Dokany.AppModels
88
{
@@ -36,6 +36,7 @@ public static DokanyOptions ToOptions(IDictionary<string, object> options)
3636
IsCachingFileNames = (bool?)options.Get(nameof(IsCachingFileNames)) ?? true,
3737
IsCachingDirectoryIds = (bool?)options.Get(nameof(IsCachingDirectoryIds)) ?? true,
3838
RecycleBinSize = (long?)options.Get(nameof(RecycleBinSize)) ?? 0L,
39+
ShorteningThreshold = (int?)options.Get(nameof(ShorteningThreshold)) ?? 0,
3940

4041
// Dokany specific
4142
MountPoint = (string?)options.Get(nameof(MountPoint))

‎src/Core/SecureFolderFS.Core.FUSE/AppModels/FuseOptions.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public static FuseOptions ToOptions(IDictionary<string, object> options)
6161
IsCachingFileNames = (bool?)options.Get(nameof(IsCachingFileNames)) ?? true,
6262
IsCachingDirectoryIds = (bool?)options.Get(nameof(IsCachingDirectoryIds)) ?? true,
6363
RecycleBinSize = (long?)options.Get(nameof(RecycleBinSize)) ?? 0L,
64+
ShorteningThreshold = (int?)options.Get(nameof(ShorteningThreshold)) ?? 0,
6465

6566
// FUSE specific
6667
MountPoint = (string?)options.Get(nameof(MountPoint)),

‎src/Core/SecureFolderFS.Core.FileSystem/Helpers/Paths/Abstract/AbstractPathHelpers.Names.cs‎

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -98,36 +98,7 @@ public static async Task<string> EncryptNameForUseAsync(string plaintextName, IF
9898
var result = await GetDirectoryIdAsync(ciphertextParentFolder, specifics, expendableDirectoryId, cancellationToken);
9999

100100
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)
101+
if (specifics.Options.ShorteningThreshold > 0 && encryptedName.Length >= specifics.Options.ShorteningThreshold)
131102
{
132103
var shortenedBase = ComputeShortenedNameBase(encryptedName);
133104
await WriteSidecarAsync(ciphertextParentFolder, shortenedBase, encryptedName, cancellationToken);

‎src/Core/SecureFolderFS.Core.FileSystem/Helpers/Paths/Abstract/AbstractPathHelpers.Shortening.cs‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ namespace SecureFolderFS.Core.FileSystem.Helpers.Paths.Abstract
1313
{
1414
public static partial class AbstractPathHelpers
1515
{
16-
private const int SHORTENING_THRESHOLD = 220;
1716
private const int MAX_SIDECAR_BYTES = 4096; // No legitimate ciphertext name approaches this upper bound
1817

1918
/// <summary>

‎src/Core/SecureFolderFS.Core.FileSystem/Helpers/Paths/PathHelpers.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public static class PathHelpers
1111
public static bool IsCoreName(string itemName)
1212
{
1313
return
14+
itemName.EndsWith(Constants.Names.SIDECAR_FILE_EXTENSION, StringComparison.OrdinalIgnoreCase) ||
1415
itemName.Contains(Constants.Names.DIRECTORY_ID_FILENAME, StringComparison.OrdinalIgnoreCase) ||
1516
itemName.Contains(Constants.Names.RECYCLE_BIN_NAME, StringComparison.OrdinalIgnoreCase) ||
1617
itemName.Contains(Constants.Names.RECYCLE_BIN_CONFIGURATION_FILENAME, StringComparison.OrdinalIgnoreCase);

‎src/Core/SecureFolderFS.Core.FileSystem/Storage/CryptoFolder.cs‎

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using SecureFolderFS.Storage.Extensions;
1616
using SecureFolderFS.Storage.Recyclable;
1717
using SecureFolderFS.Storage.Renamable;
18+
using SecureFolderFS.Storage.VirtualFileSystem;
1819

1920
namespace SecureFolderFS.Core.FileSystem.Storage
2021
{
@@ -43,6 +44,9 @@ public CryptoFolder(string plaintextId, IFolder inner, FileSystemSpecifics speci
4344
/// <inheritdoc/>
4445
public virtual async Task<IStorableChild> RenameAsync(IStorableChild storable, string newName, CancellationToken cancellationToken = default)
4546
{
47+
if (specifics.Options.IsReadOnly)
48+
throw FileSystemExceptions.FileSystemReadOnly;
49+
4650
if (Inner is not IRenamableFolder renamableFolder)
4751
throw new NotSupportedException("Renaming folder contents is not supported.");
4852

@@ -112,6 +116,9 @@ public virtual async Task DeleteAsync(IStorableChild item, CancellationToken can
112116
/// <inheritdoc/>
113117
public virtual async Task DeleteAsync(IStorableChild item, long sizeHint = -1L, bool deleteImmediately = false, CancellationToken cancellationToken = default)
114118
{
119+
if (specifics.Options.IsReadOnly)
120+
throw FileSystemExceptions.FileSystemReadOnly;
121+
115122
if (Inner is not IModifiableFolder modifiableFolder)
116123
throw new NotSupportedException("Modifying folder contents is not supported.");
117124

@@ -138,10 +145,13 @@ public virtual async Task DeleteAsync(IStorableChild item, long sizeHint = -1L,
138145
/// <inheritdoc/>
139146
public virtual async Task<IChildFolder> CreateFolderAsync(string name, bool overwrite = false, CancellationToken cancellationToken = default)
140147
{
148+
if (specifics.Options.IsReadOnly)
149+
throw FileSystemExceptions.FileSystemReadOnly;
150+
141151
if (Inner is not IModifiableFolder modifiableFolder)
142152
throw new NotSupportedException("Modifying folder contents is not supported.");
143153

144-
var encryptedName = await AbstractPathHelpers.EncryptNameAsync(name, Inner, specifics, cancellationToken);
154+
var encryptedName = await AbstractPathHelpers.EncryptNameForUseAsync(name, Inner, specifics, cancellationToken);
145155
var folder = await modifiableFolder.CreateFolderAsync(encryptedName, overwrite, cancellationToken);
146156
if (folder is not IModifiableFolder createdModifiableFolder)
147157
throw new ArgumentException("The created folder is not modifiable.");
@@ -163,10 +173,13 @@ public virtual async Task<IChildFolder> CreateFolderAsync(string name, bool over
163173
/// <inheritdoc/>
164174
public virtual async Task<IChildFile> CreateFileAsync(string name, bool overwrite = false, CancellationToken cancellationToken = default)
165175
{
176+
if (specifics.Options.IsReadOnly)
177+
throw FileSystemExceptions.FileSystemReadOnly;
178+
166179
if (Inner is not IModifiableFolder modifiableFolder)
167180
throw new NotSupportedException("Modifying folder contents is not supported.");
168181

169-
var encryptedName = await AbstractPathHelpers.EncryptNameAsync(name, Inner, specifics, cancellationToken);
182+
var encryptedName = await AbstractPathHelpers.EncryptNameForUseAsync(name, Inner, specifics, cancellationToken);
170183
var file = await modifiableFolder.CreateFileAsync(encryptedName, overwrite, cancellationToken);
171184

172185
return (IChildFile)Wrap(file, name);
@@ -183,6 +196,9 @@ public virtual Task<IChildFile> CreateCopyOfAsync(IFile fileToCopy, bool overwri
183196
public virtual async Task<IChildFile> CreateCopyOfAsync(IFile fileToCopy, bool overwrite, string newName, CancellationToken cancellationToken,
184197
CreateRenamedCopyOfDelegate fallback)
185198
{
199+
if (specifics.Options.IsReadOnly)
200+
throw FileSystemExceptions.FileSystemReadOnly;
201+
186202
if (Inner is not IModifiableFolder)
187203
throw new NotSupportedException("Modifying folder contents is not supported.");
188204

@@ -195,7 +211,7 @@ public virtual async Task<IChildFile> CreateCopyOfAsync(IFile fileToCopy, bool o
195211
return await fallback(this, fileToCopy, overwrite, newName, cancellationToken);
196212

197213
// Encrypt the new name
198-
var ciphertextNewName = await AbstractPathHelpers.EncryptNameAsync(newName, Inner, specifics, cancellationToken);
214+
var ciphertextNewName = await AbstractPathHelpers.EncryptNameForUseAsync(newName, Inner, specifics, cancellationToken);
199215

200216
// Copy the ciphertext file
201217
var copiedCiphertextFile = await createRenamedCopyOf.CreateCopyOfAsync(ciphertextFileToCopy, overwrite, ciphertextNewName, cancellationToken);
@@ -213,6 +229,9 @@ public virtual Task<IChildFile> MoveFromAsync(IChildFile fileToMove, IModifiable
213229
public virtual async Task<IChildFile> MoveFromAsync(IChildFile fileToMove, IModifiableFolder source, bool overwrite, string newName,
214230
CancellationToken cancellationToken, MoveRenamedFromDelegate fallback)
215231
{
232+
if (specifics.Options.IsReadOnly)
233+
throw FileSystemExceptions.FileSystemReadOnly;
234+
216235
if (Inner is not IModifiableFolder)
217236
throw new NotSupportedException("Modifying folder contents is not supported.");
218237

@@ -231,7 +250,7 @@ public virtual async Task<IChildFile> MoveFromAsync(IChildFile fileToMove, IModi
231250
return await fallback(this, fileToMove, source, overwrite, newName, cancellationToken);
232251

233252
// Encrypt the new name
234-
var newCiphertextName = await AbstractPathHelpers.EncryptNameAsync(newName, Inner, specifics, cancellationToken);
253+
var newCiphertextName = await AbstractPathHelpers.EncryptNameForUseAsync(newName, Inner, specifics, cancellationToken);
235254

236255
// Move the ciphertext file
237256
var movedCiphertextFile = await moveRenamedFrom.MoveFromAsync(ciphertextFileToMove, ciphertextSourceModifiableFolder, overwrite, newCiphertextName, cancellationToken, fallback);

‎src/Core/SecureFolderFS.Core.WebDav/AppModels/WebDavOptions.cs‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
using SecureFolderFS.Core.FileSystem.AppModels;
1+
using System;
2+
using System.Collections.Generic;
3+
using SecureFolderFS.Core.FileSystem.AppModels;
24
using SecureFolderFS.Shared.Extensions;
35
using SecureFolderFS.Storage.VirtualFileSystem;
4-
using System;
5-
using System.Collections.Generic;
66

77
namespace SecureFolderFS.Core.WebDav.AppModels
88
{
@@ -49,6 +49,7 @@ public static WebDavOptions ToOptions(IDictionary<string, object> options)
4949
IsCachingFileNames = (bool?)options.Get(nameof(IsCachingFileNames)) ?? true,
5050
IsCachingDirectoryIds = (bool?)options.Get(nameof(IsCachingDirectoryIds)) ?? true,
5151
RecycleBinSize = (long?)options.Get(nameof(RecycleBinSize)) ?? 0L,
52+
ShorteningThreshold = (int?)options.Get(nameof(ShorteningThreshold)) ?? 0,
5253

5354
// WebDav specific
5455
Protocol = (string?)options.Get(nameof(Protocol)) ?? "http",

‎src/Core/SecureFolderFS.Core.WinFsp/AppModels/WinFspOptions.cs‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
using SecureFolderFS.Core.FileSystem.AppModels;
1+
using System;
2+
using System.Collections.Generic;
3+
using SecureFolderFS.Core.FileSystem.AppModels;
24
using SecureFolderFS.Shared.Extensions;
35
using SecureFolderFS.Storage.VirtualFileSystem;
4-
using System;
5-
using System.Collections.Generic;
66

77
namespace SecureFolderFS.Core.WinFsp.AppModels
88
{
@@ -36,6 +36,7 @@ public static WinFspOptions ToOptions(IDictionary<string, object> options)
3636
IsCachingFileNames = (bool?)options.Get(nameof(IsCachingFileNames)) ?? true,
3737
IsCachingDirectoryIds = (bool?)options.Get(nameof(IsCachingDirectoryIds)) ?? true,
3838
RecycleBinSize = (long?)options.Get(nameof(RecycleBinSize)) ?? 0L,
39+
ShorteningThreshold = (int?)options.Get(nameof(ShorteningThreshold)) ?? 0,
3940

4041
// WinFsp specific
4142
MountPoint = (string?)options.Get(nameof(MountPoint))

‎src/Core/SecureFolderFS.Core/Constants.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public static class Associations
4545
public const string ASSOC_CONTENT_CIPHER_ID = "contentCipherScheme";
4646
public const string ASSOC_FILENAME_CIPHER_ID = "filenameCipherScheme";
4747
public const string ASSOC_FILENAME_ENCODING_ID = "filenameEncoding";
48+
public const string ASSOC_FILENAME_SHORTENING = "filenameShortening";
4849
public const string ASSOC_RECYCLE_SIZE = "recycleBinSize";
4950
public const string ASSOC_AUTHENTICATION = "authMode";
5051
public const string ASSOC_VAULT_ID = "vaultId";

‎src/Core/SecureFolderFS.Core/DataModels/V4VaultConfigurationDataModel.cs‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ public sealed record class V4VaultConfigurationDataModel : VersionDataModel
2929
/// </summary>
3030
[JsonPropertyName(Associations.ASSOC_FILENAME_ENCODING_ID)]
3131
[DefaultValue("")]
32-
public string FileNameEncodingId { get; set; } = Cryptography.Constants.CipherId.ENCODING_BASE64URL;
32+
public required string FileNameEncodingId { get; set; } = Cryptography.Constants.CipherId.ENCODING_BASE64URL;
33+
34+
/// <summary>
35+
/// Gets the threshold for shortening file names.
36+
/// </summary>
37+
[JsonPropertyName(Associations.ASSOC_FILENAME_SHORTENING)]
38+
[DefaultValue(0)]
39+
public required int ShorteningThreshold { get; init; }
3340

3441
/// <summary>
3542
/// Gets the size of the recycle bin.
@@ -77,6 +84,7 @@ public static V4VaultConfigurationDataModel V4FromVaultOptions(VaultOptions vaul
7784
ContentCipherId = vaultOptions.ContentCipherId ?? Cryptography.Constants.CipherId.XCHACHA20_POLY1305,
7885
FileNameCipherId = vaultOptions.FileNameCipherId ?? Cryptography.Constants.CipherId.AES_SIV,
7986
FileNameEncodingId = vaultOptions.NameEncodingId ?? Cryptography.Constants.CipherId.ENCODING_BASE64URL,
87+
ShorteningThreshold = vaultOptions.ShorteningThreshold,
8088
AuthenticationMethod = vaultOptions.UnlockProcedure.ToString(),
8189
RecycleBinSize = vaultOptions.RecycleBinSize,
8290
Uid = vaultOptions.VaultId ?? Guid.NewGuid().ToString(),

0 commit comments

Comments
 (0)