Skip to content

Commit 604d1d0

Browse files
JKamskerJoy-less
andauthored
Fix/url encoded mutex (#2709)
* Replace Mutex name hashing with URI escaping * Make SharedMutexNameStrategy configurable to switch back to legacy if needed * Adjust member visibility * Re-introduce normalization in sha1 hash * Falling back to sha1 if uri encode is not safe * Refactor SharedMutexNameFactory to remove NETSTANDARD2_0 preprocessor directives and simplify OS detection --------- Co-authored-by: Joyless <[email protected]>
1 parent ff4d67c commit 604d1d0

File tree

5 files changed

+108
-19
lines changed

5 files changed

+108
-19
lines changed

LiteDB/Client/Shared/SharedEngine.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Collections.Generic;
44
using System.IO;
55
using System.Threading;
6+
using LiteDB.Client.Shared;
67
using LiteDB.Vector;
78

89
namespace LiteDB
@@ -18,7 +19,7 @@ public SharedEngine(EngineSettings settings)
1819
{
1920
_settings = settings;
2021

21-
var name = Path.GetFullPath(settings.Filename).ToLower().Sha1();
22+
var name = SharedMutexNameFactory.Create(settings.Filename, settings.SharedMutexNameStrategy);
2223

2324
try
2425
{
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using System;
2+
using System.IO;
3+
using System.Runtime.InteropServices;
4+
using System.Security.Cryptography;
5+
using System.Text;
6+
using LiteDB.Engine;
7+
8+
namespace LiteDB.Client.Shared;
9+
10+
internal static class SharedMutexNameFactory
11+
{
12+
// Effective Windows limit for named mutexes (conservative).
13+
private const int WINDOWS_MUTEX_NAME_MAX = 250;
14+
15+
// If the caller adds "Global\" (7 chars) + the name + ".Mutex" (7 chars) to the mutex name,
16+
// we account for it conservatively here without baking it into the return value.
17+
// Adjust if your caller prepends something longer.
18+
private const int CONSERVATIVE_EXTERNAL_PREFIX_LENGTH = 13; // e.g., "Global\\" + name + ".Mutex"
19+
20+
internal static string Create(string fileName, SharedMutexNameStrategy strategy)
21+
{
22+
return strategy switch
23+
{
24+
SharedMutexNameStrategy.Default => CreateUsingUriEncodingWithFallback(fileName),
25+
SharedMutexNameStrategy.UriEscape => CreateUsingUriEncoding(fileName),
26+
SharedMutexNameStrategy.Sha1Hash => CreateUsingSha1(fileName),
27+
_ => throw new ArgumentOutOfRangeException(nameof(strategy), strategy, null)
28+
};
29+
}
30+
31+
private static string CreateUsingUriEncodingWithFallback(string fileName)
32+
{
33+
var normalized = Normalize(fileName);
34+
var uri = Uri.EscapeDataString(normalized);
35+
36+
if (IsWindows() &&
37+
uri.Length + CONSERVATIVE_EXTERNAL_PREFIX_LENGTH > WINDOWS_MUTEX_NAME_MAX)
38+
{
39+
// Short, stable fallback well under the limit.
40+
return "sha1-" + ComputeSha1Hex(normalized);
41+
}
42+
43+
return uri;
44+
}
45+
46+
private static string CreateUsingUriEncoding(string fileName)
47+
{
48+
var normalized = Normalize(fileName);
49+
var uri = Uri.EscapeDataString(normalized);
50+
51+
if (IsWindows() &&
52+
uri.Length + CONSERVATIVE_EXTERNAL_PREFIX_LENGTH > WINDOWS_MUTEX_NAME_MAX)
53+
{
54+
// Fallback to SHA to avoid ArgumentException on Windows.
55+
return "sha1-" + ComputeSha1Hex(normalized);
56+
}
57+
58+
return uri;
59+
}
60+
61+
private static bool IsWindows()
62+
{
63+
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
64+
}
65+
66+
internal static string CreateUsingSha1(string value)
67+
{
68+
var normalized = Normalize(value);
69+
return ComputeSha1Hex(normalized);
70+
}
71+
72+
private static string Normalize(string path)
73+
{
74+
// Invariant casing + absolute path yields stable identity.
75+
return Path.GetFullPath(path).ToLowerInvariant();
76+
}
77+
78+
private static string ComputeSha1Hex(string input)
79+
{
80+
var data = Encoding.UTF8.GetBytes(input);
81+
using var sha = SHA1.Create();
82+
var hashData = sha.ComputeHash(data);
83+
84+
var sb = new StringBuilder(hashData.Length * 2);
85+
foreach (var b in hashData)
86+
{
87+
sb.Append(b.ToString("X2"));
88+
}
89+
90+
return sb.ToString();
91+
}
92+
}

LiteDB/Engine/EngineSettings.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ public class EngineSettings
7171
/// Is used to transform a <see cref="BsonValue"/> from the database on read. This can be used to upgrade data from older versions.
7272
/// </summary>
7373
public Func<string, BsonValue, BsonValue> ReadTransform { get; set; }
74+
75+
/// <summary>
76+
/// Determines how the mutex name is generated.
77+
/// </summary>
78+
public SharedMutexNameStrategy SharedMutexNameStrategy { get; set; }
7479

7580
/// <summary>
7681
/// Create new IStreamFactory for datafile
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+

2+
namespace LiteDB.Engine;
3+
4+
public enum SharedMutexNameStrategy
5+
{
6+
Default,
7+
UriEscape,
8+
Sha1Hash
9+
}

LiteDB/Utils/Extensions/StringExtensions.cs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,6 @@ public static bool IsWord(this string str)
3737
return true;
3838
}
3939

40-
public static string Sha1(this string value)
41-
{
42-
var data = Encoding.UTF8.GetBytes(value);
43-
44-
using (var sha = SHA1.Create())
45-
{
46-
var hashData = sha.ComputeHash(data);
47-
var hash = new StringBuilder();
48-
49-
foreach (var b in hashData)
50-
{
51-
hash.Append(b.ToString("X2"));
52-
}
53-
54-
return hash.ToString();
55-
}
56-
}
57-
5840
/// <summary>
5941
/// Implement SqlLike in C# string - based on
6042
/// https://stackoverflow.com/a/8583383/3286260

0 commit comments

Comments
 (0)