diff --git a/ModernKeePassLib.Test/Keys/KcpKeyFileTests.cs b/ModernKeePassLib.Test/Keys/KcpKeyFileTests.cs
index e51fad2..647edc3 100644
--- a/ModernKeePassLib.Test/Keys/KcpKeyFileTests.cs
+++ b/ModernKeePassLib.Test/Keys/KcpKeyFileTests.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Threading.Tasks;
using ModernKeePassLib.Keys;
using ModernKeePassLib.Utility;
using Windows.Storage;
@@ -24,7 +25,7 @@ namespace ModernKeePassLib.Test.Keys
private const string ExpectedFileEnd = "\r\n\t\r\n";
[Fact]
- public void TestConstruct()
+ public async Task TestConstruct()
{
var expectedKeyData = new byte[]
{
@@ -34,16 +35,14 @@ namespace ModernKeePassLib.Test.Keys
0x45, 0xfc, 0xc8, 0x92, 0xbd, 0xeb, 0xaf, 0xc3
};
- var folder = StorageFolder.GetFolderFromPathAsync(Path.GetTempPath()).GetAwaiter().GetResult();
- var file = folder.CreateFileAsync(TestCreateFile, CreationCollisionOption.ReplaceExisting).GetAwaiter().GetResult();
- using (var fs = file.OpenStreamForWriteAsync().GetAwaiter().GetResult())
+ var folder = await StorageFolder.GetFolderFromPathAsync(Path.GetTempPath());
+ var file = await folder.CreateFileAsync(TestCreateFile, CreationCollisionOption.ReplaceExisting);
+ await using (var fs = await file.OpenStreamForWriteAsync())
{
- using (var sw = new StreamWriter(fs))
- {
- sw.Write(ExpectedFileStart);
- sw.Write(TestKey);
- sw.Write(ExpectedFileEnd);
- }
+ await using var sw = new StreamWriter(fs);
+ sw.Write(ExpectedFileStart);
+ sw.Write(TestKey);
+ sw.Write(ExpectedFileEnd);
}
try
@@ -54,19 +53,19 @@ namespace ModernKeePassLib.Test.Keys
}
finally
{
- file.DeleteAsync().GetAwaiter().GetResult();
+ await file.DeleteAsync();
}
}
[Fact]
- public void TestCreate()
+ public async Task TestCreate()
{
- var folder = StorageFolder.GetFolderFromPathAsync(Path.GetTempPath()).GetAwaiter().GetResult();
- var file = folder.CreateFileAsync(TestCreateFile, CreationCollisionOption.ReplaceExisting).GetAwaiter().GetResult();
+ var folder = await StorageFolder.GetFolderFromPathAsync(Path.GetTempPath());
+ var file = await folder.CreateFileAsync(TestCreateFile, CreationCollisionOption.ReplaceExisting);
KcpKeyFile.Create(file, null);
try
{
- var fileContents = FileIO.ReadTextAsync(file).GetAwaiter().GetResult();
+ var fileContents = await FileIO.ReadTextAsync(file);
Assert.Equal(185, fileContents.Length);
Assert.StartsWith(ExpectedFileStart, fileContents);
@@ -74,7 +73,7 @@ namespace ModernKeePassLib.Test.Keys
}
finally
{
- file.DeleteAsync().GetAwaiter().GetResult();
+ await file.DeleteAsync();
}
}
}
diff --git a/ModernKeePassLib.UwpTest/Assets/LockScreenLogo.scale-200.png b/ModernKeePassLib.UwpTest/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..735f57a
Binary files /dev/null and b/ModernKeePassLib.UwpTest/Assets/LockScreenLogo.scale-200.png differ
diff --git a/ModernKeePassLib.UwpTest/Assets/SplashScreen.scale-200.png b/ModernKeePassLib.UwpTest/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..023e7f1
Binary files /dev/null and b/ModernKeePassLib.UwpTest/Assets/SplashScreen.scale-200.png differ
diff --git a/ModernKeePassLib.UwpTest/Assets/Square150x150Logo.scale-200.png b/ModernKeePassLib.UwpTest/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..af49fec
Binary files /dev/null and b/ModernKeePassLib.UwpTest/Assets/Square150x150Logo.scale-200.png differ
diff --git a/ModernKeePassLib.UwpTest/Assets/Square44x44Logo.scale-200.png b/ModernKeePassLib.UwpTest/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..ce342a2
Binary files /dev/null and b/ModernKeePassLib.UwpTest/Assets/Square44x44Logo.scale-200.png differ
diff --git a/ModernKeePassLib.UwpTest/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/ModernKeePassLib.UwpTest/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..f6c02ce
Binary files /dev/null and b/ModernKeePassLib.UwpTest/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/ModernKeePassLib.UwpTest/Assets/StoreLogo.png b/ModernKeePassLib.UwpTest/Assets/StoreLogo.png
new file mode 100644
index 0000000..7385b56
Binary files /dev/null and b/ModernKeePassLib.UwpTest/Assets/StoreLogo.png differ
diff --git a/ModernKeePassLib.UwpTest/Assets/Wide310x150Logo.scale-200.png b/ModernKeePassLib.UwpTest/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..288995b
Binary files /dev/null and b/ModernKeePassLib.UwpTest/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/ModernKeePassLib.UwpTest/KcpKeyFileTests.cs b/ModernKeePassLib.UwpTest/KcpKeyFileTests.cs
new file mode 100644
index 0000000..524e522
--- /dev/null
+++ b/ModernKeePassLib.UwpTest/KcpKeyFileTests.cs
@@ -0,0 +1,86 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Windows.Storage;
+using Windows.Storage.AccessCache;
+using ModernKeePassLib.Keys;
+using ModernKeePassLib.Utility;
+using Xunit;
+
+namespace ModernKeePassLib.UwpTest
+{
+ public class KcpKeyFileTests
+ {
+ private const string TestCreateFile = "TestCreate.xml";
+ private const string TestKey = "0123456789";
+
+ private const string ExpectedFileStart =
+ "\r\n" +
+ "\r\n" +
+ "\t\r\n" +
+ "\t\t1.00\r\n" +
+ "\t\r\n" +
+ "\t\r\n" +
+ "\t\t";
+
+ private const string ExpectedFileEnd = "\r\n\t\r\n";
+
+ [Fact]
+ public async Task TestConstruct()
+ {
+ var expectedKeyData = new byte[]
+ {
+ 0x95, 0x94, 0xdc, 0xb9, 0x91, 0xc6, 0x65, 0xa0,
+ 0x81, 0xf6, 0x6f, 0xca, 0x07, 0x1a, 0x30, 0xd1,
+ 0x1d, 0x65, 0xcf, 0x8d, 0x9c, 0x60, 0xfb, 0xe6,
+ 0x45, 0xfc, 0xc8, 0x92, 0xbd, 0xeb, 0xaf, 0xc3
+ };
+
+ var folder = await StorageFolder.GetFolderFromPathAsync(Path.GetTempPath());
+ var file = await folder.CreateFileAsync(TestCreateFile, CreationCollisionOption.ReplaceExisting);
+ var token = StorageApplicationPermissions.FutureAccessList.Add(file);
+ using (var fs = await file.OpenStreamForWriteAsync())
+ {
+ using (var sw = new StreamWriter(fs))
+ {
+ sw.Write(ExpectedFileStart);
+ sw.Write(TestKey);
+ sw.Write(ExpectedFileEnd);
+ }
+ }
+
+ try
+ {
+ var keyFile = new KcpKeyFile(token);
+ var keyData = keyFile.KeyData.ReadData();
+ Assert.True(MemUtil.ArraysEqual(keyData, expectedKeyData));
+ }
+ finally
+ {
+ await file.DeleteAsync();
+ }
+ }
+
+ [Fact]
+ public async Task TestCreate()
+ {
+ var folder = await StorageFolder.GetFolderFromPathAsync(Path.GetTempPath());
+ var file = await folder.CreateFileAsync(TestCreateFile, CreationCollisionOption.ReplaceExisting);
+ var token = StorageApplicationPermissions.FutureAccessList.Add(file);
+ KcpKeyFile.Create(token, null);
+ try
+ {
+ var fileContents = await FileIO.ReadTextAsync(file);
+
+ Assert.Equal(185, fileContents.Length);
+ Assert.StartsWith(ExpectedFileStart, fileContents);
+ Assert.EndsWith(ExpectedFileEnd, fileContents);
+ }
+ finally
+ {
+ await file.DeleteAsync();
+ }
+ }
+ }
+}
+
diff --git a/ModernKeePassLib.UwpTest/ModernKeePassLib.UwpTest.csproj b/ModernKeePassLib.UwpTest/ModernKeePassLib.UwpTest.csproj
new file mode 100644
index 0000000..dc8adae
--- /dev/null
+++ b/ModernKeePassLib.UwpTest/ModernKeePassLib.UwpTest.csproj
@@ -0,0 +1,179 @@
+
+
+
+
+ Debug
+ x86
+ {E43877FF-6725-43DA-A3AF-F1A7CB86EE03}
+ AppContainerExe
+ Properties
+ ModernKeePassLib.UwpTest
+ ModernKeePassLib.UwpTest
+ en-US
+ UAP
+ 10.0.17763.0
+ 10.0.17763.0
+ 14
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ $(VisualStudioVersion)
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x86
+ false
+ prompt
+ true
+
+
+ bin\x86\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x86
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM
+ false
+ prompt
+ true
+
+
+ bin\ARM\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\ARM64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ bin\ARM64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ ARM64
+ false
+ prompt
+ true
+ true
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ ;2008
+ full
+ x64
+ false
+ prompt
+ true
+
+
+ bin\x64\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ true
+ ;2008
+ pdbonly
+ x64
+ false
+ prompt
+ true
+ true
+
+
+ PackageReference
+
+
+
+
+
+
+
+
+ UnitTestApp.xaml
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 6.2.8
+
+
+ 1.4.0
+
+
+ 1.4.0
+
+
+ 2.4.1
+
+
+
+
+ {15a7e2e5-2b46-4975-afbd-2898e47e5ba1}
+ ModernKeePassLib
+
+
+
+ 14.0
+
+
+
+
\ No newline at end of file
diff --git a/ModernKeePassLib.UwpTest/Package.appxmanifest b/ModernKeePassLib.UwpTest/Package.appxmanifest
new file mode 100644
index 0000000..b350b75
--- /dev/null
+++ b/ModernKeePassLib.UwpTest/Package.appxmanifest
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+ ModernKeePassLib.UwpTest
+ GeoffroyBONNEVILLE
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ModernKeePassLib.UwpTest/Properties/AssemblyInfo.cs b/ModernKeePassLib.UwpTest/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..cc4d859
--- /dev/null
+++ b/ModernKeePassLib.UwpTest/Properties/AssemblyInfo.cs
@@ -0,0 +1,18 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("ModernKeePassLib.UwpTest")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ModernKeePassLib.UwpTest")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: AssemblyMetadata("TargetPlatform","UAP")]
+
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: ComVisible(false)]
\ No newline at end of file
diff --git a/ModernKeePassLib.UwpTest/Properties/Default.rd.xml b/ModernKeePassLib.UwpTest/Properties/Default.rd.xml
new file mode 100644
index 0000000..996a839
--- /dev/null
+++ b/ModernKeePassLib.UwpTest/Properties/Default.rd.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ModernKeePassLib.UwpTest/UnitTestApp.xaml b/ModernKeePassLib.UwpTest/UnitTestApp.xaml
new file mode 100644
index 0000000..eeb9bca
--- /dev/null
+++ b/ModernKeePassLib.UwpTest/UnitTestApp.xaml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/ModernKeePassLib.UwpTest/UnitTestApp.xaml.cs b/ModernKeePassLib.UwpTest/UnitTestApp.xaml.cs
new file mode 100644
index 0000000..41c3425
--- /dev/null
+++ b/ModernKeePassLib.UwpTest/UnitTestApp.xaml.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.ApplicationModel;
+using Windows.ApplicationModel.Activation;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+
+namespace ModernKeePassLib.UwpTest
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ sealed partial class App : Application
+ {
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ this.Suspending += OnSuspending;
+ }
+
+ ///
+ /// Invoked when the application is launched normally by the end user. Other entry points
+ /// will be used such as when the application is launched to open a specific file.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(LaunchActivatedEventArgs e)
+ {
+
+#if DEBUG
+ if (System.Diagnostics.Debugger.IsAttached)
+ {
+ this.DebugSettings.EnableFrameRateCounter = true;
+ }
+#endif
+
+ Frame rootFrame = Window.Current.Content as Frame;
+
+ // Do not repeat app initialization when the Window already has content,
+ // just ensure that the window is active
+ if (rootFrame == null)
+ {
+ // Create a Frame to act as the navigation context and navigate to the first page
+ rootFrame = new Frame();
+
+ rootFrame.NavigationFailed += OnNavigationFailed;
+
+ if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
+ {
+ //TODO: Load state from previously suspended application
+ }
+
+ // Place the frame in the current Window
+ Window.Current.Content = rootFrame;
+ }
+
+ Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.CreateDefaultUI();
+
+ // Ensure the current window is active
+ Window.Current.Activate();
+
+ Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.Run(e.Arguments);
+ }
+
+ ///
+ /// Invoked when Navigation to a certain page fails
+ ///
+ /// The Frame which failed navigation
+ /// Details about the navigation failure
+ void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
+ {
+ throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
+ }
+
+ ///
+ /// Invoked when application execution is being suspended. Application state is saved
+ /// without knowing whether the application will be terminated or resumed with the contents
+ /// of memory still intact.
+ ///
+ /// The source of the suspend request.
+ /// Details about the suspend request.
+ private void OnSuspending(object sender, SuspendingEventArgs e)
+ {
+ var deferral = e.SuspendingOperation.GetDeferral();
+ //TODO: Save application state and stop any background activity
+ deferral.Complete();
+ }
+ }
+}
diff --git a/ModernKeePassLib.sln b/ModernKeePassLib.sln
index 5477438..05b52e1 100644
--- a/ModernKeePassLib.sln
+++ b/ModernKeePassLib.sln
@@ -10,17 +10,57 @@ EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|ARM = Debug|ARM
+ Debug|ARM64 = Debug|ARM64
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|ARM = Release|ARM
+ Release|ARM64 = Release|ARM64
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Debug|ARM.Build.0 = Debug|Any CPU
+ {15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Debug|x64.Build.0 = Debug|Any CPU
+ {15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Debug|x86.Build.0 = Debug|Any CPU
{15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Release|ARM.ActiveCfg = Release|Any CPU
+ {15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Release|ARM.Build.0 = Release|Any CPU
+ {15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Release|ARM64.Build.0 = Release|Any CPU
+ {15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Release|x64.ActiveCfg = Release|Any CPU
+ {15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Release|x64.Build.0 = Release|Any CPU
+ {15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Release|x86.ActiveCfg = Release|Any CPU
+ {15A7E2E5-2B46-4975-AFBD-2898E47E5BA1}.Release|x86.Build.0 = Release|Any CPU
{4797E768-279A-493A-B21B-CD96AB1F8C20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4797E768-279A-493A-B21B-CD96AB1F8C20}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4797E768-279A-493A-B21B-CD96AB1F8C20}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {4797E768-279A-493A-B21B-CD96AB1F8C20}.Debug|ARM.Build.0 = Debug|Any CPU
+ {4797E768-279A-493A-B21B-CD96AB1F8C20}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {4797E768-279A-493A-B21B-CD96AB1F8C20}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {4797E768-279A-493A-B21B-CD96AB1F8C20}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4797E768-279A-493A-B21B-CD96AB1F8C20}.Debug|x64.Build.0 = Debug|Any CPU
+ {4797E768-279A-493A-B21B-CD96AB1F8C20}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4797E768-279A-493A-B21B-CD96AB1F8C20}.Debug|x86.Build.0 = Debug|Any CPU
{4797E768-279A-493A-B21B-CD96AB1F8C20}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4797E768-279A-493A-B21B-CD96AB1F8C20}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4797E768-279A-493A-B21B-CD96AB1F8C20}.Release|ARM.ActiveCfg = Release|Any CPU
+ {4797E768-279A-493A-B21B-CD96AB1F8C20}.Release|ARM.Build.0 = Release|Any CPU
+ {4797E768-279A-493A-B21B-CD96AB1F8C20}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {4797E768-279A-493A-B21B-CD96AB1F8C20}.Release|ARM64.Build.0 = Release|Any CPU
+ {4797E768-279A-493A-B21B-CD96AB1F8C20}.Release|x64.ActiveCfg = Release|Any CPU
+ {4797E768-279A-493A-B21B-CD96AB1F8C20}.Release|x64.Build.0 = Release|Any CPU
+ {4797E768-279A-493A-B21B-CD96AB1F8C20}.Release|x86.ActiveCfg = Release|Any CPU
+ {4797E768-279A-493A-B21B-CD96AB1F8C20}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/ModernKeePassLib/Cryptography/CryptoRandomStream.cs b/ModernKeePassLib/Cryptography/CryptoRandomStream.cs
index edadfbc..e17fc83 100644
--- a/ModernKeePassLib/Cryptography/CryptoRandomStream.cs
+++ b/ModernKeePassLib/Cryptography/CryptoRandomStream.cs
@@ -225,6 +225,25 @@ namespace ModernKeePassLib.Cryptography
return MemUtil.BytesToUInt64(pb);
}
+ internal ulong GetRandomUInt64(ulong uMaxExcl)
+ {
+ if(uMaxExcl == 0) { Debug.Assert(false); throw new ArgumentOutOfRangeException("uMaxExcl"); }
+
+ ulong uGen, uRem;
+ do
+ {
+ uGen = GetRandomUInt64();
+ uRem = uGen % uMaxExcl;
+ }
+ while((uGen - uRem) > (ulong.MaxValue - (uMaxExcl - 1UL)));
+ // This ensures that the last number of the block (i.e.
+ // (uGen - uRem) + (uMaxExcl - 1)) is generatable;
+ // for signed longs, overflow to negative number:
+ // while((uGen - uRem) + (uMaxExcl - 1) < 0);
+
+ return uRem;
+ }
+
#if CRSBENCHMARK
public static string Benchmark()
{
diff --git a/ModernKeePassLib/Cryptography/CryptoStreamEx.cs b/ModernKeePassLib/Cryptography/CryptoStreamEx.cs
index 3b4e70b..a7beb03 100644
--- a/ModernKeePassLib/Cryptography/CryptoStreamEx.cs
+++ b/ModernKeePassLib/Cryptography/CryptoStreamEx.cs
@@ -28,37 +28,39 @@ using System.Security.Cryptography;
namespace ModernKeePassLib.Cryptography
{
- public sealed class CryptoStreamEx : CryptoStream
- {
- private ICryptoTransform m_t;
- private SymmetricAlgorithm m_a;
+ public sealed class CryptoStreamEx : CryptoStream
+ {
+ private ICryptoTransform m_t;
+ private SymmetricAlgorithm m_a;
- public CryptoStreamEx(Stream s, ICryptoTransform t, CryptoStreamMode m,
- SymmetricAlgorithm a) : base(s, t, m)
- {
- m_t = t;
- m_a = a;
- }
+ public CryptoStreamEx(Stream s, ICryptoTransform t, CryptoStreamMode m,
+ SymmetricAlgorithm a) : base(s, t, m)
+ {
+ m_t = t;
+ m_a = a;
+ }
- protected override void Dispose(bool disposing)
- {
- try { base.Dispose(disposing); }
- // Unnecessary exception from CryptoStream with
- // RijndaelManagedTransform when a stream hasn't been
- // read completely (e.g. incorrect master key)
- catch (CryptographicException) { }
- catch (Exception) { Debug.Assert(false); }
+ protected override void Dispose(bool disposing)
+ {
+ try { base.Dispose(disposing); }
+ // Unnecessary exception from CryptoStream with
+ // RijndaelManagedTransform when a stream hasn't been
+ // read completely (e.g. incorrect master key)
+ catch(CryptographicException) { }
+ // Similar to above, at the beginning of the stream
+ catch(IndexOutOfRangeException) { }
+ catch(Exception) { Debug.Assert(false); }
- if (disposing)
- {
- try { if (m_t != null) { m_t.Dispose(); m_t = null; } }
- catch (Exception) { Debug.Assert(false); }
+ if(disposing)
+ {
+ try { if(m_t != null) { m_t.Dispose(); m_t = null; } }
+ catch(Exception) { Debug.Assert(false); }
- // In .NET 2.0, SymmetricAlgorithm.Dispose() is not public
- try { if (m_a != null) { m_a.Clear(); m_a = null; } }
- catch (Exception) { Debug.Assert(false); }
- }
- }
- }
+ // In .NET 2.0, SymmetricAlgorithm.Dispose() is not public
+ try { if(m_a != null) { m_a.Clear(); m_a = null; } }
+ catch(Exception) { Debug.Assert(false); }
+ }
+ }
+ }
}
#endif
diff --git a/ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.cs b/ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.cs
index dcb92ee..ec78e39 100644
--- a/ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.cs
+++ b/ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.cs
@@ -42,13 +42,13 @@ namespace ModernKeePassLib.Cryptography.KeyDerivation
private const uint MaxVersion = 0x13;
private const int MinSalt = 8;
- private const int MaxSalt = int.MaxValue; // .NET limit; 2^32 - 1 in spec
+ private const int MaxSalt = int.MaxValue; // .NET limit; 2^32 - 1 in spec.
internal const ulong MinIterations = 1;
internal const ulong MaxIterations = uint.MaxValue;
internal const ulong MinMemory = 1024 * 8; // For parallelism = 1
- // internal const ulong MaxMemory = (ulong)uint.MaxValue * 1024UL; // Spec
+ // internal const ulong MaxMemory = (ulong)uint.MaxValue * 1024UL; // Spec.
internal const ulong MaxMemory = int.MaxValue; // .NET limit
internal const uint MinParallelism = 1;
diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs
index a50df78..763a0a3 100644
--- a/ModernKeePassLib/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs
+++ b/ModernKeePassLib/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs
@@ -36,20 +36,20 @@ namespace ModernKeePassLib.Cryptography.PasswordGenerator
if(pwProfile.Length == 0) return PwgError.Success;
PwCharSet pcs = new PwCharSet(pwProfile.CharSet.ToString());
- PwGenerator.PrepareCharSet(pcs, pwProfile);
+ if(!PwGenerator.PrepareCharSet(pcs, pwProfile))
+ return PwgError.InvalidCharSet;
char[] v = new char[pwProfile.Length];
try
{
for(int i = 0; i < v.Length; ++i)
{
- char ch = PwGenerator.GenerateCharacter(pwProfile,
- pcs, crsRandomSource);
-
+ char ch = PwGenerator.GenerateCharacter(pcs, crsRandomSource);
if(ch == char.MinValue)
return PwgError.TooFewCharacters;
v[i] = ch;
+ if(pwProfile.NoRepeatingCharacters) pcs.Remove(ch);
}
byte[] pbUtf8 = StrUtil.Utf8.GetBytes(v);
diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/PatternBasedGenerator.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/PatternBasedGenerator.cs
index 55773e6..b06ac42 100644
--- a/ModernKeePassLib/Cryptography/PasswordGenerator/PatternBasedGenerator.cs
+++ b/ModernKeePassLib/Cryptography/PasswordGenerator/PatternBasedGenerator.cs
@@ -34,92 +34,61 @@ namespace ModernKeePassLib.Cryptography.PasswordGenerator
{
psOut = ProtectedString.Empty;
+ string strPattern = pwProfile.Pattern;
+ if(string.IsNullOrEmpty(strPattern)) return PwgError.Success;
+
+ CharStream cs = new CharStream(strPattern);
LinkedList llGenerated = new LinkedList();
- PwCharSet pcsCurrent = new PwCharSet();
- PwCharSet pcsCustom = new PwCharSet();
- PwCharSet pcsUsed = new PwCharSet();
- bool bInCharSetDef = false;
+ PwCharSet pcs = new PwCharSet();
- string strPattern = ExpandPattern(pwProfile.Pattern);
- if(strPattern.Length == 0) return PwgError.Success;
-
- CharStream csStream = new CharStream(strPattern);
- char ch = csStream.ReadChar();
-
- while(ch != char.MinValue)
+ while(true)
{
- pcsCurrent.Clear();
+ char ch = cs.ReadChar();
+ if(ch == char.MinValue) break;
- bool bGenerateChar = false;
+ pcs.Clear();
if(ch == '\\')
{
- ch = csStream.ReadChar();
- if(ch == char.MinValue) // Backslash at the end
- {
- llGenerated.AddLast('\\');
- break;
- }
+ ch = cs.ReadChar();
+ if(ch == char.MinValue) return PwgError.InvalidPattern;
- if(bInCharSetDef) pcsCustom.Add(ch);
- else
- {
- llGenerated.AddLast(ch);
- pcsUsed.Add(ch);
- }
- }
- else if(ch == '^')
- {
- ch = csStream.ReadChar();
- if(ch == char.MinValue) // ^ at the end
- {
- llGenerated.AddLast('^');
- break;
- }
-
- if(bInCharSetDef) pcsCustom.Remove(ch);
+ pcs.Add(ch); // Allow "{...}" support and char check
}
else if(ch == '[')
{
- pcsCustom.Clear();
- bInCharSetDef = true;
+ if(!ReadCustomCharSet(cs, pcs))
+ return PwgError.InvalidPattern;
}
- else if(ch == ']')
+ else
{
- pcsCurrent.Add(pcsCustom.ToString());
+ if(!pcs.AddCharSet(ch))
+ return PwgError.InvalidPattern;
+ }
- bInCharSetDef = false;
- bGenerateChar = true;
- }
- else if(bInCharSetDef)
+ int nCount = 1;
+ if(cs.PeekChar() == '{')
{
- if(pcsCustom.AddCharSet(ch) == false)
- pcsCustom.Add(ch);
+ nCount = ReadCount(cs);
+ if(nCount < 0) return PwgError.InvalidPattern;
}
- else if(pcsCurrent.AddCharSet(ch) == false)
- {
- llGenerated.AddLast(ch);
- pcsUsed.Add(ch);
- }
- else bGenerateChar = true;
- if(bGenerateChar)
+ for(int i = 0; i < nCount; ++i)
{
- PwGenerator.PrepareCharSet(pcsCurrent, pwProfile);
-
+ if(!PwGenerator.PrepareCharSet(pcs, pwProfile))
+ return PwgError.InvalidCharSet;
if(pwProfile.NoRepeatingCharacters)
- pcsCurrent.Remove(pcsUsed.ToString());
-
- char chGen = PwGenerator.GenerateCharacter(pwProfile,
- pcsCurrent, crsRandomSource);
+ {
+ foreach(char chUsed in llGenerated)
+ pcs.Remove(chUsed);
+ }
+ char chGen = PwGenerator.GenerateCharacter(pcs,
+ crsRandomSource);
if(chGen == char.MinValue) return PwgError.TooFewCharacters;
llGenerated.AddLast(chGen);
- pcsUsed.Add(chGen);
}
-
- ch = csStream.ReadChar();
}
if(llGenerated.Count == 0) return PwgError.Success;
@@ -135,53 +104,70 @@ namespace ModernKeePassLib.Cryptography.PasswordGenerator
MemUtil.ZeroByteArray(pbUtf8);
MemUtil.ZeroArray(v);
- llGenerated.Clear();
-
return PwgError.Success;
}
- private static string ExpandPattern(string strPattern)
+ private static bool ReadCustomCharSet(CharStream cs, PwCharSet pcsOut)
{
- if(strPattern == null) { Debug.Assert(false); return string.Empty; }
-
- string str = strPattern;
+ Debug.Assert(cs.PeekChar() != '['); // Consumed already
+ Debug.Assert(pcsOut.Size == 0);
+ bool bAdd = true;
while(true)
{
- int nOpen = FindFirstUnescapedChar(str, '{');
- int nClose = FindFirstUnescapedChar(str, '}');
+ char ch = cs.ReadChar();
+ if(ch == char.MinValue) return false;
+ if(ch == ']') break;
- if((nOpen >= 0) && (nOpen < nClose))
+ if(ch == '\\')
{
- string strCount = str.Substring(nOpen + 1, nClose - nOpen - 1);
- str = str.Remove(nOpen, nClose - nOpen + 1);
+ ch = cs.ReadChar();
+ if(ch == char.MinValue) return false;
- uint uRepeat;
- if(StrUtil.TryParseUInt(strCount, out uRepeat) && (nOpen >= 1))
- {
- if(uRepeat == 0)
- str = str.Remove(nOpen - 1, 1);
- else
- str = str.Insert(nOpen, new string(str[nOpen - 1], (int)uRepeat - 1));
- }
+ if(bAdd) pcsOut.Add(ch);
+ else pcsOut.Remove(ch);
+ }
+ else if(ch == '^')
+ {
+ if(bAdd) bAdd = false;
+ else return false; // '^' toggles the mode only once
+ }
+ else
+ {
+ PwCharSet pcs = new PwCharSet();
+ if(!pcs.AddCharSet(ch)) return false;
+
+ if(bAdd) pcsOut.Add(pcs.ToString());
+ else pcsOut.Remove(pcs.ToString());
}
- else break;
}
- return str;
+ return true;
}
- private static int FindFirstUnescapedChar(string str, char ch)
+ private static int ReadCount(CharStream cs)
{
- for(int i = 0; i < str.Length; ++i)
- {
- char chCur = str[i];
+ if(cs.ReadChar() != '{') { Debug.Assert(false); return -1; }
- if(chCur == '\\') ++i; // Next is escaped, skip it
- else if(chCur == ch) return i;
+ // Ensure not empty
+ char chFirst = cs.PeekChar();
+ if((chFirst < '0') || (chFirst > '9')) return -1;
+
+ long n = 0;
+ while(true)
+ {
+ char ch = cs.ReadChar();
+ if(ch == '}') break;
+
+ if((ch >= '0') && (ch <= '9'))
+ {
+ n = (n * 10L) + (long)(ch - '0');
+ if(n > int.MaxValue) return -1;
+ }
+ else return -1;
}
- return -1;
+ return (int)n;
}
}
}
diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/PwCharSet.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/PwCharSet.cs
index 4774b7f..463de3f 100644
--- a/ModernKeePassLib/Cryptography/PasswordGenerator/PwCharSet.cs
+++ b/ModernKeePassLib/Cryptography/PasswordGenerator/PwCharSet.cs
@@ -43,7 +43,6 @@ namespace ModernKeePassLib.Cryptography.PasswordGenerator
public static readonly string UpperHex = "0123456789ABCDEF";
public static readonly string LowerHex = "0123456789abcdef";
- public static readonly string Invalid = "\t\r\n";
public static readonly string LookAlike = @"O0l1I|";
internal static readonly string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits;
diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/PwGenerator.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/PwGenerator.cs
index 472bec0..b2f33f7 100644
--- a/ModernKeePassLib/Cryptography/PasswordGenerator/PwGenerator.cs
+++ b/ModernKeePassLib/Cryptography/PasswordGenerator/PwGenerator.cs
@@ -36,7 +36,9 @@ namespace ModernKeePassLib.Cryptography.PasswordGenerator
Success = 0,
Unknown = 1,
TooFewCharacters = 2,
- UnknownAlgorithm = 3
+ UnknownAlgorithm = 3,
+ InvalidCharSet = 4,
+ InvalidPattern = 5
}
///
@@ -94,30 +96,33 @@ namespace ModernKeePassLib.Cryptography.PasswordGenerator
return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey);
}
- internal static char GenerateCharacter(PwProfile pwProfile,
- PwCharSet pwCharSet, CryptoRandomStream crsRandomSource)
+ internal static char GenerateCharacter(PwCharSet pwCharSet,
+ CryptoRandomStream crsRandomSource)
{
- if(pwCharSet.Size == 0) return char.MinValue;
+ uint cc = pwCharSet.Size;
+ if(cc == 0) return char.MinValue;
- ulong uIndex = crsRandomSource.GetRandomUInt64();
- uIndex %= (ulong)pwCharSet.Size;
-
- char ch = pwCharSet[(uint)uIndex];
-
- if(pwProfile.NoRepeatingCharacters)
- pwCharSet.Remove(ch);
-
- return ch;
+ uint i = (uint)crsRandomSource.GetRandomUInt64(cc);
+ return pwCharSet[i];
}
- internal static void PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile)
+ internal static bool PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile)
{
- pwCharSet.Remove(PwCharSet.Invalid);
+ uint cc = pwCharSet.Size;
+ for(uint i = 0; i < cc; ++i)
+ {
+ char ch = pwCharSet[i];
+ if((ch == char.MinValue) || (ch == '\t') || (ch == '\r') ||
+ (ch == '\n') || char.IsSurrogate(ch))
+ return false;
+ }
if(pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike);
- if(pwProfile.ExcludeCharacters.Length > 0)
+ if(!string.IsNullOrEmpty(pwProfile.ExcludeCharacters))
pwCharSet.Remove(pwProfile.ExcludeCharacters);
+
+ return true;
}
internal static void Shuffle(char[] v, CryptoRandomStream crsRandomSource)
@@ -127,8 +132,7 @@ namespace ModernKeePassLib.Cryptography.PasswordGenerator
for(int i = v.Length - 1; i >= 1; --i)
{
- ulong r = crsRandomSource.GetRandomUInt64();
- int j = (int)(r % (ulong)(i + 1));
+ int j = (int)crsRandomSource.GetRandomUInt64((ulong)(i + 1));
char t = v[i];
v[i] = v[j];
diff --git a/ModernKeePassLib/Interfaces/IStatusLogger.cs b/ModernKeePassLib/Interfaces/IStatusLogger.cs
index 8a275cd..3675210 100644
--- a/ModernKeePassLib/Interfaces/IStatusLogger.cs
+++ b/ModernKeePassLib/Interfaces/IStatusLogger.cs
@@ -87,7 +87,7 @@ namespace ModernKeePassLib.Interfaces
bool SetText(string strNewText, LogStatusType lsType);
///
- /// Check if the user cancelled the current work.
+ /// Check whether the user cancelled the current work.
///
/// Returns true if the caller should continue
/// the current work.
diff --git a/ModernKeePassLib/Interfaces/ITimeLogger.cs b/ModernKeePassLib/Interfaces/ITimeLogger.cs
index 20dd502..4c6fd73 100644
--- a/ModernKeePassLib/Interfaces/ITimeLogger.cs
+++ b/ModernKeePassLib/Interfaces/ITimeLogger.cs
@@ -66,7 +66,7 @@ namespace ModernKeePassLib.Interfaces
}
///
- /// Flag that determines if the object does expire.
+ /// Flag that determines whether the object expires.
///
bool Expires
{
diff --git a/ModernKeePassLib/Keys/CompositeKey.cs b/ModernKeePassLib/Keys/CompositeKey.cs
index a9dcff9..cdcce64 100644
--- a/ModernKeePassLib/Keys/CompositeKey.cs
+++ b/ModernKeePassLib/Keys/CompositeKey.cs
@@ -21,9 +21,12 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
+using System.Threading;
using ModernKeePassLib.Cryptography;
using ModernKeePassLib.Cryptography.KeyDerivation;
+using ModernKeePassLib.Interfaces;
+using ModernKeePassLib.Native;
using ModernKeePassLib.Resources;
using ModernKeePassLib.Security;
using ModernKeePassLib.Utility;
@@ -166,7 +169,6 @@ namespace ModernKeePassLib.Keys
{
ValidateUserKeys();
- // Concatenate user key data
List lData = new List();
int cbData = 0;
foreach(IUserKey pKey in m_vUserKeys)
@@ -199,13 +201,17 @@ namespace ModernKeePassLib.Keys
{
if(ckOther == null) throw new ArgumentNullException("ckOther");
+ bool bEqual;
byte[] pbThis = CreateRawCompositeKey32();
- byte[] pbOther = ckOther.CreateRawCompositeKey32();
- bool bResult = MemUtil.ArraysEqual(pbThis, pbOther);
- MemUtil.ZeroByteArray(pbOther);
- MemUtil.ZeroByteArray(pbThis);
+ try
+ {
+ byte[] pbOther = ckOther.CreateRawCompositeKey32();
+ bEqual = MemUtil.ArraysEqual(pbThis, pbOther);
+ MemUtil.ZeroByteArray(pbOther);
+ }
+ finally { MemUtil.ZeroByteArray(pbThis); }
- return bResult;
+ return bEqual;
}
[Obsolete]
@@ -231,31 +237,90 @@ namespace ModernKeePassLib.Keys
{
if(p == null) { Debug.Assert(false); throw new ArgumentNullException("p"); }
- byte[] pbRaw32 = CreateRawCompositeKey32();
- if((pbRaw32 == null) || (pbRaw32.Length != 32))
- { Debug.Assert(false); return null; }
+ byte[] pbRaw32 = null, pbTrf32 = null;
+ ProtectedBinary pbRet = null;
- KdfEngine kdf = KdfPool.Get(p.KdfUuid);
- if(kdf == null) // CryptographicExceptions are translated to "file corrupted"
- throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph +
- KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph +
- "UUID: " + p.KdfUuid.ToHexString() + ".");
-
- byte[] pbTrf32 = kdf.Transform(pbRaw32, p);
- if(pbTrf32 == null) { Debug.Assert(false); return null; }
-
- if(pbTrf32.Length != 32)
+ try
{
- Debug.Assert(false);
- pbTrf32 = CryptoUtil.HashSha256(pbTrf32);
+ pbRaw32 = CreateRawCompositeKey32();
+ if((pbRaw32 == null) || (pbRaw32.Length != 32))
+ { Debug.Assert(false); return null; }
+
+ KdfEngine kdf = KdfPool.Get(p.KdfUuid);
+ if(kdf == null) // CryptographicExceptions are translated to "file corrupted"
+ throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph +
+ KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph +
+ "UUID: " + p.KdfUuid.ToHexString() + ".");
+
+ pbTrf32 = kdf.Transform(pbRaw32, p);
+ if(pbTrf32 == null) { Debug.Assert(false); return null; }
+ if(pbTrf32.Length != 32)
+ {
+ Debug.Assert(false);
+ pbTrf32 = CryptoUtil.HashSha256(pbTrf32);
+ }
+
+ pbRet = new ProtectedBinary(true, pbTrf32);
+ }
+ finally
+ {
+ if(pbRaw32 != null) MemUtil.ZeroByteArray(pbRaw32);
+ if(pbTrf32 != null) MemUtil.ZeroByteArray(pbTrf32);
}
- ProtectedBinary pbRet = new ProtectedBinary(true, pbTrf32);
- MemUtil.ZeroByteArray(pbTrf32);
- MemUtil.ZeroByteArray(pbRaw32);
return pbRet;
}
+ private sealed class CkGkTaskInfo
+ {
+ public volatile ProtectedBinary Key = null;
+ public volatile string Error = null;
+ }
+
+ internal ProtectedBinary GenerateKey32Ex(KdfParameters p, IStatusLogger sl)
+ {
+ if(sl == null) return GenerateKey32(p);
+
+ CkGkTaskInfo ti = new CkGkTaskInfo();
+
+ ThreadStart f = delegate()
+ {
+ if(ti == null) { Debug.Assert(false); return; }
+
+ try { ti.Key = GenerateKey32(p); }
+ catch(ThreadAbortException exAbort)
+ {
+ ti.Error = ((exAbort != null) ? exAbort.Message : null);
+ Thread.ResetAbort();
+ }
+ catch(Exception ex)
+ {
+ Debug.Assert(false);
+ ti.Error = ((ex != null) ? ex.Message : null);
+ }
+ };
+
+ Thread th = new Thread(f);
+ th.Start();
+
+ Debug.Assert(PwDefs.UIUpdateDelay >= 2);
+ while(!th.Join(PwDefs.UIUpdateDelay / 2))
+ {
+ if(!sl.ContinueWork())
+ {
+ try { th.Abort(); }
+ catch(Exception) { Debug.Assert(false); }
+
+ throw new OperationCanceledException();
+ }
+ }
+
+ if(!string.IsNullOrEmpty(ti.Error)) throw new Exception(ti.Error);
+
+ Debug.Assert(ti.Key != null);
+ return ti.Key;
+ }
+
private void ValidateUserKeys()
{
int nAccounts = 0;
@@ -280,14 +345,11 @@ namespace ModernKeePassLib.Keys
{
get
{
- return KLRes.InvalidCompositeKey + MessageService.NewParagraph +
- KLRes.InvalidCompositeKeyHint;
+ return (KLRes.InvalidCompositeKey + MessageService.NewParagraph +
+ KLRes.InvalidCompositeKeyHint);
}
}
- ///
- /// Construct a new invalid composite key exception.
- ///
public InvalidCompositeKeyException()
{
}
diff --git a/ModernKeePassLib/Keys/KcpKeyFile.cs b/ModernKeePassLib/Keys/KcpKeyFile.cs
index c62493a..9ec1d5c 100644
--- a/ModernKeePassLib/Keys/KcpKeyFile.cs
+++ b/ModernKeePassLib/Keys/KcpKeyFile.cs
@@ -67,14 +67,13 @@ namespace ModernKeePassLib.Keys
{
get { return m_pbKeyData; }
}
-
#if ModernKeePassLib
- public KcpKeyFile(StorageFile strKeyFile)
- {
- Construct(IOConnectionInfo.FromFile(strKeyFile), false);
- }
+ public KcpKeyFile(StorageFile keyFile)
+ {
+ Construct(IOConnectionInfo.FromStorageFile(keyFile), false);
+ }
#else
- public KcpKeyFile(string strKeyFile)
+ public KcpKeyFile(string strKeyFile)
{
Construct(IOConnectionInfo.FromPath(strKeyFile), false);
}
@@ -183,19 +182,19 @@ namespace ModernKeePassLib.Keys
return null;
}
- ///
- /// Create a new, random key-file.
- ///
- /// Path where the key-file should be saved to.
- /// If the file exists already, it will be overwritten.
- /// Additional entropy used to generate
- /// the random key. May be null (in this case only the KeePass-internal
- /// random number generator is used).
- /// Returns a FileSaveResult error code.
+ ///
+ /// Create a new, random key-file.
+ ///
+ /// Path where the key-file should be saved to.
+ /// If the file exists already, it will be overwritten.
+ /// Additional entropy used to generate
+ /// the random key. May be null (in this case only the KeePass-internal
+ /// random number generator is used).
+ /// Returns a FileSaveResult error code.
#if ModernKeePassLib
- public static void Create(StorageFile strFilePath, byte[] pbAdditionalEntropy)
+ public static void Create(StorageFile file, byte[] pbAdditionalEntropy)
#else
- public static void Create(string strFilePath, byte[] pbAdditionalEntropy)
+ public static void Create(string strFilePath, byte[] pbAdditionalEntropy)
#endif
{
byte[] pbKey32 = CryptoRandom.Instance.GetRandomBytes(32);
@@ -215,7 +214,11 @@ namespace ModernKeePassLib.Keys
}
}
- CreateXmlKeyFile(strFilePath, pbFinalKey32);
+#if ModernKeePassLib
+ CreateXmlKeyFile(file, pbFinalKey32);
+#else
+ CreateXmlKeyFile(strFilePath, pbFinalKey32);
+#endif
}
// ================================================================
@@ -276,19 +279,23 @@ namespace ModernKeePassLib.Keys
return pbKeyData;
}
+
#if ModernKeePassLib
- private static void CreateXmlKeyFile(StorageFile strFile, byte[] pbKeyData)
+ private static void CreateXmlKeyFile(StorageFile file, byte[] pbKeyData)
+ {
+ Debug.Assert(file != null);
+ if (file == null) throw new ArgumentNullException(nameof(file));
#else
- private static void CreateXmlKeyFile(string strFile, byte[] pbKeyData)
-#endif
+ private static void CreateXmlKeyFile(string strFile, byte[] pbKeyData)
{
Debug.Assert(strFile != null);
if(strFile == null) throw new ArgumentNullException("strFile");
+#endif
Debug.Assert(pbKeyData != null);
if(pbKeyData == null) throw new ArgumentNullException("pbKeyData");
#if ModernKeePassLib
- IOConnectionInfo ioc = IOConnectionInfo.FromFile(strFile);
+ var ioc = IOConnectionInfo.FromStorageFile(file);
#else
IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFile);
#endif
diff --git a/ModernKeePassLib/Keys/KcpPassword.cs b/ModernKeePassLib/Keys/KcpPassword.cs
index b69bae4..0eb10ea 100644
--- a/ModernKeePassLib/Keys/KcpPassword.cs
+++ b/ModernKeePassLib/Keys/KcpPassword.cs
@@ -21,9 +21,9 @@ using System;
using System.Diagnostics;
using System.Text;
+using ModernKeePassLib.Cryptography;
using ModernKeePassLib.Security;
using ModernKeePassLib.Utility;
-using ModernKeePassLib.Cryptography;
namespace ModernKeePassLib.Keys
{
diff --git a/ModernKeePassLib/Keys/KeyProvider.cs b/ModernKeePassLib/Keys/KeyProvider.cs
index 16a9f1d..87b04ef 100644
--- a/ModernKeePassLib/Keys/KeyProvider.cs
+++ b/ModernKeePassLib/Keys/KeyProvider.cs
@@ -145,7 +145,7 @@ namespace ModernKeePassLib.Keys
public override byte[] GetKey(KeyProviderQueryContext ctx)
{
- return new byte[]{ 2, 3, 5, 7, 11, 13 };
+ return new byte[] { 2, 3, 5, 7, 11, 13 };
}
}
#endif
diff --git a/ModernKeePassLib/ModernKeePassLib.csproj b/ModernKeePassLib/ModernKeePassLib.csproj
index 3ff2a23..83ad1df 100644
--- a/ModernKeePassLib/ModernKeePassLib.csproj
+++ b/ModernKeePassLib/ModernKeePassLib.csproj
@@ -3,14 +3,14 @@
netstandard2.0
true
- 2.41.1
+ 2.42.1
Geoffroy Bonneville
https://www.gnu.org/licenses/gpl-3.0.en.html
https://github.com/wismna/ModernKeePass
Portable KeePass Password Management Library that targets .Net Standard and WinRT. Allows reading, editing and writing to KeePass 2.x databases.
wismna
ModernKeePassLib
- Allow opening Storage File from FutureAccessList with a token
+ Update to version 2.42.1
KeePass KeePassLib Portable PCL NetStandard ModernKeePass
Copyright © 2019 Geoffroy Bonneville
diff --git a/ModernKeePassLib/Native/Native.PCL.cs b/ModernKeePassLib/Native/Native.PCL.cs
index 8c7a312..815f49d 100644
--- a/ModernKeePassLib/Native/Native.PCL.cs
+++ b/ModernKeePassLib/Native/Native.PCL.cs
@@ -22,6 +22,12 @@ namespace ModernKeePassLib.Native
{
return Environment.OSVersion.Platform;
}
+
+ internal static string DecodeArgsToPath(string strApp)
+ {
+ if (!string.IsNullOrEmpty(strApp)) return strApp;
+ return string.Empty;
+ }
}
internal static class NativeMethods
diff --git a/ModernKeePassLib/Native/NativeLib.cs b/ModernKeePassLib/Native/NativeLib.cs
index a10f433..c3fbe15 100644
--- a/ModernKeePassLib/Native/NativeLib.cs
+++ b/ModernKeePassLib/Native/NativeLib.cs
@@ -234,16 +234,16 @@ namespace ModernKeePassLib.Native
{
ProcessStartInfo psi = new ProcessStartInfo();
+ psi.FileName = EncodePath(strAppPath);
+ if(!string.IsNullOrEmpty(strParams)) psi.Arguments = strParams;
+
psi.CreateNoWindow = true;
- psi.FileName = strAppPath;
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.UseShellExecute = false;
+
psi.RedirectStandardOutput = bStdOut;
-
if(strStdInput != null) psi.RedirectStandardInput = true;
- if(!string.IsNullOrEmpty(strParams)) psi.Arguments = strParams;
-
Process p = Process.Start(psi);
pToDispose = p;
@@ -291,7 +291,7 @@ namespace ModernKeePassLib.Native
#if !ModernKeePassLib
if((f & AppRunFlags.DoEvents) != AppRunFlags.None)
{
- List
- public static readonly uint Version32 = 0x02290000;
+ public static readonly uint Version32 = 0x022A0100;
///
/// Version, encoded as 64-bit unsigned integer
/// (component-wise, 16 bits per component).
///
- public static readonly ulong FileVersion64 = 0x0002002900000000UL;
+ public static readonly ulong FileVersion64 = 0x0002002A00010000UL;
///
/// Version, encoded as string.
///
- public static readonly string VersionString = "2.41";
+ public static readonly string VersionString = "2.42.1";
public static readonly string Copyright = @"Copyright © 2003-2019 Dominik Reichl";
@@ -181,6 +181,12 @@ namespace ModernKeePassLib
///
public static readonly string DefaultAutoTypeSequenceTan = @"{PASSWORD}";
+ ///
+ /// Maximum time (in milliseconds) after which the user interface
+ /// should be updated.
+ ///
+ internal const int UIUpdateDelay = 50;
+
///
/// Check if a name is a standard field name.
///
diff --git a/ModernKeePassLib/PwEnums.cs b/ModernKeePassLib/PwEnums.cs
index d817303..0dab7c5 100644
--- a/ModernKeePassLib/PwEnums.cs
+++ b/ModernKeePassLib/PwEnums.cs
@@ -61,7 +61,7 @@ namespace ModernKeePassLib
}
///
- /// Methods for merging password databases/entries.
+ /// Methods for merging databases/entries.
///
public enum PwMergeMethod
{
diff --git a/ModernKeePassLib/PwGroup.cs b/ModernKeePassLib/PwGroup.cs
index 6a58611..6b089ef 100644
--- a/ModernKeePassLib/PwGroup.cs
+++ b/ModernKeePassLib/PwGroup.cs
@@ -25,19 +25,24 @@ using System.Text.RegularExpressions;
using ModernKeePassLib.Collections;
using ModernKeePassLib.Delegates;
using ModernKeePassLib.Interfaces;
+using ModernKeePassLib.Resources;
using ModernKeePassLib.Security;
using ModernKeePassLib.Utility;
namespace ModernKeePassLib
{
///
- /// A group containing several password entries.
+ /// A group containing subgroups and entries.
///
public sealed class PwGroup : ITimeLogger, IStructureItem, IDeepCloneable
{
public const bool DefaultAutoTypeEnabled = true;
public const bool DefaultSearchingEnabled = true;
+ // In the tree view of Windows 10, the X coordinate is reset
+ // to 0 after 256 nested nodes
+ private const uint MaxDepth = 126; // Depth 126 = level 127 < 256/2
+
private PwObjectList m_listGroups = new PwObjectList();
private PwObjectList m_listEntries = new PwObjectList();
private PwGroup m_pParentGroup = null;
@@ -139,7 +144,8 @@ namespace ModernKeePassLib
{
get { return m_pParentGroup; }
- // Plugins: use PwGroup.AddGroup instead.
+ // Plugins: use the PwGroup.AddGroup method instead.
+ // Internal: check depth using CanAddGroup/CheckCanAddGroup.
internal set { Debug.Assert(value != this); m_pParentGroup = value; }
}
@@ -1244,7 +1250,7 @@ namespace ModernKeePassLib
PwGroup pg = m_pParentGroup;
while(pg != null)
{
- if((!bIncludeTopMostGroup) && (pg.m_pParentGroup == null))
+ if(!bIncludeTopMostGroup && (pg.m_pParentGroup == null))
break;
strPath = pg.Name + strSeparator + strPath;
@@ -1367,21 +1373,34 @@ namespace ModernKeePassLib
#endif
///
- /// Get the level of the group (i.e. the number of parent groups).
+ /// Get the depth of this group (i.e. the number of ancestors).
///
- /// Number of parent groups.
- public uint GetLevel()
+ /// Depth of this group.
+ public uint GetDepth()
{
PwGroup pg = m_pParentGroup;
- uint uLevel = 0;
+ uint d = 0;
while(pg != null)
{
- pg = pg.ParentGroup;
- ++uLevel;
+ pg = pg.m_pParentGroup;
+ ++d;
}
- return uLevel;
+ return d;
+ }
+
+ private uint GetHeight()
+ {
+ if(m_listGroups.UCount == 0) return 0;
+
+ uint h = 0;
+ foreach(PwGroup pgSub in m_listGroups)
+ {
+ h = Math.Max(h, pgSub.GetHeight());
+ }
+
+ return (h + 1);
}
public string GetAutoTypeSequenceInherited()
@@ -1520,13 +1539,31 @@ namespace ModernKeePassLib
{
if(subGroup == null) throw new ArgumentNullException("subGroup");
+ CheckCanAddGroup(subGroup);
m_listGroups.Add(subGroup);
- if(bTakeOwnership) subGroup.m_pParentGroup = this;
+ if(bTakeOwnership) subGroup.ParentGroup = this;
if(bUpdateLocationChangedOfSub) subGroup.LocationChanged = DateTime.UtcNow;
}
+ internal bool CanAddGroup(PwGroup pgSub)
+ {
+ if(pgSub == null) { Debug.Assert(false); return false; }
+
+ uint dCur = GetDepth(), hSub = pgSub.GetHeight();
+ return ((dCur + hSub + 1) <= MaxDepth);
+ }
+
+ internal void CheckCanAddGroup(PwGroup pgSub)
+ {
+ if(!CanAddGroup(pgSub))
+ {
+ Debug.Assert(false);
+ throw new InvalidOperationException(KLRes.StructsTooDeep);
+ }
+ }
+
///
/// Add an entry to this group.
///
diff --git a/ModernKeePassLib/Resources/KLRes.Generated.cs b/ModernKeePassLib/Resources/KLRes.Generated.cs
index 007d875..3e4a6c2 100644
--- a/ModernKeePassLib/Resources/KLRes.Generated.cs
+++ b/ModernKeePassLib/Resources/KLRes.Generated.cs
@@ -59,6 +59,7 @@ namespace ModernKeePassLib.Resources
m_strOldFormat = TryGetEx(dictNew, "OldFormat", m_strOldFormat);
m_strPassive = TryGetEx(dictNew, "Passive", m_strPassive);
m_strPreAuth = TryGetEx(dictNew, "PreAuth", m_strPreAuth);
+ m_strStructsTooDeep = TryGetEx(dictNew, "StructsTooDeep", m_strStructsTooDeep);
m_strTimeout = TryGetEx(dictNew, "Timeout", m_strTimeout);
m_strTryAgainSecs = TryGetEx(dictNew, "TryAgainSecs", m_strTryAgainSecs);
m_strUnknownHeaderId = TryGetEx(dictNew, "UnknownHeaderId", m_strUnknownHeaderId);
@@ -101,6 +102,7 @@ namespace ModernKeePassLib.Resources
"OldFormat",
"Passive",
"PreAuth",
+ "StructsTooDeep",
"Timeout",
"TryAgainSecs",
"UnknownHeaderId",
@@ -346,10 +348,10 @@ namespace ModernKeePassLib.Resources
}
private static string m_strFrameworkNotImplExcp =
- @"The .NET framework/runtime under which KeePass is currently running does not support this operation.";
+ @"The .NET Framework/runtime under which KeePass is currently running does not support this operation.";
///
/// Look up a localized string similar to
- /// 'The .NET framework/runtime under which KeePass is currently running does not support this operation.'.
+ /// 'The .NET Framework/runtime under which KeePass is currently running does not support this operation.'.
///
public static string FrameworkNotImplExcp
{
@@ -477,6 +479,17 @@ namespace ModernKeePassLib.Resources
get { return m_strPreAuth; }
}
+ private static string m_strStructsTooDeep =
+ @"Structures are nested too deeply.";
+ ///
+ /// Look up a localized string similar to
+ /// 'Structures are nested too deeply.'.
+ ///
+ public static string StructsTooDeep
+ {
+ get { return m_strStructsTooDeep; }
+ }
+
private static string m_strTimeout =
@"Timeout";
///
diff --git a/ModernKeePassLib/Resources/KSRes.Generated.cs b/ModernKeePassLib/Resources/KSRes.Generated.cs
index 7b49547..298a2ce 100644
--- a/ModernKeePassLib/Resources/KSRes.Generated.cs
+++ b/ModernKeePassLib/Resources/KSRes.Generated.cs
@@ -9,7 +9,7 @@ namespace ModernKeePassLib.Resources
///
/// A strongly-typed resource class, for looking up localized strings, etc.
///
- public static class KSRes
+ public static partial class KSRes
{
private static string TryGetEx(Dictionary dictNew,
string strName, string strDefault)
diff --git a/ModernKeePassLib/Serialization/FileTransactionEx.cs b/ModernKeePassLib/Serialization/FileTransactionEx.cs
index 01be96c..0f57384 100644
--- a/ModernKeePassLib/Serialization/FileTransactionEx.cs
+++ b/ModernKeePassLib/Serialization/FileTransactionEx.cs
@@ -23,18 +23,19 @@ using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text;
-
+using Windows.Storage.AccessCache;
#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT)
using System.Security.AccessControl;
#endif
-
-using ModernKeePassLib.Native;
-using ModernKeePassLib.Utility;
-using System.Threading.Tasks;
+#if ModernKeePassLib
using Windows.Storage;
-using Windows.Storage.Streams;
+#endif
+
using ModernKeePassLib.Cryptography;
+using ModernKeePassLib.Delegates;
+using ModernKeePassLib.Native;
using ModernKeePassLib.Resources;
+using ModernKeePassLib.Utility;
namespace ModernKeePassLib.Serialization
{
@@ -210,10 +211,10 @@ namespace ModernKeePassLib.Serialization
// trying to set 'Owner' or 'Group' can result in an
// UnauthorizedAccessException; thus we restore 'Access' (DACL) only
const AccessControlSections acs = AccessControlSections.Access;
-
#endif
bool bEfsEncrypted = false;
byte[] pbSec = null;
+
DateTime? otCreation = null;
bool bBaseExists = IOConnection.FileExists(m_iocBase);
@@ -228,9 +229,7 @@ namespace ModernKeePassLib.Serialization
try { if(bEfsEncrypted) File.Decrypt(m_iocBase.Path); } // For TxF
catch(Exception) { Debug.Assert(false); }
#endif
-#if ModernKeePassLib
- otCreation = m_iocBase.StorageFile.DateCreated.UtcDateTime;
-#else
+#if !ModernKeePassLib
otCreation = File.GetCreationTimeUtc(m_iocBase.Path);
#endif
#if !ModernKeePassLib
@@ -238,7 +237,7 @@ namespace ModernKeePassLib.Serialization
FileSecurity sec = File.GetAccessControl(m_iocBase.Path, acs);
if(sec != null) pbSec = sec.GetSecurityDescriptorBinaryForm();
#endif
- }
+ }
catch(Exception) { Debug.Assert(NativeLib.IsUnix()); }
// if((long)(faBase & FileAttributes.ReadOnly) != 0)
@@ -337,6 +336,7 @@ namespace ModernKeePassLib.Serialization
{
if(NativeLib.IsUnix()) return;
if(!m_iocBase.IsLocalFile()) return;
+ if(IsOneDriveWorkaroundRequired()) return;
string strID = StrUtil.AlphaNumericOnly(Convert.ToBase64String(
CryptoRandom.Instance.GetRandomBytes(16)));
@@ -354,7 +354,7 @@ namespace ModernKeePassLib.Serialization
#if ModernKeePassLib
var tempFile = ApplicationData.Current.TemporaryFolder.CreateFileAsync(m_iocTemp.Path).GetAwaiter()
.GetResult();
- m_iocTemp = IOConnectionInfo.FromFile(tempFile);
+ m_iocTemp = IOConnectionInfo.FromStorageFile(tempFile);
#else
m_iocTemp = IOConnectionInfo.FromPath(strTemp);
#endif
@@ -370,13 +370,10 @@ namespace ModernKeePassLib.Serialization
if(TxfMoveWithTx()) return true;
- // Move the temporary file onto the base file's drive first,
- // such that it cannot happen that both the base file and
- // the temporary file are deleted/corrupted
-#if ModernKeePassLib
- m_iocTemp.StorageFile = ApplicationData.Current.TemporaryFolder.CreateFileAsync(m_iocTemp.Path).GetAwaiter()
- .GetResult();
-#else
+ // Move the temporary file onto the base file's drive first,
+ // such that it cannot happen that both the base file and
+ // the temporary file are deleted/corrupted
+#if !ModernKeePassLib
const uint f = (NativeMethods.MOVEFILE_COPY_ALLOWED |
NativeMethods.MOVEFILE_REPLACE_EXISTING);
bool b = NativeMethods.MoveFileEx(m_iocTemp.Path, m_iocTxfMidFallback.Path, f);
@@ -391,9 +388,7 @@ namespace ModernKeePassLib.Serialization
private bool TxfMoveWithTx()
{
-#if ModernKeePassLib
- return true;
-#else
+#if !ModernKeePassLib
IntPtr hTx = new IntPtr((int)NativeMethods.INVALID_HANDLE_VALUE);
Debug.Assert(hTx.ToInt64() == NativeMethods.INVALID_HANDLE_VALUE);
try
@@ -438,9 +433,8 @@ namespace ModernKeePassLib.Serialization
catch(Exception) { Debug.Assert(false); }
}
}
-
- return false;
#endif
+ return false;
}
internal static void ClearOld()
@@ -470,5 +464,89 @@ namespace ModernKeePassLib.Serialization
}
catch(Exception) { Debug.Assert(false); }
}
+
+ // https://sourceforge.net/p/keepass/discussion/329220/thread/672ffecc65/
+ // https://sourceforge.net/p/keepass/discussion/329221/thread/514786c23a/
+ private bool IsOneDriveWorkaroundRequired()
+ {
+#if !ModernKeePassLib
+ if(NativeLib.IsUnix()) return false;
+
+ try
+ {
+ string strReleaseId = (Registry.GetValue(
+ "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
+ "ReleaseId", string.Empty) as string);
+ if(strReleaseId != "1809") return false;
+
+ string strFile = m_iocBase.Path;
+
+ GFunc fMatch = delegate(string strRoot, string strSfx)
+ {
+ if(string.IsNullOrEmpty(strRoot)) return false;
+ string strPfx = UrlUtil.EnsureTerminatingSeparator(
+ strRoot, false) + strSfx;
+ return strFile.StartsWith(strPfx, StrUtil.CaseIgnoreCmp);
+ };
+ GFunc fMatchEnv = delegate(string strEnv, string strSfx)
+ {
+ return fMatch(Environment.GetEnvironmentVariable(strEnv), strSfx);
+ };
+
+ string strKnown = NativeMethods.GetKnownFolderPath(
+ NativeMethods.FOLDERID_SkyDrive);
+ if(fMatch(strKnown, string.Empty)) return true;
+
+ if(fMatchEnv("USERPROFILE", "OneDrive\\")) return true;
+ if(fMatchEnv("OneDrive", string.Empty)) return true;
+ if(fMatchEnv("OneDriveCommercial", string.Empty)) return true;
+ if(fMatchEnv("OneDriveConsumer", string.Empty)) return true;
+
+ using(RegistryKey kAccs = Registry.CurrentUser.OpenSubKey(
+ "Software\\Microsoft\\OneDrive\\Accounts", false))
+ {
+ string[] vAccs = (((kAccs != null) ? kAccs.GetSubKeyNames() :
+ null) ?? new string[0]);
+
+ foreach(string strAcc in vAccs)
+ {
+ if(string.IsNullOrEmpty(strAcc)) { Debug.Assert(false); continue; }
+
+ using(RegistryKey kTenants = kAccs.OpenSubKey(
+ strAcc + "\\Tenants", false))
+ {
+ string[] vTenants = (((kTenants != null) ?
+ kTenants.GetSubKeyNames() : null) ?? new string[0]);
+
+ foreach(string strT in vTenants)
+ {
+ if(string.IsNullOrEmpty(strT)) { Debug.Assert(false); continue; }
+
+ using(RegistryKey kT = kTenants.OpenSubKey(strT, false))
+ {
+ string[] vPaths = (((kT != null) ?
+ kT.GetValueNames() : null) ?? new string[0]);
+
+ foreach(string strPath in vPaths)
+ {
+ if((strPath == null) || (strPath.Length < 4) ||
+ (strPath[1] != ':'))
+ {
+ Debug.Assert(false);
+ continue;
+ }
+
+ if(fMatch(strPath, string.Empty)) return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ catch(Exception) { Debug.Assert(false); }
+#endif
+ return false;
+ }
}
}
diff --git a/ModernKeePassLib/Serialization/IOConnection.cs b/ModernKeePassLib/Serialization/IOConnection.cs
index 7d20259..35b64cc 100644
--- a/ModernKeePassLib/Serialization/IOConnection.cs
+++ b/ModernKeePassLib/Serialization/IOConnection.cs
@@ -24,7 +24,6 @@ using System.IO;
using System.Net;
using System.Reflection;
using System.Text;
-
#if (!ModernKeePassLib && !KeePassLibSD && !KeePassUAP)
using System.Net.Cache;
using System.Net.Security;
@@ -600,7 +599,12 @@ namespace ModernKeePassLib.Serialization
private static Stream OpenReadLocal(IOConnectionInfo ioc)
{
+#if ModernKeePassLib
return ioc.StorageFile.OpenAsync(FileAccessMode.Read).GetAwaiter().GetResult().AsStream();
+#else
+ return new FileStream(ioc.Path, FileMode.Open, FileAccess.Read,
+ FileShare.Read);
+#endif
}
#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT)
@@ -654,7 +658,7 @@ namespace ModernKeePassLib.Serialization
RaiseIOAccessPreEvent(ioc, IOAccessType.Exists);
#if ModernKeePassLib
- return ioc.StorageFile.IsAvailable;
+ return ioc.StorageFile != null;
#else
if(ioc.IsLocalFile()) return File.Exists(ioc.Path);
@@ -696,7 +700,7 @@ namespace ModernKeePassLib.Serialization
#if ModernKeePassLib
if (!ioc.IsLocalFile()) return;
- ioc.StorageFile?.DeleteAsync().GetAwaiter().GetResult();
+ ioc.StorageFile?.DeleteAsync().GetAwaiter().GetResult();
#else
if(ioc.IsLocalFile()) { File.Delete(ioc.Path); return; }
@@ -718,7 +722,7 @@ namespace ModernKeePassLib.Serialization
}
#endif
#endif
- }
+ }
///
/// Rename/move a file. For local file system and WebDAV, the
@@ -735,7 +739,7 @@ namespace ModernKeePassLib.Serialization
#if ModernKeePassLib
if (!iocFrom.IsLocalFile()) return;
- iocFrom.StorageFile?.RenameAsync(iocTo.Path).GetAwaiter().GetResult();
+ iocFrom.StorageFile?.RenameAsync(iocTo.Path).GetAwaiter().GetResult();
#else
if(iocFrom.IsLocalFile()) { File.Move(iocFrom.Path, iocTo.Path); return; }
diff --git a/ModernKeePassLib/Serialization/IOConnectionInfo.cs b/ModernKeePassLib/Serialization/IOConnectionInfo.cs
index 8148907..c432f31 100644
--- a/ModernKeePassLib/Serialization/IOConnectionInfo.cs
+++ b/ModernKeePassLib/Serialization/IOConnectionInfo.cs
@@ -25,14 +25,12 @@ using System.IO;
using System.Text;
using System.Xml.Serialization;
#if ModernKeePassLib
+using System.Threading.Tasks;
using Windows.Storage;
-//using PCLStorage;
#endif
using ModernKeePassLib.Interfaces;
using ModernKeePassLib.Utility;
-using System.Threading.Tasks;
-using Windows.Storage.AccessCache;
namespace ModernKeePassLib.Serialization
{
@@ -70,6 +68,8 @@ namespace ModernKeePassLib.Serialization
{
// private IOFileFormatHint m_ioHint = IOFileFormatHint.None;
+ public StorageFile StorageFile { get; set; }
+
private string m_strUrl = string.Empty;
public string Path
{
@@ -316,29 +316,17 @@ namespace ModernKeePassLib.Serialization
}
#if ModernKeePassLib
- public static IOConnectionInfo FromFile(StorageFile file)
+ public static IOConnectionInfo FromStorageFile(StorageFile file)
{
IOConnectionInfo ioc = new IOConnectionInfo();
- ioc.StorageFile = file;
- ioc.Path = file.Path;
- ioc.CredSaveMode = IOCredSaveMode.NoSave;
-
- return ioc;
- }
-
- public static IOConnectionInfo FromPath(string strPath)
- {
- IOConnectionInfo ioc = new IOConnectionInfo();
-
- ioc.Path = strPath;
- ioc.StorageFile = StorageApplicationPermissions.FutureAccessList.GetFileAsync(strPath).GetAwaiter().GetResult();
+ ioc.StorageFile = file;
ioc.CredSaveMode = IOCredSaveMode.NoSave;
return ioc;
}
#else
- public static IOConnectionInfo FromPath(string strPath)
+ public static IOConnectionInfo FromPath(string strPath)
{
IOConnectionInfo ioc = new IOConnectionInfo();
@@ -348,10 +336,6 @@ namespace ModernKeePassLib.Serialization
return ioc;
}
#endif
-
-
- public StorageFile StorageFile { get; set; }
-
public bool CanProbablyAccess()
{
#if ModernKeePassLib
diff --git a/ModernKeePassLib/Serialization/KdbxFile.Read.Streamed.cs b/ModernKeePassLib/Serialization/KdbxFile.Read.Streamed.cs
index 4ab2046..f0ffd31 100644
--- a/ModernKeePassLib/Serialization/KdbxFile.Read.Streamed.cs
+++ b/ModernKeePassLib/Serialization/KdbxFile.Read.Streamed.cs
@@ -30,13 +30,14 @@ using System.Drawing;
using ModernKeePassLib;
using ModernKeePassLib.Collections;
-using ModernKeePassLib.Cryptography;
-using ModernKeePassLib.Cryptography.Cipher;
using ModernKeePassLib.Interfaces;
using ModernKeePassLib.Resources;
using ModernKeePassLib.Security;
using ModernKeePassLib.Utility;
+using ModernKeePassLib.Cryptography;
+using ModernKeePassLib.Cryptography.Cipher;
+
namespace ModernKeePassLib.Serialization
{
///
@@ -97,36 +98,10 @@ namespace ModernKeePassLib.Serialization
private void ReadXmlStreamed(Stream sXml, Stream sParent)
{
- ReadDocumentStreamed(CreateXmlReader(sXml), sParent);
- }
-
- internal static XmlReaderSettings CreateStdXmlReaderSettings()
- {
- XmlReaderSettings xrs = new XmlReaderSettings();
-
- xrs.CloseInput = true;
- xrs.IgnoreComments = true;
- xrs.IgnoreProcessingInstructions = true;
- xrs.IgnoreWhitespace = true;
-
-#if ModernKeePassLib || KeePassUAP
- xrs.DtdProcessing = DtdProcessing.Prohibit;
-#else
-#if !KeePassLibSD
- // Also see PrepMonoDev.sh script
- xrs.ProhibitDtd = true; // Obsolete in .NET 4, but still there
- // xrs.DtdProcessing = DtdProcessing.Prohibit; // .NET 4 only
-#endif
- xrs.ValidationType = ValidationType.None;
-#endif
-
- return xrs;
- }
-
- private static XmlReader CreateXmlReader(Stream readerStream)
- {
- XmlReaderSettings xrs = CreateStdXmlReaderSettings();
- return XmlReader.Create(readerStream, xrs);
+ using(XmlReader xr = XmlUtilEx.CreateXmlReader(sXml))
+ {
+ ReadDocumentStreamed(xr, sParent);
+ }
}
private void ReadDocumentStreamed(XmlReader xr, Stream sParentStream)
@@ -179,7 +154,7 @@ namespace ModernKeePassLib.Serialization
}
++uTagCounter;
- if(((uTagCounter % 256) == 0) && bSupportsStatus)
+ if(((uTagCounter & 0xFFU) == 0) && bSupportsStatus)
{
Debug.Assert(lStreamLength == sParentStream.Length);
uint uPct = (uint)((sParentStream.Position * 100) /
@@ -189,7 +164,8 @@ namespace ModernKeePassLib.Serialization
// position/length values (M120413)
if(uPct > 100) { Debug.Assert(false); uPct = 100; }
- m_slLogger.SetProgress(uPct);
+ if(!m_slLogger.SetProgress(uPct))
+ throw new OperationCanceledException();
}
}
@@ -1028,28 +1004,31 @@ namespace ModernKeePassLib.Serialization
private void ReadUnknown(XmlReader xr)
{
Debug.Assert(false); // Unknown node!
+ Debug.Assert(xr.NodeType == XmlNodeType.Element);
- if(xr.IsEmptyElement) { m_bReadNextNode = true; return; }
+ bool bRead = false;
+ int cOpen = 0;
- string strUnknownName = xr.Name;
-
- XorredBuffer xb = ProcessNode(xr);
- if(xb != null) { xb.Dispose(); return; } // ProcessNode sets m_bReadNextNode
-
- bool bRead = true;
- while(true)
+ do
{
if(bRead) xr.Read();
+ bRead = true;
- if(xr.NodeType == XmlNodeType.EndElement) break;
- if(xr.NodeType != XmlNodeType.Element) { bRead = true; continue; }
+ if(xr.NodeType == XmlNodeType.EndElement) --cOpen;
+ else if(xr.NodeType == XmlNodeType.Element)
+ {
+ if(!xr.IsEmptyElement)
+ {
+ XorredBuffer xb = ProcessNode(xr);
+ if(xb != null) { xb.Dispose(); bRead = m_bReadNextNode; continue; }
- ReadUnknown(xr);
- bRead = m_bReadNextNode;
+ ++cOpen;
+ }
+ }
}
+ while(cOpen > 0);
- Debug.Assert(xr.Name == strUnknownName); // On end tag
- m_bReadNextNode = true;
+ m_bReadNextNode = bRead;
}
private XorredBuffer ProcessNode(XmlReader xr)
diff --git a/ModernKeePassLib/Serialization/KdbxFile.Read.cs b/ModernKeePassLib/Serialization/KdbxFile.Read.cs
index a7a9733..2f3921f 100644
--- a/ModernKeePassLib/Serialization/KdbxFile.Read.cs
+++ b/ModernKeePassLib/Serialization/KdbxFile.Read.cs
@@ -55,26 +55,23 @@ namespace ModernKeePassLib.Serialization
///
public sealed partial class KdbxFile
{
+ ///
+ /// Load a KDBX file.
+ ///
+ /// File to load.
+ /// Format.
+ /// Status logger (optional).
#if ModernKeePassLib
public void Load(StorageFile file, KdbxFormat fmt, IStatusLogger slLogger)
- {
- IOConnectionInfo ioc = IOConnectionInfo.FromFile(file);
- Load(IOConnection.OpenRead(ioc), fmt, slLogger);
- }
-
+ {
+ IOConnectionInfo ioc = IOConnectionInfo.FromStorageFile(file);
#else
- ///
- /// Load a KDBX file.
- ///
- /// File to load.
- /// Format.
- /// Status logger (optional).
- public void Load(string strFilePath, KdbxFormat fmt, IStatusLogger slLogger)
+ public void Load(string strFilePath, KdbxFormat fmt, IStatusLogger slLogger)
{
IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFilePath);
+#endif
Load(IOConnection.OpenRead(ioc), fmt, slLogger);
}
-#endif
///
/// Load a KDBX file from a stream.
@@ -202,19 +199,17 @@ namespace ModernKeePassLib.Serialization
}
#if KeePassDebug_WriteXml
- // FileStream fsOut = new FileStream("Raw.xml", FileMode.Create,
- // FileAccess.Write, FileShare.None);
- // try
- // {
- // while(true)
- // {
- // int b = sXml.ReadByte();
- // if(b == -1) break;
- // fsOut.WriteByte((byte)b);
- // }
- // }
- // catch(Exception) { }
- // fsOut.Close();
+#warning XML output is enabled!
+ /* using(FileStream fsOut = new FileStream("Raw.xml", FileMode.Create,
+ FileAccess.Write, FileShare.None))
+ {
+ while(true)
+ {
+ int b = sXml.ReadByte();
+ if(b == -1) throw new EndOfStreamException();
+ fsOut.WriteByte((byte)b);
+ }
+ } */
#endif
ReadXmlStreamed(sXml, sHashing);
@@ -413,7 +408,7 @@ namespace ModernKeePassLib.Serialization
default:
Debug.Assert(false);
if(m_slLogger != null)
- m_slLogger.SetText(KLRes.UnknownHeaderId + @": " +
+ m_slLogger.SetText(KLRes.UnknownHeaderId + ": " +
kdbID.ToString() + "!", LogStatusType.Warning);
break;
}
diff --git a/ModernKeePassLib/Serialization/KdbxFile.Write.cs b/ModernKeePassLib/Serialization/KdbxFile.Write.cs
index ffbd6aa..d4a6226 100644
--- a/ModernKeePassLib/Serialization/KdbxFile.Write.cs
+++ b/ModernKeePassLib/Serialization/KdbxFile.Write.cs
@@ -429,7 +429,7 @@ namespace ModernKeePassLib.Serialization
};
if(!pgRoot.TraverseTree(TraversalMethod.PreOrder, gh, eh))
- throw new InvalidOperationException();
+ throw new OperationCanceledException();
while(groupStack.Count > 1)
{
diff --git a/ModernKeePassLib/Serialization/KdbxFile.cs b/ModernKeePassLib/Serialization/KdbxFile.cs
index 190830c..55a01c7 100644
--- a/ModernKeePassLib/Serialization/KdbxFile.cs
+++ b/ModernKeePassLib/Serialization/KdbxFile.cs
@@ -378,8 +378,8 @@ namespace ModernKeePassLib.Serialization
Debug.Assert(m_pwDatabase != null);
Debug.Assert(m_pwDatabase.MasterKey != null);
- ProtectedBinary pbinUser = m_pwDatabase.MasterKey.GenerateKey32(
- m_pwDatabase.KdfParameters);
+ ProtectedBinary pbinUser = m_pwDatabase.MasterKey.GenerateKey32Ex(
+ m_pwDatabase.KdfParameters, m_slLogger);
Debug.Assert(pbinUser != null);
if(pbinUser == null)
throw new SecurityException(KLRes.InvalidCompositeKey);
@@ -492,7 +492,7 @@ namespace ModernKeePassLib.Serialization
{
if(pb == null) { Debug.Assert(false); return; }
- if(string.IsNullOrEmpty(strName)) strName = "File.bin";
+ strName = UrlUtil.GetSafeFileName(strName);
string strPath;
int iTry = 1;
@@ -500,8 +500,8 @@ namespace ModernKeePassLib.Serialization
{
strPath = UrlUtil.EnsureTerminatingSeparator(strSaveDir, false);
- string strExt = UrlUtil.GetExtension(strName);
string strDesc = UrlUtil.StripExtension(strName);
+ string strExt = UrlUtil.GetExtension(strName);
strPath += strDesc;
if(iTry > 1)
diff --git a/ModernKeePassLib/Translation/KPTranslation.cs b/ModernKeePassLib/Translation/KPTranslation.cs
index a02b7d7..8aee9f1 100644
--- a/ModernKeePassLib/Translation/KPTranslation.cs
+++ b/ModernKeePassLib/Translation/KPTranslation.cs
@@ -46,6 +46,7 @@ namespace ModernKeePassLib.Translation
public sealed class KPTranslation
{
public static readonly string FileExtension = "lngx";
+ internal const string FileExtension1x = "lng";
private KPTranslationProperties m_props = new KPTranslationProperties();
public KPTranslationProperties Properties
diff --git a/ModernKeePassLib/Utility/MemUtil.cs b/ModernKeePassLib/Utility/MemUtil.cs
index adbe85b..995373b 100644
--- a/ModernKeePassLib/Utility/MemUtil.cs
+++ b/ModernKeePassLib/Utility/MemUtil.cs
@@ -264,8 +264,7 @@ namespace ModernKeePassLib.Utility
[MethodImpl(MioNoOptimize)]
public static void ZeroByteArray(byte[] pbArray)
{
- Debug.Assert(pbArray != null);
- if(pbArray == null) throw new ArgumentNullException("pbArray");
+ if(pbArray == null) { Debug.Assert(false); return; }
Array.Clear(pbArray, 0, pbArray.Length);
}
@@ -277,7 +276,7 @@ namespace ModernKeePassLib.Utility
[MethodImpl(MioNoOptimize)]
public static void ZeroArray(T[] v)
{
- if(v == null) { Debug.Assert(false); throw new ArgumentNullException("v"); }
+ if(v == null) { Debug.Assert(false); return; }
Array.Clear(v, 0, v.Length);
}
diff --git a/ModernKeePassLib/Utility/MonoWorkarounds.cs b/ModernKeePassLib/Utility/MonoWorkarounds.cs
index b26e328..a960d05 100644
--- a/ModernKeePassLib/Utility/MonoWorkarounds.cs
+++ b/ModernKeePassLib/Utility/MonoWorkarounds.cs
@@ -116,9 +116,6 @@ namespace ModernKeePassLib.Utility
// 1760:
// Input focus is not restored when activating a form.
// https://sourceforge.net/p/keepass/bugs/1760/
- // 2139:
- // Shortcut keys are ignored.
- // https://sourceforge.net/p/keepass/feature-requests/2139/
// 2140:
// Explicit control focusing is ignored.
// https://sourceforge.net/p/keepass/feature-requests/2140/
diff --git a/ModernKeePassLib/Utility/StrUtil.cs b/ModernKeePassLib/Utility/StrUtil.cs
index caa06bc..4cfc02f 100644
--- a/ModernKeePassLib/Utility/StrUtil.cs
+++ b/ModernKeePassLib/Utility/StrUtil.cs
@@ -24,7 +24,6 @@ using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
-using System.Text.RegularExpressions;
#if !KeePassUAP
using System.Drawing;
@@ -44,45 +43,41 @@ namespace ModernKeePassLib.Utility
///
public sealed class CharStream
{
- private string m_strString = string.Empty;
- private int m_nPos = 0;
+ private readonly string m_str;
+ private int m_iPos = 0;
+
+ public long Position
+ {
+ get { return m_iPos; }
+ set
+ {
+ if((value < 0) || (value > int.MaxValue))
+ throw new ArgumentOutOfRangeException("value");
+ m_iPos = (int)value;
+ }
+ }
public CharStream(string str)
{
- Debug.Assert(str != null);
- if(str == null) throw new ArgumentNullException("str");
+ if(str == null) { Debug.Assert(false); throw new ArgumentNullException("str"); }
- m_strString = str;
- }
-
- public void Seek(SeekOrigin org, int nSeek)
- {
- if(org == SeekOrigin.Begin)
- m_nPos = nSeek;
- else if(org == SeekOrigin.Current)
- m_nPos += nSeek;
- else if(org == SeekOrigin.End)
- m_nPos = m_strString.Length + nSeek;
+ m_str = str;
}
public char ReadChar()
{
- if(m_nPos < 0) return char.MinValue;
- if(m_nPos >= m_strString.Length) return char.MinValue;
+ if(m_iPos >= m_str.Length) return char.MinValue;
- char chRet = m_strString[m_nPos];
- ++m_nPos;
- return chRet;
+ return m_str[m_iPos++];
}
public char ReadChar(bool bSkipWhiteSpace)
{
- if(bSkipWhiteSpace == false) return ReadChar();
+ if(!bSkipWhiteSpace) return ReadChar();
while(true)
{
char ch = ReadChar();
-
if((ch != ' ') && (ch != '\t') && (ch != '\r') && (ch != '\n'))
return ch;
}
@@ -90,29 +85,25 @@ namespace ModernKeePassLib.Utility
public char PeekChar()
{
- if(m_nPos < 0) return char.MinValue;
- if(m_nPos >= m_strString.Length) return char.MinValue;
+ if(m_iPos >= m_str.Length) return char.MinValue;
- return m_strString[m_nPos];
+ return m_str[m_iPos];
}
public char PeekChar(bool bSkipWhiteSpace)
{
- if(bSkipWhiteSpace == false) return PeekChar();
+ if(!bSkipWhiteSpace) return PeekChar();
- int iIndex = m_nPos;
- while(true)
+ int i = m_iPos;
+ while(i < m_str.Length)
{
- if(iIndex < 0) return char.MinValue;
- if(iIndex >= m_strString.Length) return char.MinValue;
-
- char ch = m_strString[iIndex];
-
+ char ch = m_str[i];
if((ch != ' ') && (ch != '\t') && (ch != '\r') && (ch != '\n'))
return ch;
-
- ++iIndex;
+ ++i;
}
+
+ return char.MinValue;
}
}
@@ -405,44 +396,46 @@ namespace ModernKeePassLib.Utility
return str;
}
- ///
- /// Split up a command line into application and argument.
- ///
- /// Command line to split.
- /// Application path.
- /// Arguments.
- public static void SplitCommandLine(string strCmdLine, out string strApp, out string strArgs)
+ public static void SplitCommandLine(string strCmdLine, out string strApp,
+ out string strArgs)
{
- Debug.Assert(strCmdLine != null); if(strCmdLine == null) throw new ArgumentNullException("strCmdLine");
+ SplitCommandLine(strCmdLine, out strApp, out strArgs, true);
+ }
+
+ internal static void SplitCommandLine(string strCmdLine, out string strApp,
+ out string strArgs, bool bDecodeAppToPath)
+ {
+ if(strCmdLine == null) { Debug.Assert(false); throw new ArgumentNullException("strCmdLine"); }
string str = strCmdLine.Trim();
-
- strApp = null; strArgs = null;
+ strApp = null;
+ strArgs = null;
if(str.StartsWith("\""))
{
- int nSecond = str.IndexOf('\"', 1);
- if(nSecond >= 1)
+ int iSecond = UrlUtil.IndexOfSecondEnclQuote(str);
+ if(iSecond >= 1)
{
- strApp = str.Substring(1, nSecond - 1).Trim();
- strArgs = str.Remove(0, nSecond + 1).Trim();
+ strApp = str.Substring(1, iSecond - 1).Trim();
+ strArgs = str.Remove(0, iSecond + 1).Trim();
}
}
if(strApp == null)
{
- int nSpace = str.IndexOf(' ');
-
- if(nSpace >= 0)
+ int iSpace = str.IndexOf(' ');
+ if(iSpace >= 0)
{
- strApp = str.Substring(0, nSpace);
- strArgs = str.Remove(0, nSpace).Trim();
+ strApp = str.Substring(0, iSpace).Trim();
+ strArgs = str.Remove(0, iSpace + 1).Trim();
}
- else strApp = strCmdLine;
+ else strApp = str;
}
- if(strApp == null) strApp = string.Empty;
+ if(strApp == null) { Debug.Assert(false); strApp = string.Empty; }
if(strArgs == null) strArgs = string.Empty;
+
+ if(bDecodeAppToPath) strApp = NativeLib.DecodeArgsToPath(strApp);
}
// ///
@@ -1289,7 +1282,7 @@ namespace ModernKeePassLib.Utility
if(uBytes <= uGB) return (((uBytes - 1UL) / uMB) + 1UL).ToString() + " MB";
if(uBytes <= uTB) return (((uBytes - 1UL) / uGB) + 1UL).ToString() + " GB";
- return (((uBytes - 1UL)/ uTB) + 1UL).ToString() + " TB";
+ return (((uBytes - 1UL) / uTB) + 1UL).ToString() + " TB";
}
public static string FormatDataSizeKB(ulong uBytes)
diff --git a/ModernKeePassLib/Utility/UrlUtil.cs b/ModernKeePassLib/Utility/UrlUtil.cs
index 1e89085..51ddcc4 100644
--- a/ModernKeePassLib/Utility/UrlUtil.cs
+++ b/ModernKeePassLib/Utility/UrlUtil.cs
@@ -23,6 +23,7 @@ using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
+using System.Text.RegularExpressions;
#if ModernKeePassLib
using Windows.Storage;
@@ -38,12 +39,8 @@ namespace ModernKeePassLib.Utility
///
public static class UrlUtil
{
- private static readonly char[] m_vDirSeps = new char[] {
- '\\', '/', UrlUtil.LocalDirSepChar };
-#if !ModernKeePassLib
- private static readonly char[] m_vPathTrimCharsWs = new char[] {
+ private static readonly char[] g_vPathTrimCharsWs = new char[] {
'\"', ' ', '\t', '\r', '\n' };
-#endif
public static char LocalDirSepChar
{
@@ -57,6 +54,32 @@ namespace ModernKeePassLib.Utility
#endif
}
+ private static char[] g_vDirSepChars = null;
+ private static char[] DirSepChars
+ {
+ get
+ {
+ if(g_vDirSepChars == null)
+ {
+ List l = new List();
+ l.Add('/'); // For URLs, also on Windows
+
+ // On Unix-like systems, '\\' is not a separator
+ if(!NativeLib.IsUnix()) l.Add('\\');
+
+ if(!l.Contains(UrlUtil.LocalDirSepChar))
+ {
+ Debug.Assert(false);
+ l.Add(UrlUtil.LocalDirSepChar);
+ }
+
+ g_vDirSepChars = l.ToArray();
+ }
+
+ return g_vDirSepChars;
+ }
+ }
+
///
/// Get the directory (path) of a file name. The returned string may be
/// terminated by a directory separator character. Example:
@@ -79,7 +102,7 @@ namespace ModernKeePassLib.Utility
Debug.Assert(strFile != null);
if(strFile == null) throw new ArgumentNullException("strFile");
- int nLastSep = strFile.LastIndexOfAny(m_vDirSeps);
+ int nLastSep = strFile.LastIndexOfAny(UrlUtil.DirSepChars);
if(nLastSep < 0) return string.Empty; // No directory
if(bEnsureValidDirSpec && (nLastSep == 2) && (strFile[1] == ':') &&
@@ -103,7 +126,7 @@ namespace ModernKeePassLib.Utility
{
Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath");
- int nLastSep = strPath.LastIndexOfAny(m_vDirSeps);
+ int nLastSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars);
if(nLastSep < 0) return strPath;
if(nLastSep >= (strPath.Length - 1)) return string.Empty;
@@ -120,7 +143,7 @@ namespace ModernKeePassLib.Utility
{
Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath");
- int nLastDirSep = strPath.LastIndexOfAny(m_vDirSeps);
+ int nLastDirSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars);
int nLastExtDot = strPath.LastIndexOf('.');
if(nLastExtDot <= nLastDirSep) return strPath;
@@ -137,7 +160,7 @@ namespace ModernKeePassLib.Utility
{
Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath");
- int nLastDirSep = strPath.LastIndexOfAny(m_vDirSeps);
+ int nLastDirSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars);
int nLastExtDot = strPath.LastIndexOf('.');
if(nLastExtDot <= nLastDirSep) return string.Empty;
@@ -162,11 +185,8 @@ namespace ModernKeePassLib.Utility
if(nLength <= 0) return string.Empty;
char chLast = strPath[nLength - 1];
-
- for(int i = 0; i < m_vDirSeps.Length; ++i)
- {
- if(chLast == m_vDirSeps[i]) return strPath;
- }
+ if(Array.IndexOf(UrlUtil.DirSepChars, chLast) >= 0)
+ return strPath;
if(bUrl) return (strPath + '/');
return (strPath + UrlUtil.LocalDirSepChar);
@@ -230,21 +250,35 @@ namespace ModernKeePassLib.Utility
return false;
} */
+ internal static int IndexOfSecondEnclQuote(string str)
+ {
+ if(str == null) { Debug.Assert(false); return -1; }
+ if(str.Length <= 1) return -1;
+ if(str[0] != '\"') { Debug.Assert(false); return -1; }
+
+ if(NativeLib.IsUnix())
+ {
+ // Find non-escaped quote
+ string strFlt = str.Replace("\\\\", new string(
+ StrUtil.GetUnusedChar(str + "\\\""), 2)); // Same length
+ Match m = Regex.Match(strFlt, "[^\\\\]\\u0022");
+ int i = (((m != null) && m.Success) ? m.Index : -1);
+ return ((i >= 0) ? (i + 1) : -1); // Index of quote
+ }
+
+ // Windows does not allow quotes in folder/file names
+ return str.IndexOf('\"', 1);
+ }
+
public static string GetQuotedAppPath(string strPath)
{
if(strPath == null) { Debug.Assert(false); return string.Empty; }
- // int nFirst = strPath.IndexOf('\"');
- // int nSecond = strPath.IndexOf('\"', nFirst + 1);
- // if((nFirst >= 0) && (nSecond >= 0))
- // return strPath.Substring(nFirst + 1, nSecond - nFirst - 1);
- // return strPath;
-
string str = strPath.Trim();
if(str.Length <= 1) return str;
if(str[0] != '\"') return str;
- int iSecond = str.IndexOf('\"', 1);
+ int iSecond = IndexOfSecondEnclQuote(str);
if(iSecond <= 0) return str;
return str.Substring(1, iSecond - 1);
@@ -256,7 +290,7 @@ namespace ModernKeePassLib.Utility
if(strUrl == null) throw new ArgumentNullException("strUrl");
string str = strUrl;
- if(str.StartsWith(@"file:///", StrUtil.CaseIgnoreCmp))
+ if(str.StartsWith("file:///", StrUtil.CaseIgnoreCmp))
str = str.Substring(8, str.Length - 8);
str = str.Replace('/', UrlUtil.LocalDirSepChar);
@@ -338,8 +372,8 @@ namespace ModernKeePassLib.Utility
string strBase = GetShortestAbsolutePath(strBaseFile);
string strTarget = GetShortestAbsolutePath(strTargetFile);
- string[] vBase = strBase.Split(m_vDirSeps);
- string[] vTarget = strTarget.Split(m_vDirSeps);
+ string[] vBase = strBase.Split(UrlUtil.DirSepChars);
+ string[] vTarget = strTarget.Split(UrlUtil.DirSepChars);
int i = 0;
while((i < (vBase.Length - 1)) && (i < (vTarget.Length - 1)) &&
@@ -416,13 +450,14 @@ namespace ModernKeePassLib.Utility
if(IsUncPath(strPath))
{
char chSep = strPath[0];
- Debug.Assert(Array.IndexOf(m_vDirSeps, chSep) >= 0);
+ char[] vSep = ((chSep == '/') ? (new char[] { '/' }) :
+ (new char[] { '\\', '/' }));
List l = new List();
#if !KeePassLibSD
- string[] v = strPath.Split(m_vDirSeps, StringSplitOptions.None);
+ string[] v = strPath.Split(vSep, StringSplitOptions.None);
#else
- string[] v = strPath.Split(m_vDirSeps);
+ string[] v = strPath.Split(vSep);
#endif
Debug.Assert((v.Length >= 3) && (v[0].Length == 0) &&
(v[1].Length == 0));
@@ -463,8 +498,8 @@ namespace ModernKeePassLib.Utility
}
catch(Exception) { Debug.Assert(false); return strPath; }
- Debug.Assert(str.IndexOf("\\..\\") < 0);
- foreach(char ch in m_vDirSeps)
+ Debug.Assert((str.IndexOf("\\..\\") < 0) || NativeLib.IsUnix());
+ foreach(char ch in UrlUtil.DirSepChars)
{
string strSep = new string(ch, 1);
str = str.Replace(strSep + "." + strSep, strSep);
@@ -494,24 +529,30 @@ namespace ModernKeePassLib.Utility
return nLength;
}
+ internal static string GetScheme(string strUrl)
+ {
+ if(string.IsNullOrEmpty(strUrl)) return string.Empty;
+
+ int i = strUrl.IndexOf(':');
+ if(i > 0) return strUrl.Substring(0, i);
+
+ return string.Empty;
+ }
+
public static string RemoveScheme(string strUrl)
{
if(string.IsNullOrEmpty(strUrl)) return string.Empty;
- int nNetScheme = strUrl.IndexOf(@"://", StrUtil.CaseIgnoreCmp);
- int nShScheme = strUrl.IndexOf(@":/", StrUtil.CaseIgnoreCmp);
- int nSmpScheme = strUrl.IndexOf(@":", StrUtil.CaseIgnoreCmp);
+ int i = strUrl.IndexOf(':');
+ if(i < 0) return strUrl; // No scheme to remove
+ ++i;
- if((nNetScheme < 0) && (nShScheme < 0) && (nSmpScheme < 0))
- return strUrl; // No scheme
+ // A single '/' indicates a path (absolute) and should not be removed
+ if(((i + 1) < strUrl.Length) && (strUrl[i] == '/') &&
+ (strUrl[i + 1] == '/'))
+ i += 2; // Skip authority prefix
- int nMin = Math.Min(Math.Min((nNetScheme >= 0) ? nNetScheme : int.MaxValue,
- (nShScheme >= 0) ? nShScheme : int.MaxValue),
- (nSmpScheme >= 0) ? nSmpScheme : int.MaxValue);
-
- if(nMin == nNetScheme) return strUrl.Substring(nMin + 3);
- if(nMin == nShScheme) return strUrl.Substring(nMin + 2);
- return strUrl.Substring(nMin + 1);
+ return strUrl.Substring(i);
}
public static string ConvertSeparators(string strPath)
@@ -538,22 +579,50 @@ namespace ModernKeePassLib.Utility
public static string FilterFileName(string strName)
{
- if(strName == null) { Debug.Assert(false); return string.Empty; }
+ if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return string.Empty; }
- string str = strName;
+ // https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
- str = str.Replace('/', '-');
- str = str.Replace('\\', '-');
- str = str.Replace(":", string.Empty);
- str = str.Replace("*", string.Empty);
- str = str.Replace("?", string.Empty);
- str = str.Replace("\"", string.Empty);
- str = str.Replace(@"'", string.Empty);
- str = str.Replace('<', '(');
- str = str.Replace('>', ')');
- str = str.Replace('|', '-');
+ StringBuilder sb = new StringBuilder(strName.Length);
+ foreach(char ch in strName)
+ {
+ if(ch < '\u0020') continue;
- return str;
+ switch(ch)
+ {
+ case '\"':
+ case '*':
+ case ':':
+ case '?':
+ break;
+
+ case '/':
+ case '\\':
+ case '|':
+ sb.Append('-');
+ break;
+
+ case '<':
+ sb.Append('(');
+ break;
+
+ case '>':
+ sb.Append(')');
+ break;
+
+ default: sb.Append(ch); break;
+ }
+ }
+
+ // Trim trailing spaces and periods
+ for(int i = sb.Length - 1; i >= 0; --i)
+ {
+ char ch = sb[i];
+ if((ch == ' ') || (ch == '.')) sb.Remove(i, 1);
+ else break;
+ }
+
+ return sb.ToString();
}
///
@@ -672,7 +741,7 @@ namespace ModernKeePassLib.Utility
foreach(string strPathRaw in v)
{
if(strPathRaw == null) { Debug.Assert(false); continue; }
- string strPath = strPathRaw.Trim(m_vPathTrimCharsWs);
+ string strPath = strPathRaw.Trim(g_vPathTrimCharsWs);
if(strPath.Length == 0) { Debug.Assert(false); continue; }
Debug.Assert(strPath == strPathRaw);
@@ -709,7 +778,7 @@ namespace ModernKeePassLib.Utility
if(fi == null) { Debug.Assert(false); continue; }
string strPathRaw = fi.FullName;
if(strPathRaw == null) { Debug.Assert(false); continue; }
- string strPath = strPathRaw.Trim(m_vPathTrimCharsWs);
+ string strPath = strPathRaw.Trim(g_vPathTrimCharsWs);
if(strPath.Length == 0) { Debug.Assert(false); continue; }
Debug.Assert(strPath == strPathRaw);
@@ -783,5 +852,19 @@ namespace ModernKeePassLib.Utility
char ch = char.ToUpperInvariant(strPath[0]);
return (((ch >= 'A') && (ch <= 'Z')) ? ch : '\0');
}
+
+ internal static string GetSafeFileName(string strName)
+ {
+ Debug.Assert(!string.IsNullOrEmpty(strName));
+
+ string str = FilterFileName(GetFileName(strName ?? string.Empty));
+
+ if(string.IsNullOrEmpty(str))
+ {
+ Debug.Assert(false);
+ return "File.dat";
+ }
+ return str;
+ }
}
}
diff --git a/ModernKeePassLib/Utility/XmlUtilEx.cs b/ModernKeePassLib/Utility/XmlUtilEx.cs
index 87a25e8..a755070 100644
--- a/ModernKeePassLib/Utility/XmlUtilEx.cs
+++ b/ModernKeePassLib/Utility/XmlUtilEx.cs
@@ -50,7 +50,7 @@ namespace ModernKeePassLib.Utility
xrs.IgnoreProcessingInstructions = true;
xrs.IgnoreWhitespace = true;
-#if KeePassUAP
+#if KeePassUAP || ModernKeePassLib
xrs.DtdProcessing = DtdProcessing.Prohibit;
#else
// Also see PrepMonoDev.sh script