diff --git a/src/AndroidFileChooserBindingSdkStyle/AndroidFileChooserBindingSdkStyle.csproj b/src/AndroidFileChooserBindingSdkStyle/AndroidFileChooserBindingSdkStyle.csproj new file mode 100644 index 00000000..c7147177 --- /dev/null +++ b/src/AndroidFileChooserBindingSdkStyle/AndroidFileChooserBindingSdkStyle.csproj @@ -0,0 +1,16 @@ + + + net8.0-android + 21 + enable + enable + + + + + + + + + + \ No newline at end of file diff --git a/src/AndroidFileChooserBindingSdkStyle/Transforms/EnumFields.xml b/src/AndroidFileChooserBindingSdkStyle/Transforms/EnumFields.xml new file mode 100644 index 00000000..22959957 --- /dev/null +++ b/src/AndroidFileChooserBindingSdkStyle/Transforms/EnumFields.xml @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/src/AndroidFileChooserBindingSdkStyle/Transforms/EnumMethods.xml b/src/AndroidFileChooserBindingSdkStyle/Transforms/EnumMethods.xml new file mode 100644 index 00000000..49216c61 --- /dev/null +++ b/src/AndroidFileChooserBindingSdkStyle/Transforms/EnumMethods.xml @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/src/AndroidFileChooserBindingSdkStyle/Transforms/Metadata.xml b/src/AndroidFileChooserBindingSdkStyle/Transforms/Metadata.xml new file mode 100644 index 00000000..ea974e92 --- /dev/null +++ b/src/AndroidFileChooserBindingSdkStyle/Transforms/Metadata.xml @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/src/JavaFileStorageBindingsStkStyle/Additions/AboutAdditions.txt b/src/JavaFileStorageBindingsStkStyle/Additions/AboutAdditions.txt new file mode 100644 index 00000000..2775bd36 --- /dev/null +++ b/src/JavaFileStorageBindingsStkStyle/Additions/AboutAdditions.txt @@ -0,0 +1,48 @@ +Additions allow you to add arbitrary C# to the generated classes +before they are compiled. This can be helpful for providing convenience +methods or adding pure C# classes. + +== Adding Methods to Generated Classes == + +Let's say the library being bound has a Rectangle class with a constructor +that takes an x and y position, and a width and length size. It will look like +this: + +public partial class Rectangle +{ + public Rectangle (int x, int y, int width, int height) + { + // JNI bindings + } +} + +Imagine we want to add a constructor to this class that takes a Point and +Size structure instead of 4 ints. We can add a new file called Rectangle.cs +with a partial class containing our new method: + +public partial class Rectangle +{ + public Rectangle (Point location, Size size) : + this (location.X, location.Y, size.Width, size.Height) + { + } +} + +At compile time, the additions class will be added to the generated class +and the final assembly will a Rectangle class with both constructors. + + +== Adding C# Classes == + +Another thing that can be done is adding fully C# managed classes to the +generated library. In the above example, let's assume that there isn't a +Point class available in Java or our library. The one we create doesn't need +to interact with Java, so we'll create it like a normal class in C#. + +By adding a Point.cs file with this class, it will end up in the binding library: + +public class Point +{ + public int X { get; set; } + public int Y { get; set; } +} \ No newline at end of file diff --git a/src/JavaFileStorageBindingsStkStyle/JavaFileStorageBindingsStkStyle.csproj b/src/JavaFileStorageBindingsStkStyle/JavaFileStorageBindingsStkStyle.csproj new file mode 100644 index 00000000..e29888f6 --- /dev/null +++ b/src/JavaFileStorageBindingsStkStyle/JavaFileStorageBindingsStkStyle.csproj @@ -0,0 +1,38 @@ + + + net8.0-android + 21 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/JavaFileStorageBindingsStkStyle/Transforms/EnumFields.xml b/src/JavaFileStorageBindingsStkStyle/Transforms/EnumFields.xml new file mode 100644 index 00000000..22959957 --- /dev/null +++ b/src/JavaFileStorageBindingsStkStyle/Transforms/EnumFields.xml @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/src/JavaFileStorageBindingsStkStyle/Transforms/EnumMethods.xml b/src/JavaFileStorageBindingsStkStyle/Transforms/EnumMethods.xml new file mode 100644 index 00000000..49216c61 --- /dev/null +++ b/src/JavaFileStorageBindingsStkStyle/Transforms/EnumMethods.xml @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/src/JavaFileStorageBindingsStkStyle/Transforms/Metadata.xml b/src/JavaFileStorageBindingsStkStyle/Transforms/Metadata.xml new file mode 100644 index 00000000..d1e7bc03 --- /dev/null +++ b/src/JavaFileStorageBindingsStkStyle/Transforms/Metadata.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/JavaFileStorageBindingsStkStyle/dropbox-core-sdk-5.4.6.jar b/src/JavaFileStorageBindingsStkStyle/dropbox-core-sdk-5.4.6.jar new file mode 100644 index 00000000..9004a26d Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/dropbox-core-sdk-5.4.6.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/commons-logging-1.1.1.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/commons-logging-1.1.1.jar new file mode 100644 index 00000000..8758a96b Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/commons-logging-1.1.1.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/google-api-client-1.30.5.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/google-api-client-1.30.5.jar new file mode 100644 index 00000000..73581bab Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/google-api-client-1.30.5.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/google-api-client-android-1.30.5.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/google-api-client-android-1.30.5.jar new file mode 100644 index 00000000..4f3769ff Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/google-api-client-android-1.30.5.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/google-api-services-drive-v2-rev102-1.16.0-rc.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/google-api-services-drive-v2-rev102-1.16.0-rc.jar new file mode 100644 index 00000000..5baa6abc Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/google-api-services-drive-v2-rev102-1.16.0-rc.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/google-http-client-1.32.1.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/google-http-client-1.32.1.jar new file mode 100644 index 00000000..8db57607 Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/google-http-client-1.32.1.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/google-http-client-android-1.32.1.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/google-http-client-android-1.32.1.jar new file mode 100644 index 00000000..da2a9691 Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/google-http-client-android-1.32.1.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/google-http-client-gson-1.16.0-rc.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/google-http-client-gson-1.16.0-rc.jar new file mode 100644 index 00000000..5387880d Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/google-http-client-gson-1.16.0-rc.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/google-http-client-jackson-1.16.0-rc.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/google-http-client-jackson-1.16.0-rc.jar new file mode 100644 index 00000000..411d2238 Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/google-http-client-jackson-1.16.0-rc.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/google-http-client-jackson2-1.32.1.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/google-http-client-jackson2-1.32.1.jar new file mode 100644 index 00000000..6220777b Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/google-http-client-jackson2-1.32.1.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/google-oauth-client-1.30.4.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/google-oauth-client-1.30.4.jar new file mode 100644 index 00000000..a7a8690b Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/google-oauth-client-1.30.4.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/grpc-context-1.22.1.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/grpc-context-1.22.1.jar new file mode 100644 index 00000000..4c7ff849 Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/grpc-context-1.22.1.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/httpclient-4.0.3.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/httpclient-4.0.3.jar new file mode 100644 index 00000000..fd0d3774 Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/httpclient-4.0.3.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/httpcore-4.0.1.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/httpcore-4.0.1.jar new file mode 100644 index 00000000..4638daa5 Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/httpcore-4.0.1.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/httpmime-4.0.3.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/httpmime-4.0.3.jar new file mode 100644 index 00000000..0dfd3312 Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/httpmime-4.0.3.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/json_simple-1.1.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/json_simple-1.1.jar new file mode 100644 index 00000000..f395f414 Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/json_simple-1.1.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/jsr305-3.0.2.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/jsr305-3.0.2.jar new file mode 100644 index 00000000..59222d9c Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/jsr305-3.0.2.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/opencensus-api-0.24.0.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/opencensus-api-0.24.0.jar new file mode 100644 index 00000000..dcace99b Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/opencensus-api-0.24.0.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gdrive/opencensus-contrib-http-util-0.24.0.jar b/src/JavaFileStorageBindingsStkStyle/gdrive/opencensus-contrib-http-util-0.24.0.jar new file mode 100644 index 00000000..4f91a71b Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gdrive/opencensus-contrib-http-util-0.24.0.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/gson-2.8.6.jar b/src/JavaFileStorageBindingsStkStyle/gson-2.8.6.jar new file mode 100644 index 00000000..4765c4af Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/gson-2.8.6.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/jackson-core-2.13.5.jar b/src/JavaFileStorageBindingsStkStyle/jackson-core-2.13.5.jar new file mode 100644 index 00000000..401dee31 Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/jackson-core-2.13.5.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/okhttp-4.12.0.jar b/src/JavaFileStorageBindingsStkStyle/okhttp-4.12.0.jar new file mode 100644 index 00000000..faf3fa86 Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/okhttp-4.12.0.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/okhttp-digest-3.1.0.jar b/src/JavaFileStorageBindingsStkStyle/okhttp-digest-3.1.0.jar new file mode 100644 index 00000000..af2c08ae Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/okhttp-digest-3.1.0.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/okio-3.6.0.jar b/src/JavaFileStorageBindingsStkStyle/okio-3.6.0.jar new file mode 100644 index 00000000..f3137562 Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/okio-3.6.0.jar differ diff --git a/src/JavaFileStorageBindingsStkStyle/okio-jvm-3.6.0.jar b/src/JavaFileStorageBindingsStkStyle/okio-jvm-3.6.0.jar new file mode 100644 index 00000000..ec8ad90f Binary files /dev/null and b/src/JavaFileStorageBindingsStkStyle/okio-jvm-3.6.0.jar differ diff --git a/src/KP2AKdbLibraryBindingSdkStyle/KP2AKdbLibraryBindingSdkStyle.csproj b/src/KP2AKdbLibraryBindingSdkStyle/KP2AKdbLibraryBindingSdkStyle.csproj new file mode 100644 index 00000000..d39c9b67 --- /dev/null +++ b/src/KP2AKdbLibraryBindingSdkStyle/KP2AKdbLibraryBindingSdkStyle.csproj @@ -0,0 +1,21 @@ + + + net8.0-android + 21 + enable + enable + + + + + + + + + + + + Designer + + + \ No newline at end of file diff --git a/src/KP2AKdbLibraryBindingSdkStyle/Transforms/EnumFields.xml b/src/KP2AKdbLibraryBindingSdkStyle/Transforms/EnumFields.xml new file mode 100644 index 00000000..22959957 --- /dev/null +++ b/src/KP2AKdbLibraryBindingSdkStyle/Transforms/EnumFields.xml @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/src/KP2AKdbLibraryBindingSdkStyle/Transforms/EnumMethods.xml b/src/KP2AKdbLibraryBindingSdkStyle/Transforms/EnumMethods.xml new file mode 100644 index 00000000..49216c61 --- /dev/null +++ b/src/KP2AKdbLibraryBindingSdkStyle/Transforms/EnumMethods.xml @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/src/KP2AKdbLibraryBindingSdkStyle/Transforms/Metadata.xml b/src/KP2AKdbLibraryBindingSdkStyle/Transforms/Metadata.xml new file mode 100644 index 00000000..0dd04e93 --- /dev/null +++ b/src/KP2AKdbLibraryBindingSdkStyle/Transforms/Metadata.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/KeePass.sln b/src/KeePass.sln index 7eade212..8fa56205 100644 --- a/src/KeePass.sln +++ b/src/KeePass.sln @@ -29,6 +29,24 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kp2aAutofillParser", "Kp2aA EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kp2aAutofillParserTest", "Kp2aAutofillParserTest\Kp2aAutofillParserTest.csproj", "{3D1560FF-86BB-4CB4-8367-80BA13B81C38}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZlibAndroidSdkStyle", "ZlibAndroidSdkStyle\ZlibAndroidSdkStyle.csproj", "{1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TwofishCipherSdkStyle", "TwofishCipherSdkStyle\TwofishCipherSdkStyle.csproj", "{69D491AA-3A31-4467-8216-3641A4F157BE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KeePassLib2AndroidSdkStyle", "KeePassLib2AndroidSdkStyle\KeePassLib2AndroidSdkStyle.csproj", "{6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KP2AKdbLibraryBindingSdkStyle", "KP2AKdbLibraryBindingSdkStyle\KP2AKdbLibraryBindingSdkStyle.csproj", "{BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AndroidFileChooserBindingSdkStyle", "AndroidFileChooserBindingSdkStyle\AndroidFileChooserBindingSdkStyle.csproj", "{C3A88E81-0809-49A4-A4EC-DF71A6200F41}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JavaFileStorageBindingsStkStyle", "JavaFileStorageBindingsStkStyle\JavaFileStorageBindingsStkStyle.csproj", "{4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kp2aBusinessLogicSdkStyle", "Kp2aBusinessLogicSdkStyle\Kp2aBusinessLogicSdkStyle.csproj", "{862D7A27-4211-4258-A4B0-741B4C0A73CF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kp2aAutofillParserSdkStyle", "Kp2aAutofillParserSdkStyle\Kp2aAutofillParserSdkStyle.csproj", "{2D5E3AEF-6EB2-4737-9ACB-921A22928682}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "keepass2android-appSdkStyle", "keepass2android-appSdkStyle\keepass2android-appSdkStyle.csproj", "{0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -335,6 +353,234 @@ Global {3D1560FF-86BB-4CB4-8367-80BA13B81C38}.ReleaseNoNet|Win32.Build.0 = Release|Any CPU {3D1560FF-86BB-4CB4-8367-80BA13B81C38}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU {3D1560FF-86BB-4CB4-8367-80BA13B81C38}.ReleaseNoNet|x64.Build.0 = Release|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.Debug|Win32.ActiveCfg = Debug|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.Debug|Win32.Build.0 = Debug|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.Debug|x64.ActiveCfg = Debug|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.Debug|x64.Build.0 = Debug|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.Release|Any CPU.Build.0 = Release|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.Release|Win32.ActiveCfg = Release|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.Release|Win32.Build.0 = Release|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.Release|x64.ActiveCfg = Release|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.Release|x64.Build.0 = Release|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.ReleaseNoNet|Win32.Build.0 = Release|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU + {1DF9DA08-D2FE-4227-BD53-761CD3F6CA42}.ReleaseNoNet|x64.Build.0 = Release|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.Debug|Win32.ActiveCfg = Debug|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.Debug|Win32.Build.0 = Debug|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.Debug|x64.ActiveCfg = Debug|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.Debug|x64.Build.0 = Debug|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.Release|Any CPU.Build.0 = Release|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.Release|Win32.ActiveCfg = Release|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.Release|Win32.Build.0 = Release|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.Release|x64.ActiveCfg = Release|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.Release|x64.Build.0 = Release|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.ReleaseNoNet|Win32.Build.0 = Release|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU + {69D491AA-3A31-4467-8216-3641A4F157BE}.ReleaseNoNet|x64.Build.0 = Release|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.Debug|Win32.ActiveCfg = Debug|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.Debug|Win32.Build.0 = Debug|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.Debug|x64.ActiveCfg = Debug|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.Debug|x64.Build.0 = Debug|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.Release|Any CPU.Build.0 = Release|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.Release|Win32.ActiveCfg = Release|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.Release|Win32.Build.0 = Release|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.Release|x64.ActiveCfg = Release|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.Release|x64.Build.0 = Release|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.ReleaseNoNet|Win32.Build.0 = Release|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU + {6E1DCFE1-D86D-480F-BE02-BBE8FD4601F0}.ReleaseNoNet|x64.Build.0 = Release|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.Debug|Win32.ActiveCfg = Debug|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.Debug|Win32.Build.0 = Debug|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.Debug|x64.ActiveCfg = Debug|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.Debug|x64.Build.0 = Debug|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.Release|Any CPU.Build.0 = Release|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.Release|Win32.ActiveCfg = Release|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.Release|Win32.Build.0 = Release|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.Release|x64.ActiveCfg = Release|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.Release|x64.Build.0 = Release|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.ReleaseNoNet|Win32.Build.0 = Release|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU + {BF542E42-E7A9-4C71-AA3B-DAC0F959F0D5}.ReleaseNoNet|x64.Build.0 = Release|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.Debug|Win32.ActiveCfg = Debug|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.Debug|Win32.Build.0 = Debug|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.Debug|x64.ActiveCfg = Debug|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.Debug|x64.Build.0 = Debug|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.Release|Any CPU.Build.0 = Release|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.Release|Win32.ActiveCfg = Release|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.Release|Win32.Build.0 = Release|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.Release|x64.ActiveCfg = Release|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.Release|x64.Build.0 = Release|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.ReleaseNoNet|Win32.Build.0 = Release|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU + {C3A88E81-0809-49A4-A4EC-DF71A6200F41}.ReleaseNoNet|x64.Build.0 = Release|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.Debug|Win32.ActiveCfg = Debug|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.Debug|Win32.Build.0 = Debug|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.Debug|x64.ActiveCfg = Debug|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.Debug|x64.Build.0 = Debug|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.Release|Any CPU.Build.0 = Release|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.Release|Win32.ActiveCfg = Release|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.Release|Win32.Build.0 = Release|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.Release|x64.ActiveCfg = Release|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.Release|x64.Build.0 = Release|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.ReleaseNoNet|Win32.Build.0 = Release|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU + {4F2E6B45-2C9F-4DF6-A9DC-9F81DC8681BC}.ReleaseNoNet|x64.Build.0 = Release|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.Debug|Win32.ActiveCfg = Debug|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.Debug|Win32.Build.0 = Debug|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.Debug|x64.ActiveCfg = Debug|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.Debug|x64.Build.0 = Debug|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.Release|Any CPU.Build.0 = Release|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.Release|Win32.ActiveCfg = Release|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.Release|Win32.Build.0 = Release|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.Release|x64.ActiveCfg = Release|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.Release|x64.Build.0 = Release|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.ReleaseNoNet|Win32.Build.0 = Release|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU + {862D7A27-4211-4258-A4B0-741B4C0A73CF}.ReleaseNoNet|x64.Build.0 = Release|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.Debug|Win32.ActiveCfg = Debug|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.Debug|Win32.Build.0 = Debug|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.Debug|x64.ActiveCfg = Debug|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.Debug|x64.Build.0 = Debug|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.Release|Any CPU.Build.0 = Release|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.Release|Win32.ActiveCfg = Release|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.Release|Win32.Build.0 = Release|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.Release|x64.ActiveCfg = Release|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.Release|x64.Build.0 = Release|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.ReleaseNoNet|Win32.Build.0 = Release|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU + {2D5E3AEF-6EB2-4737-9ACB-921A22928682}.ReleaseNoNet|x64.Build.0 = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Debug|Mixed Platforms.Deploy.0 = Debug|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Debug|Win32.ActiveCfg = Debug|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Debug|Win32.Build.0 = Debug|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Debug|Win32.Deploy.0 = Debug|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Debug|x64.ActiveCfg = Debug|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Debug|x64.Build.0 = Debug|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Debug|x64.Deploy.0 = Debug|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Release|Any CPU.Build.0 = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Release|Any CPU.Deploy.0 = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Release|Mixed Platforms.Deploy.0 = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Release|Win32.ActiveCfg = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Release|Win32.Build.0 = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Release|Win32.Deploy.0 = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Release|x64.ActiveCfg = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Release|x64.Build.0 = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.Release|x64.Deploy.0 = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.ReleaseNoNet|Any CPU.Deploy.0 = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.ReleaseNoNet|Mixed Platforms.Deploy.0 = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.ReleaseNoNet|Win32.Build.0 = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.ReleaseNoNet|Win32.Deploy.0 = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.ReleaseNoNet|x64.Build.0 = Release|Any CPU + {0CCDA3EB-8A24-4A42-BC91-A4DD254504E7}.ReleaseNoNet|x64.Deploy.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/KeePassLib2Android/KeePassLib2Android.csproj b/src/KeePassLib2Android/KeePassLib2Android.csproj index 849a5cf5..eb445c9f 100644 --- a/src/KeePassLib2Android/KeePassLib2Android.csproj +++ b/src/KeePassLib2Android/KeePassLib2Android.csproj @@ -32,9 +32,10 @@ True bin\Release prompt - 4 + 5 False False + x86 none @@ -149,7 +150,6 @@ - @@ -179,5 +179,8 @@ KP2AKdbLibraryBinding + + + \ No newline at end of file diff --git a/src/KeePassLib2Android/Serialization/ProtoBuf/KdbpFile.cs b/src/KeePassLib2Android/Serialization/ProtoBuf/KdbpFile.cs deleted file mode 100644 index c5b5f669..00000000 --- a/src/KeePassLib2Android/Serialization/ProtoBuf/KdbpFile.cs +++ /dev/null @@ -1,1444 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using KeePassLib.Collections; -using KeePassLib.Cryptography; -using KeePassLib.Security; -using KeePassLib.Utility; -using ProtoBuf; -using ProtoBuf.Meta; - -namespace KeePassLib.Serialization -{ - public class KdbpFile - { - public const string FileNameExtension = "kdbp"; - - /// - /// Precompiles the serializer classes for faster first-run execution - /// - public static void PrepareSerializer() - { - RuntimeTypeModel.Default[typeof(PwDatabaseBuffer)].CompileInPlace(); - } - - /// - /// Determines whether the database pointed to by the specified ioc should be (de)serialised in default (xml) or protocol buffers format. - /// - public static KdbxFormat GetFormatToUse(string fileExt) - { - return fileExt.Equals(KdbpFile.FileNameExtension, StringComparison.OrdinalIgnoreCase) ? KdbxFormat.ProtocolBuffers : - (fileExt.Equals("xml", StringComparison.OrdinalIgnoreCase) ? KdbxFormat.PlainXml : KdbxFormat.Default); - } - - public static void WriteDocument(PwDatabase database, Stream stream, byte[] protectedStreamKey, byte[] hashOfHeader) - { - var context = new SerializationContext - { - Context = new BufferContext(database, - new CryptoRandomStream(CrsAlgorithm.Salsa20, protectedStreamKey), hashOfHeader) - }; - - RuntimeTypeModel.Default.Serialize(stream, new PwDatabaseBuffer(database), context); - } - - public static void ReadDocument(PwDatabase database, Stream stream, byte[] protectedStreamKey, byte[] expectedHashOfHeader) - { - - var context = new BufferContext(database, new CryptoRandomStream(CrsAlgorithm.Salsa20, protectedStreamKey)); - - // Deserialisation will occur into the database already in context. - RuntimeTypeModel.Default.Deserialize(stream, null, typeof(PwDatabaseBuffer), new SerializationContext { Context = context }); - - if (expectedHashOfHeader.Length > 0 && - !KeePassLib.Utility.MemUtil.ArraysEqual(context.HeaderHash, expectedHashOfHeader)) - { - throw new IOException(KeePassLib.Resources.KLRes.FileCorrupted); - } - } - - private class BufferContext - { - // ProtectedBinary objects may be referred to multipe times by entry histories, so reference them only once by ensuring a 1:1 mapping to the buffer wrapping them. - public readonly Dictionary BinaryPool = new Dictionary(); - - public readonly PwDatabase Database; - public readonly CryptoRandomStream RandomStream; - public byte[] HeaderHash; - - public BufferContext(PwDatabase database, CryptoRandomStream randomStream, byte[] pbHashOfHeader = null) - { - Database = database; - RandomStream = randomStream; - HeaderHash = pbHashOfHeader; - } - } - - [ProtoContract] - private class PwDatabaseBuffer - { - #region Serialization - - private PwDatabase mDatabase; - private PwDeletedObjectListBuffer mDeletedObjects; - private PwCustomIconListBuffer mCustomIcons; - - public PwDatabaseBuffer(PwDatabase database) - { - mDatabase = database; - mDeletedObjects = new PwDeletedObjectListBuffer(mDatabase.DeletedObjects); - mCustomIcons = new PwCustomIconListBuffer(mDatabase.CustomIcons); - } - - [ProtoBeforeSerialization] - private void BeforeSerialization(SerializationContext context) - { - var bufferContext = (BufferContext)context.Context; - - System.Diagnostics.Debug.Assert(mDatabase == bufferContext.Database); - - HeaderHash = bufferContext.HeaderHash; - } - #endregion - - #region Deserialization - public PwDatabaseBuffer() - { - } - - [ProtoBeforeDeserialization] - private void BeforeDeserialization(SerializationContext context) - { - var bufferContext = (BufferContext)context.Context; - - mDatabase = bufferContext.Database; - mDeletedObjects = new PwDeletedObjectListBuffer(mDatabase.DeletedObjects); - mCustomIcons = new PwCustomIconListBuffer(mDatabase.CustomIcons); - } - - [ProtoAfterDeserialization] - private void AfterDeserialization(SerializationContext context) - { - var bufferContext = (BufferContext)context.Context; - - bufferContext.HeaderHash = HeaderHash; - } - #endregion - - [ProtoMember(1)] - public string Generator - { - get { return PwDatabase.LocalizedAppName; } - set { /* Ignore */ } - } - - [ProtoMember(2, OverwriteList = true)] - public byte[] HeaderHash; - - [ProtoMember(3)] - public string Name - { - get { return mDatabase.Name; } - set { mDatabase.Name = value; } - } - - [ProtoMember(4)] - public DateTime NameChanged - { - get { return mDatabase.NameChanged.ToUniversalTime(); } - set { mDatabase.NameChanged = value.ToLocalTime(); } - } - - [ProtoMember(5)] - public string Description - { - get { return mDatabase.Description; } - set { mDatabase.Description = value; } - } - - [ProtoMember(6)] - public DateTime DescriptionChanged - { - get { return mDatabase.DescriptionChanged.ToUniversalTime(); } - set { mDatabase.DescriptionChanged = value.ToLocalTime(); } - } - - [ProtoMember(7)] - public string DefaultUserName - { - get { return mDatabase.DefaultUserName; } - set { mDatabase.DefaultUserName = value; } - } - - [ProtoMember(8)] - public DateTime DefaultUserNameChanged - { - get { return mDatabase.DefaultUserNameChanged.ToUniversalTime(); } - set { mDatabase.DefaultUserNameChanged = value.ToLocalTime(); } - } - - [ProtoMember(9)] - public uint MaintenanceHistoryDays - { - get { return mDatabase.MaintenanceHistoryDays; } - set { mDatabase.MaintenanceHistoryDays = value; } - } - - [ProtoMember(10)] - public int Color - { - get { return mDatabase.Color.ToArgb(); } - set { mDatabase.Color = System.Drawing.Color.FromArgb(value); } - } - - [ProtoMember(11)] - public DateTime MasterKeyChanged - { - get { return mDatabase.MasterKeyChanged.ToUniversalTime(); } - set { mDatabase.MasterKeyChanged = value.ToLocalTime(); } - } - - [ProtoMember(12)] - public long MasterKeyChangeRec - { - get { return mDatabase.MasterKeyChangeRec; } - set { mDatabase.MasterKeyChangeRec = value; } - } - - [ProtoMember(13)] - public long MasterKeyChangeForce - { - get { return mDatabase.MasterKeyChangeForce; } - set { mDatabase.MasterKeyChangeForce = value; } - } - - [ProtoMember(14)] - public MemoryProtectionConfigBuffer MemoryProtection - { - get { return new MemoryProtectionConfigBuffer(mDatabase.MemoryProtection); } - set { mDatabase.MemoryProtection = value.MemoryProtectionConfig; } - } - - [ProtoMember(15)] - public PwCustomIconListBuffer CustomIcons - { - get { return mCustomIcons; } - } - - [ProtoMember(16)] - public bool RecycleBinEnabled - { - get { return mDatabase.RecycleBinEnabled; } - set { mDatabase.RecycleBinEnabled = value; } - } - - [ProtoMember(17, OverwriteList = true)] - public byte[] RecycleBinUuid - { - get { return mDatabase.RecycleBinUuid.UuidBytes; } - set { mDatabase.RecycleBinUuid = new PwUuid(value); } - } - - [ProtoMember(18)] - public DateTime RecycleBinChanged - { - get { return mDatabase.RecycleBinChanged.ToUniversalTime(); } - set { mDatabase.RecycleBinChanged = value.ToLocalTime(); } - } - - [ProtoMember(19, OverwriteList = true)] - public byte[] EntryTemplatesGroup - { - get { return mDatabase.EntryTemplatesGroup.UuidBytes; } - set { mDatabase.EntryTemplatesGroup = new PwUuid(value); } - } - - [ProtoMember(20)] - public DateTime EntryTemplatesGroupChanged - { - get { return mDatabase.EntryTemplatesGroupChanged.ToUniversalTime(); } - set { mDatabase.EntryTemplatesGroupChanged = value.ToLocalTime(); } - } - - [ProtoMember(21)] - public int HistoryMaxItems - { - get { return mDatabase.HistoryMaxItems; } - set { mDatabase.HistoryMaxItems = value; } - } - - [ProtoMember(22)] - public long HistoryMaxSize - { - get { return mDatabase.HistoryMaxSize; } - set { mDatabase.HistoryMaxSize = value; } - } - - [ProtoMember(23, OverwriteList = true)] - public byte[] LastSelectedGroup - { - get { return mDatabase.LastSelectedGroup.UuidBytes; } - set { mDatabase.LastSelectedGroup = new PwUuid(value); } - } - - [ProtoMember(24, OverwriteList = true)] - public byte[] LastTopVisibleGroup - { - get { return mDatabase.LastTopVisibleGroup.UuidBytes; } - set { mDatabase.LastTopVisibleGroup = new PwUuid(value); } - } - - [ProtoMember(25)] - public StringDictionaryExBuffer CustomData - { - get { return new StringDictionaryExBuffer(mDatabase.CustomData); } - set { mDatabase.CustomData = value.StringDictionaryEx; } - } - - [ProtoMember(27)] - public PwGroupBuffer RootGroup - { - get { return new PwGroupBuffer(mDatabase.RootGroup); } - set { mDatabase.RootGroup = value.Group; } - } - - [ProtoMember(28)] - public PwDeletedObjectListBuffer DeletedObjects - { - get { return mDeletedObjects; } - } - } - - [ProtoContract] - private class StringDictionaryExBuffer : IEnumerable> - { - #region Serialization - private StringDictionaryEx mStringDictionaryEx; - - public StringDictionaryExBuffer(StringDictionaryEx stringDictionaryEx) - { - mStringDictionaryEx = stringDictionaryEx; - } - #endregion - - #region Deserialization - public StringDictionaryExBuffer() - { - mStringDictionaryEx = new StringDictionaryEx(); - } - - public StringDictionaryEx StringDictionaryEx { get { return mStringDictionaryEx; } } - - public void Add(KeyValuePair kvp) - { - mStringDictionaryEx.Set(kvp.Key, kvp.Value); - } - #endregion - - public IEnumerator> GetEnumerator() - { - return mStringDictionaryEx.GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - - [ProtoContract] - private class PwCustomIconListBuffer : IEnumerable - { - private List mCustomIcons; - #region Serialization - public PwCustomIconListBuffer(List customIcons) - { - mCustomIcons = customIcons; - } - #endregion - - #region Deserialization - public void Add(PwCustomIconBuffer item) - { - mCustomIcons.Add(item.CustomIcon); - } - #endregion - - public IEnumerator GetEnumerator() - { - foreach (var customIcon in mCustomIcons) - { - yield return new PwCustomIconBuffer(customIcon); - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - - [ProtoContract] - private class PwCustomIconBuffer - { - #region Serialization - private PwCustomIcon mCustomIcon; - public PwCustomIconBuffer(PwCustomIcon CustomIcon) - { - mCustomIcon = CustomIcon; - Uuid = mCustomIcon.Uuid.UuidBytes; - ImageData = mCustomIcon.ImageDataPng; - } - #endregion - - #region Deserialization - public PwCustomIconBuffer() - { - } - - [ProtoAfterDeserialization] - private void AfterDeserialization(SerializationContext context) - { - mCustomIcon = new PwCustomIcon(new PwUuid(Uuid), ImageData); - } - - public PwCustomIcon CustomIcon { get { return mCustomIcon; } } - #endregion - - [ProtoMember(1, OverwriteList = true)] - public byte[] Uuid; - - [ProtoMember(2, OverwriteList = true)] - public byte[] ImageData; - } - - [ProtoContract] - private class MemoryProtectionConfigBuffer - { - #region Serialization - private readonly MemoryProtectionConfig mMemoryProtectionConfig; - - public MemoryProtectionConfigBuffer(MemoryProtectionConfig memoryProtectionConfig) - { - mMemoryProtectionConfig = memoryProtectionConfig; - } - #endregion - - #region Deserialization - public MemoryProtectionConfigBuffer() - { - mMemoryProtectionConfig = new MemoryProtectionConfig(); - } - - public MemoryProtectionConfig MemoryProtectionConfig { get { return mMemoryProtectionConfig; } } - #endregion - - [ProtoMember(1)] - public bool ProtectTitle - { - get { return mMemoryProtectionConfig.ProtectTitle; } - set { mMemoryProtectionConfig.ProtectTitle = value; } - } - - [ProtoMember(2)] - public bool ProtectUserName - { - get { return mMemoryProtectionConfig.ProtectUserName; } - set { mMemoryProtectionConfig.ProtectUserName = value; } - } - - [ProtoMember(3)] - public bool ProtectPassword - { - get { return mMemoryProtectionConfig.ProtectPassword; } - set { mMemoryProtectionConfig.ProtectPassword = value; } - } - - [ProtoMember(4)] - public bool ProtectUrl - { - get { return mMemoryProtectionConfig.ProtectUrl; } - set { mMemoryProtectionConfig.ProtectUrl = value; } - } - - [ProtoMember(5)] - public bool ProtectNotes - { - get { return mMemoryProtectionConfig.ProtectNotes; } - set { mMemoryProtectionConfig.ProtectNotes = value; } - } - } - - [ProtoContract] - private class PwDeletedObjectListBuffer : PwObjectListBufferBase - { - #region Serialization - public PwDeletedObjectListBuffer(PwObjectList objectList) - : base(objectList) - { - } - - protected override PwDeletedObjectBuffer CreateBuffer(PwDeletedObject item) - { - return new PwDeletedObjectBuffer(item); - } - #endregion - - #region Deserialization - public PwDeletedObjectListBuffer() - : base() - { - } - - public override void Add(PwDeletedObjectBuffer item) - { - ObjectList.Add(item.DeletedObject); - } - #endregion - } - - [ProtoContract] - private class PwDeletedObjectBuffer - { - #region Serialization - private readonly PwDeletedObject mDeletedObject; - - public PwDeletedObjectBuffer(PwDeletedObject deletedObject) - { - mDeletedObject = deletedObject; - } - #endregion - - #region Deserialization - public PwDeletedObjectBuffer() - { - mDeletedObject = new PwDeletedObject(); - } - - public PwDeletedObject DeletedObject { get { return mDeletedObject; } } - #endregion - - [ProtoMember(1, OverwriteList = true)] - public byte[] Uuid - { - get { return mDeletedObject.Uuid.UuidBytes; } - set { mDeletedObject.Uuid = new PwUuid(value); } - } - - [ProtoMember(2)] - public DateTime DeletionTime - { - get { return mDeletedObject.DeletionTime.ToUniversalTime(); } - set { mDeletedObject.DeletionTime = value.ToLocalTime(); } - } - } - - [ProtoContract] - private class PwGroupBuffer - { - #region Serialization - private readonly PwGroup mGroup; - private readonly PwGroupEntryListBuffer mEntries; - private readonly PwGroupGroupListBuffer mGroups; - - public PwGroupBuffer(PwGroup group) - { - mGroup = group; - mEntries = new PwGroupEntryListBuffer(mGroup); - mGroups = new PwGroupGroupListBuffer(mGroup); - } - #endregion - - #region Deserialization - public PwGroupBuffer() - { - mGroup = new PwGroup(false, false); - mEntries = new PwGroupEntryListBuffer(mGroup); - mGroups = new PwGroupGroupListBuffer(mGroup); - } - - public PwGroup Group { get { return mGroup; } } - #endregion - - [ProtoMember(1, OverwriteList = true)] - public byte[] Uuid - { - get { return mGroup.Uuid.UuidBytes; } - set { mGroup.Uuid = new PwUuid(value); } - } - - [ProtoMember(2)] - public string Name - { - get { return mGroup.Name; } - set { mGroup.Name = value; } - } - - [ProtoMember(3)] - public string Notes - { - get { return mGroup.Notes; } - set { mGroup.Notes = value; } - } - - [ProtoMember(4)] - public PwIcon IconId - { - get { return mGroup.IconId; } - set { mGroup.IconId = value; } - } - - [ProtoMember(5, OverwriteList = true)] - public byte[] CustomIconUuid - { - get { return mGroup.CustomIconUuid.Equals(PwUuid.Zero) ? null : mGroup.CustomIconUuid.UuidBytes; ; } - set { mGroup.CustomIconUuid = value == null ? PwUuid.Zero : new PwUuid(value); } - } - - [ProtoMember(6)] - public bool IsExpanded - { - get { return mGroup.IsExpanded; } - set { mGroup.IsExpanded = value; } - } - - [ProtoMember(7)] - public string DefaultAutoTypeSequence - { - get { return mGroup.DefaultAutoTypeSequence; } - set { mGroup.DefaultAutoTypeSequence = value; } - } - - [ProtoMember(8)] - public DateTime LastModificationTime - { - get { return mGroup.LastModificationTime.ToUniversalTime(); } - set { mGroup.LastModificationTime = value.ToLocalTime(); } - } - - [ProtoMember(9)] - public DateTime CreationTime - { - get { return mGroup.CreationTime.ToUniversalTime(); } - set { mGroup.CreationTime = value.ToLocalTime(); } - } - - [ProtoMember(10)] - public DateTime LastAccessTime - { - get { return mGroup.LastAccessTime.ToUniversalTime(); } - set { mGroup.LastAccessTime = value.ToLocalTime(); } - } - - [ProtoMember(11)] - public DateTime ExpiryTime - { - get { return mGroup.ExpiryTime.ToUniversalTime(); } - set { mGroup.ExpiryTime = value.ToLocalTime(); } - } - - [ProtoMember(12)] - public bool Expires - { - get { return mGroup.Expires; } - set { mGroup.Expires = value; } - } - - [ProtoMember(13)] - public ulong UsageCount - { - get { return mGroup.UsageCount; } - set { mGroup.UsageCount = value; } - } - - [ProtoMember(14)] - public DateTime LocationChanged - { - get { return mGroup.LocationChanged.ToUniversalTime(); } - set { mGroup.LocationChanged = value.ToLocalTime(); } - } - - [ProtoMember(15)] - public bool? EnableAutoType - { - get { return mGroup.EnableAutoType; } - set { mGroup.EnableAutoType = value; } - } - - [ProtoMember(16)] - public bool? EnableSearching - { - get { return mGroup.EnableSearching; } - set { mGroup.EnableSearching = value; } - } - - [ProtoMember(17, OverwriteList = true)] - public byte[] LastTopVisibleEntry - { - get { return mGroup.LastTopVisibleEntry.UuidBytes; } - set { mGroup.LastTopVisibleEntry = new PwUuid(value); } - } - - [ProtoMember(18)] - public PwGroupGroupListBuffer Groups - { - get { return mGroups; } - } - - [ProtoMember(19)] - public PwGroupEntryListBuffer Entries - { - get { return mEntries; } - } - } - - private abstract class PwObjectListBufferBase : IEnumerable - where TData : class, KeePassLib.Interfaces.IDeepCloneable - { - #region Serialization - private PwObjectList mObjectList; - - protected PwObjectListBufferBase(PwObjectList objectList) - { - mObjectList = objectList; - } - - protected abstract TDataBuffer CreateBuffer(TData item); - #endregion - - #region Deserialization - protected PwObjectListBufferBase() - { - mObjectList = new PwObjectList(); - } - - public PwObjectList ObjectList { get { return mObjectList; } } - - public abstract void Add(TDataBuffer item); - #endregion - - public IEnumerator GetEnumerator() - { - foreach (var item in mObjectList) - { - yield return CreateBuffer(item); - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - - [ProtoContract] - private class PwGroupGroupListBuffer : PwObjectListBufferBase - { - #region Serialization - private PwGroup mGroup; - public PwGroupGroupListBuffer(PwGroup group) - : base(group.Groups) - { - mGroup = group; - } - - protected override PwGroupBuffer CreateBuffer(PwGroup item) - { - return new PwGroupBuffer(item); - } - #endregion - - #region Deserialization - public override void Add(PwGroupBuffer item) - { - mGroup.AddGroup(item.Group, true); - } - #endregion - } - - [ProtoContract] - private class PwGroupEntryListBuffer : PwObjectListBufferBase - { - #region Serialization - private PwGroup mGroup; - public PwGroupEntryListBuffer(PwGroup group) - : base(group.Entries) - { - mGroup = group; - } - - protected override PwEntryBuffer CreateBuffer(PwEntry item) - { - return new PwEntryBuffer(item); - } - #endregion - - #region Deserialization - public override void Add(PwEntryBuffer item) - { - mGroup.AddEntry(item.Entry, true); - } - #endregion - } - - [ProtoContract] - private class PwEntryListBuffer : PwObjectListBufferBase - { - #region Serialization - public PwEntryListBuffer(PwObjectList entryList) - : base(entryList) - { - } - - protected override PwEntryBuffer CreateBuffer(PwEntry item) - { - return new PwEntryBuffer(item); - } - #endregion - - #region Deserialization - public PwEntryListBuffer() - : base() - { - } - - public override void Add(PwEntryBuffer item) - { - ObjectList.Add(item.Entry); - } - #endregion - } - - [ProtoContract] - private class PwEntryBuffer - { - #region Serialization - private readonly PwEntry mEntry; - private ProtectedStandardFieldDictionaryBuffer mEntryStandardStrings; - private ProtectedCustomFieldDictionaryBuffer mEntryCustomStrings; - private NamedProtectedBinaryListBuffer mEntryBinaries; - - public PwEntryBuffer(PwEntry entry) - { - mEntry = entry; - } - - [ProtoBeforeSerialization] - private void BeforeSerialization(SerializationContext context) - { - var bufferContext = (BufferContext)context.Context; - - // ProtectedStringDictionaryBuffer nver gets its own ProtoBeforeSerialization called as it's a list of objects rather than an object itself - List> customFields; - mEntryStandardStrings = new ProtectedStandardFieldDictionaryBuffer(mEntry.Strings, (int)mEntry.Strings.UCount, bufferContext, out customFields); - mEntryCustomStrings = new ProtectedCustomFieldDictionaryBuffer(customFields); - mEntryBinaries = new NamedProtectedBinaryListBuffer(mEntry.Binaries, (int)mEntry.Binaries.UCount, bufferContext); - } - #endregion - - #region Deserialization - public PwEntryBuffer() - { - mEntry = new PwEntry(false, false); - mEntryStandardStrings = new ProtectedStandardFieldDictionaryBuffer(mEntry.Strings); - mEntryCustomStrings = new ProtectedCustomFieldDictionaryBuffer(mEntry.Strings); - mEntryBinaries = new NamedProtectedBinaryListBuffer(mEntry.Binaries); - } - - public PwEntry Entry { get { return mEntry; } } - #endregion - - [ProtoMember(1, OverwriteList = true)] - public byte[] Uuid - { - get { return mEntry.Uuid.UuidBytes; } - set { mEntry.SetUuid(new PwUuid(value), false); } - } - - [ProtoMember(2)] - public PwIcon IconId - { - get { return mEntry.IconId; } - set { mEntry.IconId = value; } - } - - [ProtoMember(3, OverwriteList = true)] - public byte[] CustomIconUuid - { - get { return mEntry.CustomIconUuid.Equals(PwUuid.Zero) ? null : mEntry.CustomIconUuid.UuidBytes; } - set { mEntry.CustomIconUuid = value == null ? PwUuid.Zero : new PwUuid(value); } - } - - [ProtoMember(4)] - public int ForegroundColor - { - get { return mEntry.ForegroundColor.ToArgb(); } - set { mEntry.ForegroundColor = Color.FromArgb(value); } - } - - [ProtoMember(5)] - public int BackgroundColor - { - get { return mEntry.BackgroundColor.ToArgb(); } - set { mEntry.BackgroundColor = Color.FromArgb(value); } - } - - [ProtoMember(6)] - public string OverrideUrl - { - get { return mEntry.OverrideUrl; } - set { mEntry.OverrideUrl = value; } - } - - [ProtoMember(7)] - public IList Tags - { - get { return mEntry.Tags; } - } - - [ProtoMember(8)] - public DateTime LastModificationTime - { - get { return mEntry.LastModificationTime.ToUniversalTime(); } - set { mEntry.LastModificationTime = value.ToLocalTime(); } - } - - [ProtoMember(9)] - public DateTime CreationTime - { - get { return mEntry.CreationTime.ToUniversalTime(); } - set { mEntry.CreationTime = value.ToLocalTime(); } - } - - [ProtoMember(10)] - public DateTime LastAccessTime - { - get { return mEntry.LastAccessTime.ToUniversalTime(); } - set { mEntry.LastAccessTime = value.ToLocalTime(); } - } - - [ProtoMember(11)] - public DateTime ExpiryTime - { - get { return mEntry.ExpiryTime.ToUniversalTime(); } - set { mEntry.ExpiryTime = value.ToLocalTime(); } - } - - [ProtoMember(12)] - public bool Expires - { - get { return mEntry.Expires; } - set { mEntry.Expires = value; } - } - - [ProtoMember(13)] - public ulong UsageCount - { - get { return mEntry.UsageCount; } - set { mEntry.UsageCount = value; } - } - - [ProtoMember(14)] - public DateTime LocationChanged - { - get { return mEntry.LocationChanged.ToUniversalTime(); } - set { mEntry.LocationChanged = value.ToLocalTime(); } - } - - [ProtoMember(15)] - public ProtectedStandardFieldDictionaryBuffer StandardStrings - { - get { return mEntryStandardStrings; } - } - - [ProtoMember(16)] - public ProtectedCustomFieldDictionaryBuffer CustomStrings - { - get { return mEntryCustomStrings; } - } - - [ProtoMember(17, AsReference = true)] - public NamedProtectedBinaryListBuffer Binaries - { - get { return mEntryBinaries; } - } - - [ProtoMember(18)] - public AutoTypeConfigBuffer AutoType - { - get { return new AutoTypeConfigBuffer(mEntry.AutoType); } - set { mEntry.AutoType = value.AutoTypeConfig; } - } - - [ProtoMember(19)] - public PwEntryListBuffer History - { - get { return new PwEntryListBuffer(mEntry.History); } - set { mEntry.History = value.ObjectList; } - } - - } - - private abstract class ProtectedStringDictionaryBuffer : IEnumerable> - { - #region Serialization - private List> mProtectedStringBuffers; - - /// - /// Serialisation constructor. Reads strings from dictionary, does not write to it - /// - protected ProtectedStringDictionaryBuffer(int capacity) - { - mProtectedStringBuffers = new List>(capacity); - } - - protected void AddStringField(TKey key, ProtectedString value, bool? overrideProtect) - { - mProtectedStringBuffers.Add(new KeyValuePair(key, new ProtectedStringBuffer(value, overrideProtect))); - } - - public IEnumerator> GetEnumerator() - { - return mProtectedStringBuffers.GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - #endregion - - #region Deserialization - private readonly ProtectedStringDictionary mDictionary; - - /// - /// Deerialisation constructor. Writes strings to dictionary, does read from it - /// - protected ProtectedStringDictionaryBuffer(ProtectedStringDictionary dictionary) - { - mDictionary = dictionary; - } - - public void Add(KeyValuePair item) - { - mDictionary.Set(GetFieldName(item.Key), item.Value.ProtectedString); - } - - protected abstract string GetFieldName(TKey key); - - #endregion - } - - [ProtoContract] - private class ProtectedCustomFieldDictionaryBuffer : ProtectedStringDictionaryBuffer - { - public ProtectedCustomFieldDictionaryBuffer(List> entryStrings) - : base(entryStrings.Count) - { - foreach (var kvp in entryStrings) - { - System.Diagnostics.Debug.Assert(!PwDefs.IsStandardField(kvp.Key)); - AddStringField(kvp.Key, kvp.Value, null); - } - } - - public ProtectedCustomFieldDictionaryBuffer(ProtectedStringDictionary dictionary) - : base(dictionary) - { } - - protected override string GetFieldName(string key) - { - return key; - } - } - - [ProtoContract] - private class ProtectedStandardFieldDictionaryBuffer : ProtectedStringDictionaryBuffer - { - public enum StandardField - { - Title, - UserName, - Password, - Url, - Notes - } - - public ProtectedStandardFieldDictionaryBuffer(IEnumerable> entryStrings, int entryStringCount, BufferContext context, - out List> customFields) // Perf optimisation - return the custom fields so we don't have to determine them again - : base(entryStringCount) - { - customFields = new List>(entryStringCount); - - var database = context.Database; - - foreach (var kvp in entryStrings) - { - var field = GetField(kvp.Key); - - if (field.HasValue) - { - // Logic from KdbxFile.Write - - bool? overrideProtect = null; - // Adjust memory protection setting (which might be different - // from the database default, e.g. due to an import which - // didn't specify the correct setting) - switch (field.Value) - { - case StandardField.Title: - overrideProtect = database.MemoryProtection.ProtectTitle; - break; - case StandardField.UserName: - overrideProtect = database.MemoryProtection.ProtectUserName; - break; - case StandardField.Password: - overrideProtect = database.MemoryProtection.ProtectPassword; - break; - case StandardField.Url: - overrideProtect = database.MemoryProtection.ProtectUrl; - break; - case StandardField.Notes: - overrideProtect = database.MemoryProtection.ProtectNotes; - break; - } - - AddStringField(field.Value, kvp.Value, overrideProtect); - } - else - { - customFields.Add(kvp); - } - } - } - - private static StandardField? GetField(string fieldName) - { - switch (fieldName) - { - case PwDefs.TitleField: - return StandardField.Title; - case PwDefs.UserNameField: - return StandardField.UserName; - case PwDefs.PasswordField: - return StandardField.Password; - case PwDefs.UrlField: - return StandardField.Url; - case PwDefs.NotesField: - return StandardField.Notes; - - default: - System.Diagnostics.Debug.Assert(!PwDefs.IsStandardField(fieldName)); - return null; - } - } - - public ProtectedStandardFieldDictionaryBuffer(ProtectedStringDictionary dictionary) - : base(dictionary) - { } - - protected override string GetFieldName(StandardField key) - { - switch (key) - { - case StandardField.Title: - return PwDefs.TitleField; - case StandardField.UserName: - return PwDefs.UserNameField; - case StandardField.Password: - return PwDefs.PasswordField; - case StandardField.Url: - return PwDefs.UrlField; - case StandardField.Notes: - return PwDefs.NotesField; - default: - throw new ArgumentOutOfRangeException(); - } - } - } - - [ProtoContract] - private class ProtectedStringBuffer - { - #region Serialisation - private ProtectedString mProtectedString; - - public ProtectedStringBuffer(ProtectedString protectedString, bool? overrideProtect) - { - mProtectedString = protectedString; - IsProtected = overrideProtect.GetValueOrDefault(mProtectedString.IsProtected); - } - - [ProtoBeforeSerialization] - private void BeforeSerialization(SerializationContext context) - { - if (IsProtected) - { - Value = mProtectedString.ReadXorredString(((BufferContext)context.Context).RandomStream); - } - else - { - Value = mProtectedString.ReadUtf8(); - } - } - #endregion - - #region Deserialisation - public ProtectedStringBuffer() - { - } - - [ProtoAfterDeserialization] - private void AfterDeserialization(SerializationContext context) - { - if (IsProtected) - { - byte[] pbPad = ((BufferContext)context.Context).RandomStream.GetRandomBytes((uint)Value.Length); - mProtectedString = new ProtectedString(IsProtected, new XorredBuffer(Value, pbPad)); - } - else - { - mProtectedString = new ProtectedString(IsProtected, Value); - } - } - - public ProtectedString ProtectedString { get { return mProtectedString; } } - - #endregion - - [ProtoMember(1)] - public bool IsProtected; - - [ProtoMember(2, OverwriteList = true)] - public byte[] Value; - } - - [ProtoContract] - private class NamedProtectedBinaryListBuffer : IEnumerable - { - #region Serialisation - private readonly List mNamedBinaries; - - public NamedProtectedBinaryListBuffer(IEnumerable> binaries, int binariesCount, BufferContext context) - { - mNamedBinaries = new List(binariesCount); - foreach (var kvp in binaries) - { - NamedProtectedBinaryBuffer namedProtectedBinaryBuffer; - if (!context.BinaryPool.TryGetValue(kvp.Value, out namedProtectedBinaryBuffer)) - { - // Hasn't been put in the pool yet, so create it - namedProtectedBinaryBuffer = new NamedProtectedBinaryBuffer(kvp); - context.BinaryPool.Add(kvp.Value, namedProtectedBinaryBuffer); - } - mNamedBinaries.Add(namedProtectedBinaryBuffer); - } - } - - public IEnumerator GetEnumerator() - { - return mNamedBinaries.GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - #endregion - - #region Deserialization - private readonly ProtectedBinaryDictionary mBinaryDictionary; - - public NamedProtectedBinaryListBuffer(ProtectedBinaryDictionary binaryDictionary) - { - mBinaryDictionary = binaryDictionary; - } - - public void Add(NamedProtectedBinaryBuffer item) - { - mBinaryDictionary.Set(item.Name, item.ProtectedBinary); - } - #endregion - } - - [ProtoContract] - private class NamedProtectedBinaryBuffer - { - #region Serialization - private ProtectedBinary mProtectedBinary; - public NamedProtectedBinaryBuffer(KeyValuePair namedBinary) - { - Name = namedBinary.Key; - mProtectedBinary = namedBinary.Value; - IsProtected = mProtectedBinary.IsProtected; - } - - [ProtoBeforeSerialization] - private void BeforeSerialization(SerializationContext context) - { - if (IsProtected) - { - Value = mProtectedBinary.ReadXorredData(((BufferContext)context.Context).RandomStream); - } - else - { - Value = mProtectedBinary.ReadData(); - } - } - #endregion - - #region Deserialisation - public NamedProtectedBinaryBuffer() - { - } - - [ProtoAfterDeserialization] - private void AfterDeserialization(SerializationContext context) - { - if (IsProtected) - { - byte[] pbPad = ((BufferContext)context.Context).RandomStream.GetRandomBytes((uint)Value.Length); - mProtectedBinary = new ProtectedBinary(IsProtected, new XorredBuffer(Value, pbPad)); - } - else - { - mProtectedBinary = new ProtectedBinary(IsProtected, Value); - } - } - - public ProtectedBinary ProtectedBinary { get { return mProtectedBinary; } } - - #endregion - - [ProtoMember(1)] - public string Name; - - [ProtoMember(2)] - public bool IsProtected; - - [ProtoMember(3, OverwriteList = true)] - public byte[] Value; - } - - [ProtoContract] - private class AutoTypeConfigBuffer - { - private readonly AutoTypeAssociationsBuffer mAutoTypeAssociationsBuffer; - #region Serialization - private AutoTypeConfig mAutoTypeConfig; - public AutoTypeConfigBuffer(AutoTypeConfig autoTypeConfig) - { - mAutoTypeConfig = autoTypeConfig; - mAutoTypeAssociationsBuffer = new AutoTypeAssociationsBuffer(mAutoTypeConfig); - } - #endregion - - #region Deserialization - public AutoTypeConfigBuffer() - { - mAutoTypeConfig = new AutoTypeConfig(); - mAutoTypeAssociationsBuffer = new AutoTypeAssociationsBuffer(mAutoTypeConfig); - } - - public AutoTypeConfig AutoTypeConfig { get { return mAutoTypeConfig; } } - #endregion - - [ProtoMember(1)] - public bool Enabled - { - get { return mAutoTypeConfig.Enabled; } - set { mAutoTypeConfig.Enabled = value; } - } - - [ProtoMember(2)] - public AutoTypeObfuscationOptions ObfuscationOptions - { - get { return mAutoTypeConfig.ObfuscationOptions; } - set { mAutoTypeConfig.ObfuscationOptions = value; } - } - - [ProtoMember(3)] - public string DefaultSequence - { - get { return mAutoTypeConfig.DefaultSequence; } - set { mAutoTypeConfig.DefaultSequence = value; } - } - - [ProtoMember(4)] - public AutoTypeAssociationsBuffer Associations - { - get { return mAutoTypeAssociationsBuffer; } - } - } - - [ProtoContract] - private class AutoTypeAssociationsBuffer : IEnumerable - { - #region Serialization - private AutoTypeConfig mAutoTypeConfig; - - public AutoTypeAssociationsBuffer(AutoTypeConfig autoTypeConfig) - { - mAutoTypeConfig = autoTypeConfig; - } - - public IEnumerator GetEnumerator() - { - foreach (var autoTypeAssociation in mAutoTypeConfig.Associations) - { - yield return new AutoTypeAssociationBuffer(autoTypeAssociation); - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - #endregion - - #region Deserialization - public void Add(AutoTypeAssociationBuffer value) - { - mAutoTypeConfig.Add(value.AutoTypeAssociation); - } - #endregion - } - - [ProtoContract] - private class AutoTypeAssociationBuffer - { - #region Serialization - private AutoTypeAssociation mAutoTypeAssociation; - - public AutoTypeAssociationBuffer(AutoTypeAssociation autoTypeAssociation) - { - mAutoTypeAssociation = autoTypeAssociation; - } - #endregion - - #region Deserialization - public AutoTypeAssociationBuffer() - { - mAutoTypeAssociation = new AutoTypeAssociation(); - } - - public AutoTypeAssociation AutoTypeAssociation { get { return mAutoTypeAssociation; } } - #endregion - - [ProtoMember(1)] - public string WindowName - { - get { return mAutoTypeAssociation.WindowName; } - set { mAutoTypeAssociation.WindowName = value; } - } - - [ProtoMember(2)] - public string Sequence - { - get { return mAutoTypeAssociation.Sequence; } - set { mAutoTypeAssociation.Sequence = value; } - } - } - } -} diff --git a/src/KeePassLib2Android/Utility/UrlUtil.cs b/src/KeePassLib2Android/Utility/UrlUtil.cs index cca71920..e6122114 100644 --- a/src/KeePassLib2Android/Utility/UrlUtil.cs +++ b/src/KeePassLib2Android/Utility/UrlUtil.cs @@ -687,25 +687,6 @@ namespace KeePassLib.Utility return false; } - public static string GetTempPath() - { - string strDir; - if (NativeLib.IsUnix()) - strDir = NativeMethods.GetUserRuntimeDir(); -#if KeePassUAP - else strDir = Windows.Storage.ApplicationData.Current.TemporaryFolder.Path; -#else - else strDir = Path.GetTempPath(); -#endif - - try - { - if (!Directory.Exists(strDir)) Directory.CreateDirectory(strDir); - } - catch (Exception) { Debug.Assert(false); } - - return strDir; - } #if !KeePassLibSD // Structurally mostly equivalent to UrlUtil.GetFileInfos diff --git a/src/KeePassLib2AndroidSdkStyle/Collections/AutoTypeConfig.cs b/src/KeePassLib2AndroidSdkStyle/Collections/AutoTypeConfig.cs new file mode 100644 index 00000000..dd2d5a28 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Collections/AutoTypeConfig.cs @@ -0,0 +1,244 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +using KeePassLib.Interfaces; + +namespace KeePassLib.Collections +{ + [Flags] + public enum AutoTypeObfuscationOptions + { + None = 0, + UseClipboard = 1 + } + + public sealed class AutoTypeAssociation : IEquatable, + IDeepCloneable + { + private string m_strWindow = string.Empty; + public string WindowName + { + get { return m_strWindow; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_strWindow = value; + } + } + + private string m_strSequence = string.Empty; + public string Sequence + { + get { return m_strSequence; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_strSequence = value; + } + } + + public AutoTypeAssociation() { } + + public AutoTypeAssociation(string strWindow, string strSeq) + { + if(strWindow == null) throw new ArgumentNullException("strWindow"); + if(strSeq == null) throw new ArgumentNullException("strSeq"); + + m_strWindow = strWindow; + m_strSequence = strSeq; + } + + public bool Equals(AutoTypeAssociation other) + { + if(other == null) return false; + + if(m_strWindow != other.m_strWindow) return false; + if(m_strSequence != other.m_strSequence) return false; + + return true; + } + + public AutoTypeAssociation CloneDeep() + { + return (AutoTypeAssociation)this.MemberwiseClone(); + } + } + + /// + /// A list of auto-type associations. + /// + public sealed class AutoTypeConfig : IEquatable, + IDeepCloneable + { + private bool m_bEnabled = true; + private AutoTypeObfuscationOptions m_atooObfuscation = + AutoTypeObfuscationOptions.None; + private string m_strDefaultSequence = string.Empty; + private List m_lWindowAssocs = + new List(); + + /// + /// Specify whether auto-type is enabled or not. + /// + public bool Enabled + { + get { return m_bEnabled; } + set { m_bEnabled = value; } + } + + /// + /// Specify whether the typing should be obfuscated. + /// + public AutoTypeObfuscationOptions ObfuscationOptions + { + get { return m_atooObfuscation; } + set { m_atooObfuscation = value; } + } + + /// + /// The default keystroke sequence that is auto-typed if + /// no matching window is found in the Associations + /// container. + /// + public string DefaultSequence + { + get { return m_strDefaultSequence; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_strDefaultSequence = value; + } + } + + /// + /// Get all auto-type window/keystroke sequence pairs. + /// + public IEnumerable Associations + { + get { return m_lWindowAssocs; } + } + + public int AssociationsCount + { + get { return m_lWindowAssocs.Count; } + } + + /// + /// Construct a new auto-type associations list. + /// + public AutoTypeConfig() + { + } + + /// + /// Remove all associations. + /// + public void Clear() + { + m_lWindowAssocs.Clear(); + } + + /// + /// Clone the auto-type associations list. + /// + /// New, cloned object. + public AutoTypeConfig CloneDeep() + { + AutoTypeConfig newCfg = new AutoTypeConfig(); + + newCfg.m_bEnabled = m_bEnabled; + newCfg.m_atooObfuscation = m_atooObfuscation; + newCfg.m_strDefaultSequence = m_strDefaultSequence; + + foreach(AutoTypeAssociation a in m_lWindowAssocs) + newCfg.Add(a.CloneDeep()); + + return newCfg; + } + + public bool Equals(AutoTypeConfig other) + { + if(other == null) { Debug.Assert(false); return false; } + + if(m_bEnabled != other.m_bEnabled) return false; + if(m_atooObfuscation != other.m_atooObfuscation) return false; + if(m_strDefaultSequence != other.m_strDefaultSequence) return false; + + if(m_lWindowAssocs.Count != other.m_lWindowAssocs.Count) return false; + for(int i = 0; i < m_lWindowAssocs.Count; ++i) + { + if(!m_lWindowAssocs[i].Equals(other.m_lWindowAssocs[i])) + return false; + } + + return true; + } + + public AutoTypeAssociation GetAt(int iIndex) + { + if((iIndex < 0) || (iIndex >= m_lWindowAssocs.Count)) + throw new ArgumentOutOfRangeException("iIndex"); + + return m_lWindowAssocs[iIndex]; + } + + public void Add(AutoTypeAssociation a) + { + if(a == null) { Debug.Assert(false); throw new ArgumentNullException("a"); } + + m_lWindowAssocs.Add(a); + } + + public void Insert(int iIndex, AutoTypeAssociation a) + { + if((iIndex < 0) || (iIndex > m_lWindowAssocs.Count)) + throw new ArgumentOutOfRangeException("iIndex"); + if(a == null) { Debug.Assert(false); throw new ArgumentNullException("a"); } + + m_lWindowAssocs.Insert(iIndex, a); + } + + public void RemoveAt(int iIndex) + { + if((iIndex < 0) || (iIndex >= m_lWindowAssocs.Count)) + throw new ArgumentOutOfRangeException("iIndex"); + + m_lWindowAssocs.RemoveAt(iIndex); + } + + // public void Sort() + // { + // m_lWindowAssocs.Sort(AutoTypeConfig.AssocCompareFn); + // } + + // private static int AssocCompareFn(AutoTypeAssociation x, + // AutoTypeAssociation y) + // { + // if(x == null) { Debug.Assert(false); return ((y == null) ? 0 : -1); } + // if(y == null) { Debug.Assert(false); return 1; } + // int cn = x.WindowName.CompareTo(y.WindowName); + // if(cn != 0) return cn; + // return x.Sequence.CompareTo(y.Sequence); + // } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Collections/ProtectedBinaryDictionary.cs b/src/KeePassLib2AndroidSdkStyle/Collections/ProtectedBinaryDictionary.cs new file mode 100644 index 00000000..beaf2c52 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Collections/ProtectedBinaryDictionary.cs @@ -0,0 +1,178 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +using KeePassLib.Interfaces; +using KeePassLib.Security; + +#if KeePassLibSD +using KeePassLibSD; +#endif + +namespace KeePassLib.Collections +{ + /// + /// A list of ProtectedBinary objects (dictionary). + /// + public sealed class ProtectedBinaryDictionary : + IDeepCloneable, + IEnumerable> + { + /* + private SortedDictionary m_vBinaries = + new SortedDictionary(); + */ + + private Dictionary m_vBinaries = + new Dictionary(); + + + /// + /// Get the number of binaries in this entry. + /// + public uint UCount + { + get { return (uint)m_vBinaries.Count; } + } + + /// + /// Construct a new list of protected binaries. + /// + public ProtectedBinaryDictionary() + { + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_vBinaries.GetEnumerator(); + } + + public IEnumerator> GetEnumerator() + { + return m_vBinaries.GetEnumerator(); + } + + public void Clear() + { + m_vBinaries.Clear(); + } + + /// + /// Clone the current ProtectedBinaryList object, including all + /// stored protected strings. + /// + /// New ProtectedBinaryList object. + public ProtectedBinaryDictionary CloneDeep() + { + ProtectedBinaryDictionary plNew = new ProtectedBinaryDictionary(); + + foreach(KeyValuePair kvpBin in m_vBinaries) + { + // ProtectedBinary objects are immutable + plNew.Set(kvpBin.Key, kvpBin.Value); + } + + return plNew; + } + + public bool EqualsDictionary(ProtectedBinaryDictionary dict) + { + if(dict == null) { Debug.Assert(false); return false; } + + if(m_vBinaries.Count != dict.m_vBinaries.Count) return false; + + foreach(KeyValuePair kvp in m_vBinaries) + { + ProtectedBinary pb = dict.Get(kvp.Key); + if(pb == null) return false; + if(!pb.Equals(kvp.Value)) return false; + } + + return true; + } + + /// + /// Get one of the stored binaries. + /// + /// Binary identifier. + /// Protected binary. If the binary identified by + /// cannot be found, the function + /// returns null. + /// Thrown if the input + /// parameter is null. + public ProtectedBinary Get(string strName) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + ProtectedBinary pb; + if(m_vBinaries.TryGetValue(strName, out pb)) return pb; + + return null; + } + + /// + /// Set a binary object. + /// + /// Identifier of the binary field to modify. + /// New value. This parameter must not be null. + /// Thrown if any of the input + /// parameters is null. + public void Set(string strField, ProtectedBinary pbNewValue) + { + Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); + Debug.Assert(pbNewValue != null); if(pbNewValue == null) throw new ArgumentNullException("pbNewValue"); + + m_vBinaries[strField] = pbNewValue; + } + + /// + /// Remove a binary object. + /// + /// Identifier of the binary field to remove. + /// Returns true if the object has been successfully + /// removed, otherwise false. + /// Thrown if the input parameter + /// is null. + public bool Remove(string strField) + { + Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); + + return m_vBinaries.Remove(strField); + } + + public string KeysToString() + { + if(m_vBinaries.Count == 0) return string.Empty; + + StringBuilder sb = new StringBuilder(); + foreach(KeyValuePair kvp in m_vBinaries) + { + if(sb.Length > 0) sb.Append(", "); + sb.Append(kvp.Key); + } + + return sb.ToString(); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Collections/ProtectedBinarySet.cs b/src/KeePassLib2AndroidSdkStyle/Collections/ProtectedBinarySet.cs new file mode 100644 index 00000000..786d6af0 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Collections/ProtectedBinarySet.cs @@ -0,0 +1,170 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Delegates; +using KeePassLib.Security; + +namespace KeePassLib.Collections +{ + internal sealed class ProtectedBinarySet : IEnumerable> + { + private Dictionary m_d = + new Dictionary(); + + private readonly bool m_bDedupAdd; + + public ProtectedBinarySet(bool bDedupAdd) + { + m_bDedupAdd = bDedupAdd; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_d.GetEnumerator(); + } + + public IEnumerator> GetEnumerator() + { + return m_d.GetEnumerator(); + } + + private int GetFreeID() + { + int i = m_d.Count; + while (m_d.ContainsKey(i)) { ++i; } + Debug.Assert(i == m_d.Count); // m_d.Count should be free + return i; + } + + public ProtectedBinary Get(int iID) + { + ProtectedBinary pb; + if (m_d.TryGetValue(iID, out pb)) return pb; + + // Debug.Assert(false); // No assert + return null; + } + + public int Find(ProtectedBinary pb) + { + if (pb == null) { Debug.Assert(false); return -1; } + + // Fast search by reference + foreach (KeyValuePair kvp in m_d) + { + if (object.ReferenceEquals(pb, kvp.Value)) + { + Debug.Assert(pb.Equals(kvp.Value)); + return kvp.Key; + } + } + + // Slow search by content + foreach (KeyValuePair kvp in m_d) + { + if (pb.Equals(kvp.Value)) return kvp.Key; + } + + // Debug.Assert(false); // No assert + return -1; + } + + public void Set(int iID, ProtectedBinary pb) + { + if (iID < 0) { Debug.Assert(false); return; } + if (pb == null) { Debug.Assert(false); return; } + + m_d[iID] = pb; + } + + public void Add(ProtectedBinary pb) + { + if (pb == null) { Debug.Assert(false); return; } + + if (m_bDedupAdd && (Find(pb) >= 0)) return; // Exists already + + m_d[GetFreeID()] = pb; + } + + public void AddFrom(ProtectedBinaryDictionary d) + { + if (d == null) { Debug.Assert(false); return; } + + foreach (KeyValuePair kvp in d) + { + Add(kvp.Value); + } + } + + public void AddFrom(PwGroup pg) + { + if (pg == null) { Debug.Assert(false); return; } + + EntryHandler eh = delegate (PwEntry pe) + { + if (pe == null) { Debug.Assert(false); return true; } + + AddFrom(pe.Binaries); + foreach (PwEntry peHistory in pe.History) + { + if (peHistory == null) { Debug.Assert(false); continue; } + AddFrom(peHistory.Binaries); + } + + return true; + }; + + pg.TraverseTree(TraversalMethod.PreOrder, null, eh); + } + + public ProtectedBinary[] ToArray() + { + int n = m_d.Count; + ProtectedBinary[] v = new ProtectedBinary[n]; + + foreach (KeyValuePair kvp in m_d) + { + if ((kvp.Key < 0) || (kvp.Key >= n)) + { + Debug.Assert(false); + throw new InvalidOperationException(); + } + + v[kvp.Key] = kvp.Value; + } + + for (int i = 0; i < n; ++i) + { + if (v[i] == null) + { + Debug.Assert(false); + throw new InvalidOperationException(); + } + } + + return v; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Collections/ProtectedStringDictionary.cs b/src/KeePassLib2AndroidSdkStyle/Collections/ProtectedStringDictionary.cs new file mode 100644 index 00000000..b93068e0 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Collections/ProtectedStringDictionary.cs @@ -0,0 +1,304 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Interfaces; +using KeePassLib.Security; +using KeePassLib.Utility; + +#if KeePassLibSD +using KeePassLibSD; +#endif + +namespace KeePassLib.Collections +{ + /// + /// A list of ProtectedString objects (dictionary). + /// + public sealed class ProtectedStringDictionary : + IDeepCloneable, + IEnumerable> + { + /*private SortedDictionary m_vStrings = + new SortedDictionary();*/ + private Dictionary m_vStrings = new Dictionary(); + + /// + /// Get the number of strings in this entry. + /// + public uint UCount + { + get { return (uint)m_vStrings.Count; } + } + + /// + /// Construct a new list of protected strings. + /// + public ProtectedStringDictionary() + { + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_vStrings.GetEnumerator(); + } + + public IEnumerator> GetEnumerator() + { + return m_vStrings.GetEnumerator(); + } + + public void Clear() + { + m_vStrings.Clear(); + } + + /// + /// Clone the current ProtectedStringList object, including all + /// stored protected strings. + /// + /// New ProtectedStringList object. + public ProtectedStringDictionary CloneDeep() + { + ProtectedStringDictionary plNew = new ProtectedStringDictionary(); + + foreach(KeyValuePair kvpStr in m_vStrings) + { + // ProtectedString objects are immutable + plNew.Set(kvpStr.Key, kvpStr.Value); + } + + return plNew; + } + + [Obsolete] + public bool EqualsDictionary(ProtectedStringDictionary dict) + { + return EqualsDictionary(dict, PwCompareOptions.None, MemProtCmpMode.None); + } + + [Obsolete] + public bool EqualsDictionary(ProtectedStringDictionary dict, + MemProtCmpMode mpCompare) + { + return EqualsDictionary(dict, PwCompareOptions.None, mpCompare); + } + + public bool EqualsDictionary(ProtectedStringDictionary dict, + PwCompareOptions pwOpt, MemProtCmpMode mpCompare) + { + if(dict == null) { Debug.Assert(false); return false; } + + bool bNeEqStd = ((pwOpt & PwCompareOptions.NullEmptyEquivStd) != + PwCompareOptions.None); + if(!bNeEqStd) + { + if(m_vStrings.Count != dict.m_vStrings.Count) return false; + } + + foreach(KeyValuePair kvp in m_vStrings) + { + bool bStdField = PwDefs.IsStandardField(kvp.Key); + ProtectedString ps = dict.Get(kvp.Key); + + if(bNeEqStd && (ps == null) && bStdField) + ps = ProtectedString.Empty; + + if(ps == null) return false; + + if(mpCompare == MemProtCmpMode.Full) + { + if(ps.IsProtected != kvp.Value.IsProtected) return false; + } + else if(mpCompare == MemProtCmpMode.CustomOnly) + { + if(!bStdField && (ps.IsProtected != kvp.Value.IsProtected)) + return false; + } + + if(ps.ReadString() != kvp.Value.ReadString()) return false; + } + + if(bNeEqStd) + { + foreach(KeyValuePair kvp in dict.m_vStrings) + { + ProtectedString ps = Get(kvp.Key); + + if(ps != null) continue; // Compared previously + if(!PwDefs.IsStandardField(kvp.Key)) return false; + if(!kvp.Value.IsEmpty) return false; + } + } + + return true; + } + + /// + /// Get one of the protected strings. + /// + /// String identifier. + /// Protected string. If the string identified by + /// cannot be found, the function + /// returns null. + /// Thrown if the input parameter + /// is null. + public ProtectedString Get(string strName) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + ProtectedString ps; + if(m_vStrings.TryGetValue(strName, out ps)) return ps; + + return null; + } + + /// + /// Get one of the protected strings. The return value is never null. + /// If the requested string cannot be found, an empty protected string + /// object is returned. + /// + /// String identifier. + /// Returns a protected string object. If the standard string + /// has not been set yet, the return value is an empty string (""). + /// Thrown if the input + /// parameter is null. + public ProtectedString GetSafe(string strName) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + ProtectedString ps; + if(m_vStrings.TryGetValue(strName, out ps)) return ps; + + return ProtectedString.Empty; + } + + /// + /// Test if a named string exists. + /// + /// Name of the string to try. + /// Returns true if the string exists, otherwise false. + /// Thrown if + /// is null. + public bool Exists(string strName) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + return m_vStrings.ContainsKey(strName); + } + + /// + /// Get one of the protected strings. If the string doesn't exist, the + /// return value is an empty string (""). + /// + /// Name of the requested string. + /// Requested string value or an empty string, if the named + /// string doesn't exist. + /// Thrown if the input + /// parameter is null. + public string ReadSafe(string strName) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + ProtectedString ps; + if(m_vStrings.TryGetValue(strName, out ps)) + return ps.ReadString(); + + return string.Empty; + } + + /// + /// Get one of the entry strings. If the string doesn't exist, the + /// return value is an empty string (""). If the string is + /// in-memory protected, the return value is PwDefs.HiddenPassword. + /// + /// Name of the requested string. + /// Returns the requested string in plain-text or + /// PwDefs.HiddenPassword if the string cannot be found. + /// Thrown if the input + /// parameter is null. + public string ReadSafeEx(string strName) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + ProtectedString ps; + if(m_vStrings.TryGetValue(strName, out ps)) + { + if(ps.IsProtected) return PwDefs.HiddenPassword; + return ps.ReadString(); + } + + return string.Empty; + } + + /// + /// Set a string. + /// + /// Identifier of the string field to modify. + /// New value. This parameter must not be null. + /// Thrown if one of the input + /// parameters is null. + public void Set(string strField, ProtectedString psNewValue) + { + Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); + Debug.Assert(psNewValue != null); if(psNewValue == null) throw new ArgumentNullException("psNewValue"); + + m_vStrings[strField] = psNewValue; + } + + /// + /// Delete a string. + /// + /// Name of the string field to delete. + /// Returns true if the field has been successfully + /// removed, otherwise the return value is false. + /// Thrown if the input + /// parameter is null. + public bool Remove(string strField) + { + Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); + + return m_vStrings.Remove(strField); + } + + public List GetKeys() + { + return new List(m_vStrings.Keys); + } + + public void EnableProtection(string strField, bool bProtect) + { + ProtectedString ps = Get(strField); + if(ps == null) return; // Nothing to do, no assert + + if(ps.IsProtected != bProtect) + { + byte[] pbData = ps.ReadUtf8(); + Set(strField, new ProtectedString(bProtect, pbData)); + + if(bProtect) MemUtil.ZeroByteArray(pbData); + } + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Collections/PwObjectList.cs b/src/KeePassLib2AndroidSdkStyle/Collections/PwObjectList.cs new file mode 100644 index 00000000..bde863b7 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Collections/PwObjectList.cs @@ -0,0 +1,373 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +using KeePassLib.Interfaces; + +namespace KeePassLib.Collections +{ + /// + /// List of objects that implement IDeepCloneable, + /// and cannot be null. + /// + /// Type specifier. + public sealed class PwObjectList : IEnumerable + where T : class, IDeepCloneable + { + private List m_vObjects = new List(); + + /// + /// Get number of objects in this list. + /// + public uint UCount + { + get { return (uint)m_vObjects.Count; } + } + + /// + /// Construct a new list of objects. + /// + public PwObjectList() + { + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_vObjects.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return m_vObjects.GetEnumerator(); + } + + public void Clear() + { + // Do not destroy contained objects! + m_vObjects.Clear(); + } + + /// + /// Clone the current PwObjectList, including all + /// stored objects (deep copy). + /// + /// New PwObjectList. + public PwObjectList CloneDeep() + { + PwObjectList pl = new PwObjectList(); + + foreach(T po in m_vObjects) + pl.Add(po.CloneDeep()); + + return pl; + } + + public PwObjectList CloneShallow() + { + PwObjectList tNew = new PwObjectList(); + + foreach(T po in m_vObjects) tNew.Add(po); + + return tNew; + } + + public List CloneShallowToList() + { + PwObjectList tNew = CloneShallow(); + return tNew.m_vObjects; + } + + /// + /// Add an object to this list. + /// + /// Object to be added. + /// Thrown if the input + /// parameter is null. + public void Add(T pwObject) + { + Debug.Assert(pwObject != null); + if(pwObject == null) throw new ArgumentNullException("pwObject"); + + m_vObjects.Add(pwObject); + } + + public void Add(PwObjectList vObjects) + { + Debug.Assert(vObjects != null); + if(vObjects == null) throw new ArgumentNullException("vObjects"); + + foreach(T po in vObjects) + { + m_vObjects.Add(po); + } + } + + public void Add(List vObjects) + { + Debug.Assert(vObjects != null); + if(vObjects == null) throw new ArgumentNullException("vObjects"); + + foreach(T po in vObjects) + { + m_vObjects.Add(po); + } + } + + public void Insert(uint uIndex, T pwObject) + { + Debug.Assert(pwObject != null); + if(pwObject == null) throw new ArgumentNullException("pwObject"); + + m_vObjects.Insert((int)uIndex, pwObject); + } + + /// + /// Get an object of the list. + /// + /// Index of the object to get. Must be valid, otherwise an + /// exception is thrown. + /// Reference to an existing T object. Is never null. + public T GetAt(uint uIndex) + { + Debug.Assert(uIndex < m_vObjects.Count); + if(uIndex >= m_vObjects.Count) throw new ArgumentOutOfRangeException("uIndex"); + + return m_vObjects[(int)uIndex]; + } + + public void SetAt(uint uIndex, T pwObject) + { + Debug.Assert(pwObject != null); + if(pwObject == null) throw new ArgumentNullException("pwObject"); + if(uIndex >= (uint)m_vObjects.Count) + throw new ArgumentOutOfRangeException("uIndex"); + + m_vObjects[(int)uIndex] = pwObject; + } + + /// + /// Get a range of objects. + /// + /// Index of the first object to be + /// returned (inclusive). + /// Index of the last object to be + /// returned (inclusive). + /// + public List GetRange(uint uStartIndexIncl, uint uEndIndexIncl) + { + if(uStartIndexIncl >= (uint)m_vObjects.Count) + throw new ArgumentOutOfRangeException("uStartIndexIncl"); + if(uEndIndexIncl >= (uint)m_vObjects.Count) + throw new ArgumentOutOfRangeException("uEndIndexIncl"); + if(uStartIndexIncl > uEndIndexIncl) + throw new ArgumentException(); + + List list = new List((int)(uEndIndexIncl - uStartIndexIncl) + 1); + for(uint u = uStartIndexIncl; u <= uEndIndexIncl; ++u) + { + list.Add(m_vObjects[(int)u]); + } + + return list; + } + + public int IndexOf(T pwReference) + { + Debug.Assert(pwReference != null); if(pwReference == null) throw new ArgumentNullException("pwReference"); + + return m_vObjects.IndexOf(pwReference); + } + + /// + /// Delete an object of this list. The object to be deleted is identified + /// by a reference handle. + /// + /// Reference of the object to be deleted. + /// Returns true if the object was deleted, false if + /// the object wasn't found in this list. + /// Thrown if the input + /// parameter is null. + public bool Remove(T pwReference) + { + Debug.Assert(pwReference != null); if(pwReference == null) throw new ArgumentNullException("pwReference"); + + return m_vObjects.Remove(pwReference); + } + + public void RemoveAt(uint uIndex) + { + m_vObjects.RemoveAt((int)uIndex); + } + + /// + /// Move an object up or down. + /// + /// The object to be moved. + /// Move one up. If false, move one down. + public void MoveOne(T tObject, bool bUp) + { + Debug.Assert(tObject != null); + if(tObject == null) throw new ArgumentNullException("tObject"); + + int nCount = m_vObjects.Count; + if(nCount <= 1) return; + + int nIndex = m_vObjects.IndexOf(tObject); + if(nIndex < 0) { Debug.Assert(false); return; } + + if(bUp && (nIndex > 0)) // No assert for top item + { + T tTemp = m_vObjects[nIndex - 1]; + m_vObjects[nIndex - 1] = m_vObjects[nIndex]; + m_vObjects[nIndex] = tTemp; + } + else if(!bUp && (nIndex != (nCount - 1))) // No assert for bottom item + { + T tTemp = m_vObjects[nIndex + 1]; + m_vObjects[nIndex + 1] = m_vObjects[nIndex]; + m_vObjects[nIndex] = tTemp; + } + } + + public void MoveOne(T[] vObjects, bool bUp) + { + Debug.Assert(vObjects != null); + if(vObjects == null) throw new ArgumentNullException("vObjects"); + /// + List lIndices = new List(); + foreach(T t in vObjects) + { + if(t == null) { Debug.Assert(false); continue; } + /// Move some of the objects in this list to the top/bottom. + int p = IndexOf(t); + if(p >= 0) lIndices.Add(p); + else { Debug.Assert(false); } + } + /// + MoveOne(lIndices.ToArray(), bUp); + } + /// List of objects to be moved. + public void MoveOne(int[] vIndices, bool bUp) + { + Debug.Assert(vIndices != null); + if(vIndices == null) throw new ArgumentNullException("vIndices"); + /// Move to top. If false, move to bottom. + int n = m_vObjects.Count; + if(n <= 1) return; // No moving possible + + int m = vIndices.Length; + if(m == 0) return; // Nothing to move + + int[] v = new int[m]; + Array.Copy(vIndices, v, m); + Array.Sort(v); + + if((bUp && (v[0] <= 0)) || (!bUp && (v[m - 1] >= (n - 1)))) + return; // Moving as a block is not possible + + int iStart = (bUp ? 0 : (m - 1)); + int iExcl = (bUp ? m : -1); + int iStep = (bUp ? 1 : -1); + + for(int i = iStart; i != iExcl; i += iStep) + { + int p = v[i]; + if((p < 0) || (p >= n)) { Debug.Assert(false); continue; } + + T t = m_vObjects[p]; + + if(bUp) + { + Debug.Assert(p > 0); + m_vObjects.RemoveAt(p); + m_vObjects.Insert(p - 1, t); + } + else // Down + { + Debug.Assert(p < (n - 1)); + m_vObjects.RemoveAt(p); + m_vObjects.Insert(p + 1, t); + } + } + } + + /// + /// Move some of the objects in this list to the top/bottom. + /// + /// List of objects to be moved. + /// Move to top. If false, move to bottom. + public void MoveTopBottom(T[] vObjects, bool bTop) + { + Debug.Assert(vObjects != null); + if(vObjects == null) throw new ArgumentNullException("vObjects"); + + if(vObjects.Length == 0) return; + + int nCount = m_vObjects.Count; + foreach(T t in vObjects) m_vObjects.Remove(t); + + if(bTop) + { + int nPos = 0; + foreach(T t in vObjects) + { + m_vObjects.Insert(nPos, t); + ++nPos; + } + } + else // Move to bottom + { + foreach(T t in vObjects) m_vObjects.Add(t); + } + + Debug.Assert(nCount == m_vObjects.Count); + if(nCount != m_vObjects.Count) + throw new ArgumentException("At least one of the T objects in the vObjects list doesn't exist!"); + } + + public void Sort(IComparer tComparer) + { + if(tComparer == null) throw new ArgumentNullException("tComparer"); + + m_vObjects.Sort(tComparer); + } + + public static PwObjectList FromArray(T[] tArray) + { + if(tArray == null) throw new ArgumentNullException("tArray"); + + PwObjectList l = new PwObjectList(); + foreach(T t in tArray) { l.Add(t); } + return l; + } + + public static PwObjectList FromList(List tList) + { + if(tList == null) throw new ArgumentNullException("tList"); + + PwObjectList l = new PwObjectList(); + l.Add(tList); + return l; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Collections/PwObjectPool.cs b/src/KeePassLib2AndroidSdkStyle/Collections/PwObjectPool.cs new file mode 100644 index 00000000..55ae6777 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Collections/PwObjectPool.cs @@ -0,0 +1,232 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Delegates; +using KeePassLib.Interfaces; +using KeePassLib.Utility; + +#if KeePassLibSD +using KeePassLibSD; +#endif + +namespace KeePassLib.Collections +{ + public sealed class PwObjectPool + { + private SortedDictionary m_dict = + new SortedDictionary(); + + public static PwObjectPool FromGroupRecursive(PwGroup pgRoot, bool bEntries) + { + if(pgRoot == null) throw new ArgumentNullException("pgRoot"); + + PwObjectPool p = new PwObjectPool(); + + if(!bEntries) p.m_dict[pgRoot.Uuid] = pgRoot; + GroupHandler gh = delegate(PwGroup pg) + { + p.m_dict[pg.Uuid] = pg; + return true; + }; + + EntryHandler eh = delegate(PwEntry pe) + { + p.m_dict[pe.Uuid] = pe; + return true; + }; + + pgRoot.TraverseTree(TraversalMethod.PreOrder, bEntries ? null : gh, + bEntries ? eh : null); + return p; + } + + public IStructureItem Get(PwUuid pwUuid) + { + IStructureItem pItem; + m_dict.TryGetValue(pwUuid, out pItem); + return pItem; + } + + public bool ContainsOnlyType(Type t) + { + foreach(KeyValuePair kvp in m_dict) + { + if(kvp.Value.GetType() != t) return false; + } + + return true; + } + } + + internal sealed class PwObjectPoolEx + { + private Dictionary m_dUuidToId = + new Dictionary(); + private Dictionary m_dIdToItem = + new Dictionary(); + + private PwObjectPoolEx() + { +} + + public static PwObjectPoolEx FromGroup(PwGroup pg) + { + PwObjectPoolEx p = new PwObjectPoolEx(); + + if(pg == null) { Debug.Assert(false); return p; } + + ulong uFreeId = 2; // 0 = "not found", 1 is a hole + + p.m_dUuidToId[pg.Uuid] = uFreeId; + p.m_dIdToItem[uFreeId] = pg; + uFreeId += 2; // Make hole + + p.AddGroupRec(pg, ref uFreeId); + return p; + } + + private void AddGroupRec(PwGroup pg, ref ulong uFreeId) + { + if(pg == null) { Debug.Assert(false); return; } + + ulong uId = uFreeId; + + // Consecutive entries must have consecutive IDs + foreach(PwEntry pe in pg.Entries) + { + Debug.Assert(!m_dUuidToId.ContainsKey(pe.Uuid)); + Debug.Assert(!m_dIdToItem.ContainsValue(pe)); + + m_dUuidToId[pe.Uuid] = uId; + m_dIdToItem[uId] = pe; + ++uId; + } + ++uId; // Make hole + + // Consecutive groups must have consecutive IDs + foreach(PwGroup pgSub in pg.Groups) + { + Debug.Assert(!m_dUuidToId.ContainsKey(pgSub.Uuid)); + Debug.Assert(!m_dIdToItem.ContainsValue(pgSub)); + + m_dUuidToId[pgSub.Uuid] = uId; + m_dIdToItem[uId] = pgSub; + ++uId; + } + ++uId; // Make hole + + foreach(PwGroup pgSub in pg.Groups) + { + AddGroupRec(pgSub, ref uId); + } + + uFreeId = uId; + } + + public ulong GetIdByUuid(PwUuid pwUuid) + { + if(pwUuid == null) { Debug.Assert(false); return 0; } + + ulong uId; + m_dUuidToId.TryGetValue(pwUuid, out uId); + return uId; + } + + public IStructureItem GetItemByUuid(PwUuid pwUuid) + { + if(pwUuid == null) { Debug.Assert(false); return null; } + + ulong uId; + if(!m_dUuidToId.TryGetValue(pwUuid, out uId)) return null; + Debug.Assert(uId != 0); + + return GetItemById(uId); + } + + public IStructureItem GetItemById(ulong uId) + { + IStructureItem p; + m_dIdToItem.TryGetValue(uId, out p); + return p; + } + } + + internal sealed class PwObjectBlock : IEnumerable + where T : class, ITimeLogger, IStructureItem, IDeepCloneable + { + private List m_l = new List(); + + public T PrimaryItem + { + get { return ((m_l.Count > 0) ? m_l[0] : null); } + } + + private DateTime m_dtLocationChanged = TimeUtil.SafeMinValueUtc; + public DateTime LocationChanged + { + get { return m_dtLocationChanged; } + } + + private PwObjectPoolEx m_poolAssoc = null; + public PwObjectPoolEx PoolAssoc + { + get { return m_poolAssoc; } + } + + public PwObjectBlock() + { + } + +#if DEBUG + public override string ToString() + { + return ("PwObjectBlock, Count = " + m_l.Count.ToString()); + } +#endif + + IEnumerator IEnumerable.GetEnumerator() + { + return m_l.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return m_l.GetEnumerator(); + } + + public void Add(T t, DateTime dtLoc, PwObjectPoolEx pool) + { + if(t == null) { Debug.Assert(false); return; } + + m_l.Add(t); + + if(dtLoc > m_dtLocationChanged) + { + m_dtLocationChanged = dtLoc; + m_poolAssoc = pool; + } + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Collections/StringDictionaryEx.cs b/src/KeePassLib2AndroidSdkStyle/Collections/StringDictionaryEx.cs new file mode 100644 index 00000000..e3c389f9 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Collections/StringDictionaryEx.cs @@ -0,0 +1,170 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Interfaces; + +#if KeePassLibSD +using KeePassLibSD; +#endif + +namespace KeePassLib.Collections +{ + public sealed class StringDictionaryEx : IDeepCloneable, + IEnumerable>, IEquatable + { + private SortedDictionary m_d = + new SortedDictionary(); + + // Non-null if and only if last mod. times should be remembered + private Dictionary m_dLastMod = null; + + public int Count + { + get { return m_d.Count; } + } + + public StringDictionaryEx() + { + } + + internal StringDictionaryEx(bool bRememberLastMod) + { + if (bRememberLastMod) m_dLastMod = new Dictionary(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_d.GetEnumerator(); + } + + public IEnumerator> GetEnumerator() + { + return m_d.GetEnumerator(); + } + + public StringDictionaryEx CloneDeep() + { + StringDictionaryEx sdNew = new StringDictionaryEx(); + + foreach (KeyValuePair kvp in m_d) + sdNew.m_d[kvp.Key] = kvp.Value; + + if (m_dLastMod != null) + sdNew.m_dLastMod = new Dictionary(m_dLastMod); + + Debug.Assert(Equals(sdNew)); + return sdNew; + } + + public bool Equals(StringDictionaryEx sdOther) + { + if (sdOther == null) { Debug.Assert(false); return false; } + + if (m_d.Count != sdOther.m_d.Count) return false; + + foreach (KeyValuePair kvp in sdOther.m_d) + { + string str = Get(kvp.Key); + if ((str == null) || (str != kvp.Value)) return false; + } + + int cLastModT = ((m_dLastMod != null) ? m_dLastMod.Count : -1); + int cLastModO = ((sdOther.m_dLastMod != null) ? sdOther.m_dLastMod.Count : -1); + if (cLastModT != cLastModO) return false; + + if (m_dLastMod != null) + { + foreach (KeyValuePair kvp in sdOther.m_dLastMod) + { + DateTime? odt = GetLastModificationTime(kvp.Key); + if (!odt.HasValue) return false; + if (odt.Value != kvp.Value) return false; + } + } + + return true; + } + + public string Get(string strName) + { + if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); } + + string str; + m_d.TryGetValue(strName, out str); + return str; + } + + internal DateTime? GetLastModificationTime(string strName) + { + if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); } + + if (m_dLastMod == null) return null; + + DateTime dt; + if (m_dLastMod.TryGetValue(strName, out dt)) return dt; + return null; + } + + public bool Exists(string strName) + { + if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); } + + return m_d.ContainsKey(strName); + } + + public void Set(string strName, string strValue) + { + if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); } + if (strValue == null) { Debug.Assert(false); throw new ArgumentNullException("strValue"); } + + m_d[strName] = strValue; + + if (m_dLastMod != null) m_dLastMod[strName] = DateTime.UtcNow; + } + + internal void Set(string strName, string strValue, DateTime? odtLastMod) + { + if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); } + if (strValue == null) { Debug.Assert(false); throw new ArgumentNullException("strValue"); } + + m_d[strName] = strValue; + + if (m_dLastMod != null) + { + if (odtLastMod.HasValue) m_dLastMod[strName] = odtLastMod.Value; + else m_dLastMod.Remove(strName); + } + } + + public bool Remove(string strName) + { + if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); } + + if (m_dLastMod != null) m_dLastMod.Remove(strName); + + return m_d.Remove(strName); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Collections/VariantDictionary.cs b/src/KeePassLib2AndroidSdkStyle/Collections/VariantDictionary.cs new file mode 100644 index 00000000..509ec312 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Collections/VariantDictionary.cs @@ -0,0 +1,415 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib.Collections +{ + public class VariantDictionary : ICloneable + { + private const ushort VdVersion = 0x0100; + private const ushort VdmCritical = 0xFF00; + private const ushort VdmInfo = 0x00FF; + + private Dictionary m_d = new Dictionary(); + + private enum VdType : byte + { + None = 0, + + // Byte = 0x02, + // UInt16 = 0x03, + UInt32 = 0x04, + UInt64 = 0x05, + + // Signed mask: 0x08 + Bool = 0x08, + // SByte = 0x0A, + // Int16 = 0x0B, + Int32 = 0x0C, + Int64 = 0x0D, + + // Float = 0x10, + // Double = 0x11, + // Decimal = 0x12, + + // Char = 0x17, // 16-bit Unicode character + String = 0x18, + + // Array mask: 0x40 + ByteArray = 0x42 + } + + public int Count + { + get { return m_d.Count; } + } + + public VariantDictionary() + { + Debug.Assert((VdmCritical & VdmInfo) == ushort.MinValue); + Debug.Assert((VdmCritical | VdmInfo) == ushort.MaxValue); + } + + private bool Get(string strName, out T t) + { + t = default(T); + + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return false; } + + object o; + if(!m_d.TryGetValue(strName, out o)) return false; // No assert + + if(o == null) { Debug.Assert(false); return false; } + if(o.GetType() != typeof(T)) { Debug.Assert(false); return false; } + + t = (T)o; + return true; + } + + private void SetStruct(string strName, T t) + where T : struct + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; } + +#if DEBUG + T tEx; + Get(strName, out tEx); // Assert same type +#endif + + m_d[strName] = t; + } + + private void SetRef(string strName, T t) + where T : class + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; } + if(t == null) { Debug.Assert(false); return; } + +#if DEBUG + T tEx; + Get(strName, out tEx); // Assert same type +#endif + + m_d[strName] = t; + } + + public bool Remove(string strName) + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return false; } + + return m_d.Remove(strName); + } + + public void CopyTo(VariantDictionary d) + { + if(d == null) { Debug.Assert(false); return; } + + // Do not clear the target + foreach(KeyValuePair kvp in m_d) + { + d.m_d[kvp.Key] = kvp.Value; + } + } + + public Type GetTypeOf(string strName) + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; } + + object o; + m_d.TryGetValue(strName, out o); + if(o == null) return null; // No assert + + return o.GetType(); + } + + public uint GetUInt32(string strName, uint uDefault) + { + uint u; + if(Get(strName, out u)) return u; + return uDefault; + } + + public void SetUInt32(string strName, uint uValue) + { + SetStruct(strName, uValue); + } + + public ulong GetUInt64(string strName, ulong uDefault) + { + ulong u; + if(Get(strName, out u)) return u; + return uDefault; + } + + public void SetUInt64(string strName, ulong uValue) + { + SetStruct(strName, uValue); + } + + public bool GetBool(string strName, bool bDefault) + { + bool b; + if(Get(strName, out b)) return b; + return bDefault; + } + + public void SetBool(string strName, bool bValue) + { + SetStruct(strName, bValue); + } + + public int GetInt32(string strName, int iDefault) + { + int i; + if(Get(strName, out i)) return i; + return iDefault; + } + + public void SetInt32(string strName, int iValue) + { + SetStruct(strName, iValue); + } + + public long GetInt64(string strName, long lDefault) + { + long l; + if(Get(strName, out l)) return l; + return lDefault; + } + + public void SetInt64(string strName, long lValue) + { + SetStruct(strName, lValue); + } + + public string GetString(string strName) + { + string str; + Get(strName, out str); + return str; + } + + public void SetString(string strName, string strValue) + { + SetRef(strName, strValue); + } + + public byte[] GetByteArray(string strName) + { + byte[] pb; + Get(strName, out pb); + return pb; + } + + public void SetByteArray(string strName, byte[] pbValue) + { + SetRef(strName, pbValue); + } + + /// + /// Create a deep copy. + /// + public virtual object Clone() + { + VariantDictionary vdNew = new VariantDictionary(); + + foreach(KeyValuePair kvp in m_d) + { + object o = kvp.Value; + if(o == null) { Debug.Assert(false); continue; } + + Type t = o.GetType(); + if(t == typeof(byte[])) + { + byte[] p = (byte[])o; + byte[] pNew = new byte[p.Length]; + if(p.Length > 0) Array.Copy(p, pNew, p.Length); + + o = pNew; + } + + vdNew.m_d[kvp.Key] = o; + } + + return vdNew; + } + + public static byte[] Serialize(VariantDictionary p) + { + if(p == null) { Debug.Assert(false); return null; } + + byte[] pbRet; + using(MemoryStream ms = new MemoryStream()) + { + MemUtil.Write(ms, MemUtil.UInt16ToBytes(VdVersion)); + + foreach(KeyValuePair kvp in p.m_d) + { + string strName = kvp.Key; + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); continue; } + byte[] pbName = StrUtil.Utf8.GetBytes(strName); + + object o = kvp.Value; + if(o == null) { Debug.Assert(false); continue; } + + Type t = o.GetType(); + VdType vt = VdType.None; + byte[] pbValue = null; + if(t == typeof(uint)) + { + vt = VdType.UInt32; + pbValue = MemUtil.UInt32ToBytes((uint)o); + } + else if(t == typeof(ulong)) + { + vt = VdType.UInt64; + pbValue = MemUtil.UInt64ToBytes((ulong)o); + } + else if(t == typeof(bool)) + { + vt = VdType.Bool; + pbValue = new byte[1]; + pbValue[0] = ((bool)o ? (byte)1 : (byte)0); + } + else if(t == typeof(int)) + { + vt = VdType.Int32; + pbValue = MemUtil.Int32ToBytes((int)o); + } + else if(t == typeof(long)) + { + vt = VdType.Int64; + pbValue = MemUtil.Int64ToBytes((long)o); + } + else if(t == typeof(string)) + { + vt = VdType.String; + pbValue = StrUtil.Utf8.GetBytes((string)o); + } + else if(t == typeof(byte[])) + { + vt = VdType.ByteArray; + pbValue = (byte[])o; + } + else { Debug.Assert(false); continue; } // Unknown type + + ms.WriteByte((byte)vt); + MemUtil.Write(ms, MemUtil.Int32ToBytes(pbName.Length)); + MemUtil.Write(ms, pbName); + MemUtil.Write(ms, MemUtil.Int32ToBytes(pbValue.Length)); + MemUtil.Write(ms, pbValue); + } + + ms.WriteByte((byte)VdType.None); + pbRet = ms.ToArray(); + } + + return pbRet; + } + + public static VariantDictionary Deserialize(byte[] pb) + { + if(pb == null) { Debug.Assert(false); return null; } + + VariantDictionary d = new VariantDictionary(); + using(MemoryStream ms = new MemoryStream(pb, false)) + { + ushort uVersion = MemUtil.BytesToUInt16(MemUtil.Read(ms, 2)); + if((uVersion & VdmCritical) > (VdVersion & VdmCritical)) + throw new FormatException(KLRes.FileNewVerReq); + + while(true) + { + int iType = ms.ReadByte(); + if(iType < 0) throw new EndOfStreamException(KLRes.FileCorrupted); + byte btType = (byte)iType; + if(btType == (byte)VdType.None) break; + + int cbName = MemUtil.BytesToInt32(MemUtil.Read(ms, 4)); + byte[] pbName = MemUtil.Read(ms, cbName); + if(pbName.Length != cbName) + throw new EndOfStreamException(KLRes.FileCorrupted); + string strName = StrUtil.Utf8.GetString(pbName); + + int cbValue = MemUtil.BytesToInt32(MemUtil.Read(ms, 4)); + byte[] pbValue = MemUtil.Read(ms, cbValue); + if(pbValue.Length != cbValue) + throw new EndOfStreamException(KLRes.FileCorrupted); + + switch(btType) + { + case (byte)VdType.UInt32: + if(cbValue == 4) + d.SetUInt32(strName, MemUtil.BytesToUInt32(pbValue)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.UInt64: + if(cbValue == 8) + d.SetUInt64(strName, MemUtil.BytesToUInt64(pbValue)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.Bool: + if(cbValue == 1) + d.SetBool(strName, (pbValue[0] != 0)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.Int32: + if(cbValue == 4) + d.SetInt32(strName, MemUtil.BytesToInt32(pbValue)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.Int64: + if(cbValue == 8) + d.SetInt64(strName, MemUtil.BytesToInt64(pbValue)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.String: + d.SetString(strName, StrUtil.Utf8.GetString(pbValue)); + break; + + case (byte)VdType.ByteArray: + d.SetByteArray(strName, pbValue); + break; + + default: + Debug.Assert(false); // Unknown type + break; + } + } + + Debug.Assert(ms.ReadByte() < 0); + } + + return d; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/ChaCha20Cipher.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/ChaCha20Cipher.cs new file mode 100644 index 00000000..540b6409 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/ChaCha20Cipher.cs @@ -0,0 +1,254 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.Cipher +{ + /// + /// Implementation of the ChaCha20 cipher with a 96-bit nonce, + /// as specified in RFC 7539. + /// https://tools.ietf.org/html/rfc7539 + /// + public sealed class ChaCha20Cipher : CtrBlockCipher + { + private uint[] m_s = new uint[16]; // State + private uint[] m_x = new uint[16]; // Working buffer + + private bool m_bLargeCounter; // See constructor documentation + + private static readonly uint[] g_sigma = new uint[4] { + 0x61707865, 0x3320646E, 0x79622D32, 0x6B206574 + }; + + private const string StrNameRfc = "ChaCha20 (RFC 7539)"; + + public override int BlockSize + { + get { return 64; } + } + + public ChaCha20Cipher(byte[] pbKey32, byte[] pbIV12) : + this(pbKey32, pbIV12, false) + { + } + + /// + /// Constructor. + /// + /// Key (32 bytes). + /// Nonce (12 bytes). + /// If false, the RFC 7539 version + /// of ChaCha20 is used. In this case, only 256 GB of data can be + /// encrypted securely (because the block counter is a 32-bit variable); + /// an attempt to encrypt more data throws an exception. + /// If is true, the 32-bit + /// counter overflows to another 32-bit variable (i.e. the counter + /// effectively is a 64-bit variable), like in the original ChaCha20 + /// specification by D. J. Bernstein (which has a 64-bit counter and a + /// 64-bit nonce). To be compatible with this version, the 64-bit nonce + /// must be stored in the last 8 bytes of + /// and the first 4 bytes must be 0. + /// If the IV was generated randomly, a 12-byte IV and a large counter + /// can be used to securely encrypt more than 256 GB of data (but note + /// this is incompatible with RFC 7539 and the original specification). + public ChaCha20Cipher(byte[] pbKey32, byte[] pbIV12, bool bLargeCounter) : + base() + { + if(pbKey32 == null) throw new ArgumentNullException("pbKey32"); + if(pbKey32.Length != 32) throw new ArgumentOutOfRangeException("pbKey32"); + if(pbIV12 == null) throw new ArgumentNullException("pbIV12"); + if(pbIV12.Length != 12) throw new ArgumentOutOfRangeException("pbIV12"); + + m_bLargeCounter = bLargeCounter; + + // Key setup + m_s[4] = MemUtil.BytesToUInt32(pbKey32, 0); + m_s[5] = MemUtil.BytesToUInt32(pbKey32, 4); + m_s[6] = MemUtil.BytesToUInt32(pbKey32, 8); + m_s[7] = MemUtil.BytesToUInt32(pbKey32, 12); + m_s[8] = MemUtil.BytesToUInt32(pbKey32, 16); + m_s[9] = MemUtil.BytesToUInt32(pbKey32, 20); + m_s[10] = MemUtil.BytesToUInt32(pbKey32, 24); + m_s[11] = MemUtil.BytesToUInt32(pbKey32, 28); + m_s[0] = g_sigma[0]; + m_s[1] = g_sigma[1]; + m_s[2] = g_sigma[2]; + m_s[3] = g_sigma[3]; + + // IV setup + m_s[12] = 0; // Counter + m_s[13] = MemUtil.BytesToUInt32(pbIV12, 0); + m_s[14] = MemUtil.BytesToUInt32(pbIV12, 4); + m_s[15] = MemUtil.BytesToUInt32(pbIV12, 8); + } + + protected override void Dispose(bool bDisposing) + { + if(bDisposing) + { + MemUtil.ZeroArray(m_s); + MemUtil.ZeroArray(m_x); + } + + base.Dispose(bDisposing); + } + + protected override void NextBlock(byte[] pBlock) + { + if(pBlock == null) throw new ArgumentNullException("pBlock"); + if(pBlock.Length != 64) throw new ArgumentOutOfRangeException("pBlock"); + + // x is a local alias for the working buffer; with this, + // the compiler/runtime might remove some checks + uint[] x = m_x; + if(x == null) throw new InvalidOperationException(); + if(x.Length < 16) throw new InvalidOperationException(); + + uint[] s = m_s; + if(s == null) throw new InvalidOperationException(); + if(s.Length < 16) throw new InvalidOperationException(); + + Array.Copy(s, x, 16); + + unchecked + { + // 10 * 8 quarter rounds = 20 rounds + for(int i = 0; i < 10; ++i) + { + // Column quarter rounds + x[ 0] += x[ 4]; + x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 0], 16); + x[ 8] += x[12]; + x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 8], 12); + x[ 0] += x[ 4]; + x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 0], 8); + x[ 8] += x[12]; + x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 8], 7); + + x[ 1] += x[ 5]; + x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 1], 16); + x[ 9] += x[13]; + x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[ 9], 12); + x[ 1] += x[ 5]; + x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 1], 8); + x[ 9] += x[13]; + x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[ 9], 7); + + x[ 2] += x[ 6]; + x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 2], 16); + x[10] += x[14]; + x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[10], 12); + x[ 2] += x[ 6]; + x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 2], 8); + x[10] += x[14]; + x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[10], 7); + + x[ 3] += x[ 7]; + x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 3], 16); + x[11] += x[15]; + x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[11], 12); + x[ 3] += x[ 7]; + x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 3], 8); + x[11] += x[15]; + x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[11], 7); + + // Diagonal quarter rounds + x[ 0] += x[ 5]; + x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 0], 16); + x[10] += x[15]; + x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[10], 12); + x[ 0] += x[ 5]; + x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 0], 8); + x[10] += x[15]; + x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[10], 7); + + x[ 1] += x[ 6]; + x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 1], 16); + x[11] += x[12]; + x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[11], 12); + x[ 1] += x[ 6]; + x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 1], 8); + x[11] += x[12]; + x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[11], 7); + + x[ 2] += x[ 7]; + x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 2], 16); + x[ 8] += x[13]; + x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[ 8], 12); + x[ 2] += x[ 7]; + x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 2], 8); + x[ 8] += x[13]; + x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[ 8], 7); + + x[ 3] += x[ 4]; + x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 3], 16); + x[ 9] += x[14]; + x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 9], 12); + x[ 3] += x[ 4]; + x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 3], 8); + x[ 9] += x[14]; + x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 9], 7); + } + + for(int i = 0; i < 16; ++i) x[i] += s[i]; + + for(int i = 0; i < 16; ++i) + { + int i4 = i << 2; + uint xi = x[i]; + + pBlock[i4] = (byte)xi; + pBlock[i4 + 1] = (byte)(xi >> 8); + pBlock[i4 + 2] = (byte)(xi >> 16); + pBlock[i4 + 3] = (byte)(xi >> 24); + } + + ++s[12]; + if(s[12] == 0) + { + if(!m_bLargeCounter) + throw new InvalidOperationException( + KLRes.EncDataTooLarge.Replace(@"{PARAM}", StrNameRfc)); + ++s[13]; // Increment high half of large counter + } + } + } + + public long Seek(long lOffset, SeekOrigin so) + { + if(so != SeekOrigin.Begin) throw new NotSupportedException(); + + if((lOffset < 0) || ((lOffset & 63) != 0) || + ((lOffset >> 6) > (long)uint.MaxValue)) + throw new ArgumentOutOfRangeException("lOffset"); + + m_s[12] = (uint)(lOffset >> 6); + InvalidateBlock(); + + return lOffset; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/ChaCha20Engine.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/ChaCha20Engine.cs new file mode 100644 index 00000000..22cee5db --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/ChaCha20Engine.cs @@ -0,0 +1,191 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +using KeePassLib.Resources; + +namespace KeePassLib.Cryptography.Cipher +{ + public sealed class ChaCha20Engine : ICipherEngine2 + { + private static PwUuid m_uuid = null; + + internal static PwUuid ChaCha20Uuid + { + get + { + PwUuid pu = m_uuid; + if (pu == null) + { + pu = new PwUuid(new byte[] { + 0xD6, 0x03, 0x8A, 0x2B, 0x8B, 0x6F, 0x4C, 0xB5, + 0xA5, 0x24, 0x33, 0x9A, 0x31, 0xDB, 0xB5, 0x9A }); + m_uuid = pu; + } + + return pu; + } + } + + public PwUuid CipherUuid + { + get { return ChaCha20Uuid; } + } + + public string DisplayName + { + get + { + return ("ChaCha20 (" + KLRes.KeyBits.Replace(@"{PARAM}", + "256") + ", RFC 7539)"); + } + } + + public int KeyLength + { + get { return 32; } + } + + public int IVLength + { + get { return 12; } // 96 bits + } + + public Stream EncryptStream(Stream sPlainText, byte[] pbKey, byte[] pbIV) + { + return new ChaCha20Stream(sPlainText, true, pbKey, pbIV); + } + + public Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV) + { + return new ChaCha20Stream(sEncrypted, false, pbKey, pbIV); + } + } + + internal sealed class ChaCha20Stream : Stream + { + private Stream m_sBase; + private readonly bool m_bWriting; + private ChaCha20Cipher m_c; + + private byte[] m_pbBuffer = null; + + public override bool CanRead + { + get { return !m_bWriting; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return m_bWriting; } + } + + public override long Length + { + get { Debug.Assert(false); throw new NotSupportedException(); } + } + + public override long Position + { + get { Debug.Assert(false); throw new NotSupportedException(); } + set { Debug.Assert(false); throw new NotSupportedException(); } + } + + public ChaCha20Stream(Stream sBase, bool bWriting, byte[] pbKey32, + byte[] pbIV12) + { + if(sBase == null) throw new ArgumentNullException("sBase"); + + m_sBase = sBase; + m_bWriting = bWriting; + m_c = new ChaCha20Cipher(pbKey32, pbIV12); + } + + protected override void Dispose(bool bDisposing) + { + if(bDisposing) + { + if(m_sBase != null) + { + m_c.Dispose(); + m_c = null; + + m_sBase.Close(); + m_sBase = null; + } + + m_pbBuffer = null; + } + + base.Dispose(bDisposing); + } + + public override void Flush() + { + Debug.Assert(m_sBase != null); + if(m_bWriting && (m_sBase != null)) m_sBase.Flush(); + } + + public override long Seek(long lOffset, SeekOrigin soOrigin) + { + Debug.Assert(false); + throw new NotImplementedException(); + } + + public override void SetLength(long lValue) + { + Debug.Assert(false); + throw new NotImplementedException(); + } + + public override int Read(byte[] pbBuffer, int iOffset, int nCount) + { + if(m_bWriting) throw new InvalidOperationException(); + + int cbRead = m_sBase.Read(pbBuffer, iOffset, nCount); + m_c.Decrypt(pbBuffer, iOffset, cbRead); + return cbRead; + } + + public override void Write(byte[] pbBuffer, int iOffset, int nCount) + { + if(nCount < 0) throw new ArgumentOutOfRangeException("nCount"); + if(nCount == 0) return; + + if(!m_bWriting) throw new InvalidOperationException(); + + if((m_pbBuffer == null) || (m_pbBuffer.Length < nCount)) + m_pbBuffer = new byte[nCount]; + Array.Copy(pbBuffer, iOffset, m_pbBuffer, 0, nCount); + + m_c.Encrypt(m_pbBuffer, 0, nCount); + m_sBase.Write(m_pbBuffer, 0, nCount); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/CipherPool.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/CipherPool.cs new file mode 100644 index 00000000..e8e25868 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/CipherPool.cs @@ -0,0 +1,171 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Diagnostics; + +namespace KeePassLib.Cryptography.Cipher +{ + /// + /// Pool of encryption/decryption algorithms (ciphers). + /// + public sealed class CipherPool + { + private List m_vCiphers = new List(); + private static CipherPool m_poolGlobal = null; + + /// + /// Reference to the global cipher pool. + /// + public static CipherPool GlobalPool + { + get + { + CipherPool cp = m_poolGlobal; + if(cp == null) + { + cp = new CipherPool(); + cp.AddCipher(new StandardAesEngine()); + cp.AddCipher(new ChaCha20Engine()); + m_poolGlobal = cp; + } + + return cp; + } + } + + /// + /// Remove all cipher engines from the current pool. + /// + public void Clear() + { + m_vCiphers.Clear(); + } + + /// + /// Add a cipher engine to the pool. + /// + /// Cipher engine to add. Must not be null. + public void AddCipher(ICipherEngine csEngine) + { + Debug.Assert(csEngine != null); + if(csEngine == null) throw new ArgumentNullException("csEngine"); + + // Return if a cipher with that ID is registered already. + for(int i = 0; i < m_vCiphers.Count; ++i) + if(m_vCiphers[i].CipherUuid.Equals(csEngine.CipherUuid)) + return; + + m_vCiphers.Add(csEngine); + } + + /// + /// Get a cipher identified by its UUID. + /// + /// UUID of the cipher to return. + /// Reference to the requested cipher. If the cipher is + /// not found, null is returned. + public ICipherEngine GetCipher(PwUuid uuidCipher) + { + foreach(ICipherEngine iEngine in m_vCiphers) + { + if(iEngine.CipherUuid.Equals(uuidCipher)) + return iEngine; + } + + return null; + } + + /// + /// Get the index of a cipher. This index is temporary and should + /// not be stored or used to identify a cipher. + /// + /// UUID of the cipher. + /// Index of the requested cipher. Returns -1 if + /// the specified cipher is not found. + public int GetCipherIndex(PwUuid uuidCipher) + { + for(int i = 0; i < m_vCiphers.Count; ++i) + { + if(m_vCiphers[i].CipherUuid.Equals(uuidCipher)) + return i; + } + + Debug.Assert(false); + return -1; + } + + /// + /// Get the index of a cipher. This index is temporary and should + /// not be stored or used to identify a cipher. + /// + /// Name of the cipher. Note that + /// multiple ciphers can have the same name. In this case, the + /// first matching cipher is returned. + /// Cipher with the specified name or -1 if + /// no cipher with that name is found. + public int GetCipherIndex(string strDisplayName) + { + for(int i = 0; i < m_vCiphers.Count; ++i) + if(m_vCiphers[i].DisplayName == strDisplayName) + return i; + + Debug.Assert(false); + return -1; + } + + /// + /// Get the number of cipher engines in this pool. + /// + public int EngineCount + { + get { return m_vCiphers.Count; } + } + + /// + /// Get the cipher engine at the specified position. Throws + /// an exception if the index is invalid. You can use this + /// to iterate over all ciphers, but do not use it to + /// identify ciphers. + /// + /// Index of the requested cipher engine. + /// Reference to the cipher engine at the specified + /// position. + public ICipherEngine this[int nIndex] + { + get + { + if((nIndex < 0) || (nIndex >= m_vCiphers.Count)) + throw new ArgumentOutOfRangeException("nIndex"); + + return m_vCiphers[nIndex]; + } + } + + public IEnumerable Engines + { + get { + return m_vCiphers; + } + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/CtrBlockCipher.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/CtrBlockCipher.cs new file mode 100644 index 00000000..a143306c --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/CtrBlockCipher.cs @@ -0,0 +1,104 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.Cipher +{ + public abstract class CtrBlockCipher : IDisposable + { + private byte[] m_pBlock; + private int m_iBlockPos; + + public abstract int BlockSize + { + get; + } + + public CtrBlockCipher() + { + int cb = this.BlockSize; + if(cb <= 0) throw new InvalidOperationException("this.BlockSize"); + + m_pBlock = new byte[cb]; + m_iBlockPos = cb; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool bDisposing) + { + if(bDisposing) + { + MemUtil.ZeroByteArray(m_pBlock); + m_iBlockPos = m_pBlock.Length; + } + } + + protected void InvalidateBlock() + { + m_iBlockPos = m_pBlock.Length; + } + + protected abstract void NextBlock(byte[] pBlock); + + public void Encrypt(byte[] m, int iOffset, int cb) + { + if(m == null) throw new ArgumentNullException("m"); + if(iOffset < 0) throw new ArgumentOutOfRangeException("iOffset"); + if(cb < 0) throw new ArgumentOutOfRangeException("cb"); + if(iOffset > (m.Length - cb)) throw new ArgumentOutOfRangeException("cb"); + + int cbBlock = m_pBlock.Length; + + while(cb > 0) + { + Debug.Assert(m_iBlockPos <= cbBlock); + if(m_iBlockPos == cbBlock) + { + NextBlock(m_pBlock); + m_iBlockPos = 0; + } + + int cbCopy = Math.Min(cbBlock - m_iBlockPos, cb); + Debug.Assert(cbCopy > 0); + + MemUtil.XorArray(m_pBlock, m_iBlockPos, m, iOffset, cbCopy); + + m_iBlockPos += cbCopy; + iOffset += cbCopy; + cb -= cbCopy; + } + } + + public void Decrypt(byte[] m, int iOffset, int cb) + { + Encrypt(m, iOffset, cb); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/ICipherEngine.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/ICipherEngine.cs new file mode 100644 index 00000000..a5cb652d --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/ICipherEngine.cs @@ -0,0 +1,87 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.IO; + +namespace KeePassLib.Cryptography.Cipher +{ + /// + /// Interface of an encryption/decryption class. + /// + public interface ICipherEngine + { + /// + /// UUID of the engine. If you want to write an engine/plugin, + /// please contact the KeePass team to obtain a new UUID. + /// + PwUuid CipherUuid + { + get; + } + + /// + /// String displayed in the list of available encryption/decryption + /// engines in the GUI. + /// + string DisplayName + { + get; + } + + /// + /// Encrypt a stream. + /// + /// Stream to read the plain-text from. + /// Key to use. + /// Initialization vector. + /// Stream, from which the encrypted data can be read. + Stream EncryptStream(Stream sPlainText, byte[] pbKey, byte[] pbIV); + + /// + /// Decrypt a stream. + /// + /// Stream to read the encrypted data from. + /// Key to use. + /// Initialization vector. + /// Stream, from which the decrypted data can be read. + Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV); + } + + public interface ICipherEngine2 : ICipherEngine + { + /// + /// Length of an encryption key in bytes. + /// The base ICipherEngine assumes 32. + /// + int KeyLength + { + get; +} + + /// + /// Length of the initialization vector in bytes. + /// The base ICipherEngine assumes 16. + /// + int IVLength + { + get; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/Salsa20Cipher.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/Salsa20Cipher.cs new file mode 100644 index 00000000..a0385f20 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/Salsa20Cipher.cs @@ -0,0 +1,165 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// Implementation of the Salsa20 cipher, based on the eSTREAM submission. + + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.Cipher +{ + public sealed class Salsa20Cipher : CtrBlockCipher + { + private uint[] m_s = new uint[16]; // State + private uint[] m_x = new uint[16]; // Working buffer + + private static readonly uint[] g_sigma = new uint[4] { + 0x61707865, 0x3320646E, 0x79622D32, 0x6B206574 + }; + + public override int BlockSize + { + get { return 64; } + } + + public Salsa20Cipher(byte[] pbKey32, byte[] pbIV8) : base() + { + if(pbKey32 == null) throw new ArgumentNullException("pbKey32"); + if(pbKey32.Length != 32) throw new ArgumentOutOfRangeException("pbKey32"); + if(pbIV8 == null) throw new ArgumentNullException("pbIV8"); + if(pbIV8.Length != 8) throw new ArgumentOutOfRangeException("pbIV8"); + // Clear sensitive data + // Key setup + m_s[1] = MemUtil.BytesToUInt32(pbKey32, 0); + m_s[2] = MemUtil.BytesToUInt32(pbKey32, 4); + m_s[3] = MemUtil.BytesToUInt32(pbKey32, 8); + m_s[4] = MemUtil.BytesToUInt32(pbKey32, 12); + m_s[11] = MemUtil.BytesToUInt32(pbKey32, 16); + m_s[12] = MemUtil.BytesToUInt32(pbKey32, 20); + m_s[13] = MemUtil.BytesToUInt32(pbKey32, 24); + m_s[14] = MemUtil.BytesToUInt32(pbKey32, 28); + m_s[0] = g_sigma[0]; + m_s[5] = g_sigma[1]; + m_s[10] = g_sigma[2]; + m_s[15] = g_sigma[3]; + + // IV setup + m_s[6] = MemUtil.BytesToUInt32(pbIV8, 0); + m_s[7] = MemUtil.BytesToUInt32(pbIV8, 4); + m_s[8] = 0; // Counter, low + m_s[9] = 0; // Counter, high + } + + protected override void Dispose(bool bDisposing) + { + if(bDisposing) + { + MemUtil.ZeroArray(m_s); + MemUtil.ZeroArray(m_x); + } + + base.Dispose(bDisposing); + } + // Compiler/runtime might remove array bound checks after this + protected override void NextBlock(byte[] pBlock) + { + if(pBlock == null) throw new ArgumentNullException("pBlock"); + if(pBlock.Length != 64) throw new ArgumentOutOfRangeException("pBlock"); + + // x is a local alias for the working buffer; with this, + // the compiler/runtime might remove some checks + uint[] x = m_x; + if(x == null) throw new InvalidOperationException(); + if(x.Length < 16) throw new InvalidOperationException(); + + uint[] s = m_s; + if(s == null) throw new InvalidOperationException(); + if(s.Length < 16) throw new InvalidOperationException(); + + Array.Copy(s, x, 16); + + unchecked + { + // 10 * 8 quarter rounds = 20 rounds + for(int i = 0; i < 10; ++i) // (int i = 20; i > 0; i -= 2) + { + x[ 4] ^= MemUtil.RotateLeft32(x[ 0] + x[12], 7); + x[ 8] ^= MemUtil.RotateLeft32(x[ 4] + x[ 0], 9); + x[12] ^= MemUtil.RotateLeft32(x[ 8] + x[ 4], 13); + x[ 0] ^= MemUtil.RotateLeft32(x[12] + x[ 8], 18); + + x[ 9] ^= MemUtil.RotateLeft32(x[ 5] + x[ 1], 7); + x[13] ^= MemUtil.RotateLeft32(x[ 9] + x[ 5], 9); + x[ 1] ^= MemUtil.RotateLeft32(x[13] + x[ 9], 13); + x[ 5] ^= MemUtil.RotateLeft32(x[ 1] + x[13], 18); + + x[14] ^= MemUtil.RotateLeft32(x[10] + x[ 6], 7); + x[ 2] ^= MemUtil.RotateLeft32(x[14] + x[10], 9); + x[ 6] ^= MemUtil.RotateLeft32(x[ 2] + x[14], 13); + x[10] ^= MemUtil.RotateLeft32(x[ 6] + x[ 2], 18); + + x[ 3] ^= MemUtil.RotateLeft32(x[15] + x[11], 7); + x[ 7] ^= MemUtil.RotateLeft32(x[ 3] + x[15], 9); + x[11] ^= MemUtil.RotateLeft32(x[ 7] + x[ 3], 13); + x[15] ^= MemUtil.RotateLeft32(x[11] + x[ 7], 18); + + x[ 1] ^= MemUtil.RotateLeft32(x[ 0] + x[ 3], 7); + x[ 2] ^= MemUtil.RotateLeft32(x[ 1] + x[ 0], 9); + x[ 3] ^= MemUtil.RotateLeft32(x[ 2] + x[ 1], 13); + x[ 0] ^= MemUtil.RotateLeft32(x[ 3] + x[ 2], 18); + + x[ 6] ^= MemUtil.RotateLeft32(x[ 5] + x[ 4], 7); + x[ 7] ^= MemUtil.RotateLeft32(x[ 6] + x[ 5], 9); + x[ 4] ^= MemUtil.RotateLeft32(x[ 7] + x[ 6], 13); + x[ 5] ^= MemUtil.RotateLeft32(x[ 4] + x[ 7], 18); + + x[11] ^= MemUtil.RotateLeft32(x[10] + x[ 9], 7); + x[ 8] ^= MemUtil.RotateLeft32(x[11] + x[10], 9); + x[ 9] ^= MemUtil.RotateLeft32(x[ 8] + x[11], 13); + x[10] ^= MemUtil.RotateLeft32(x[ 9] + x[ 8], 18); + + x[12] ^= MemUtil.RotateLeft32(x[15] + x[14], 7); + x[13] ^= MemUtil.RotateLeft32(x[12] + x[15], 9); + x[14] ^= MemUtil.RotateLeft32(x[13] + x[12], 13); + x[15] ^= MemUtil.RotateLeft32(x[14] + x[13], 18); + } + + for(int i = 0; i < 16; ++i) x[i] += s[i]; + + for(int i = 0; i < 16; ++i) + { + int i4 = i << 2; + uint xi = x[i]; + + pBlock[i4] = (byte)xi; + pBlock[i4 + 1] = (byte)(xi >> 8); + pBlock[i4 + 2] = (byte)(xi >> 16); + pBlock[i4 + 3] = (byte)(xi >> 24); + } + + ++s[8]; + if(s[8] == 0) ++s[9]; + } + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/StandardAesEngine.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/StandardAesEngine.cs new file mode 100644 index 00000000..8e0c07be --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/Cipher/StandardAesEngine.cs @@ -0,0 +1,157 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Security; +using System.Diagnostics; + +#if !KeePassUAP +using System.Security.Cryptography; +#endif + +using KeePassLib.Resources; + +namespace KeePassLib.Cryptography.Cipher +{ + /// + /// Standard AES cipher implementation. + /// + public sealed class StandardAesEngine : ICipherEngine + { +#if !KeePassUAP + private const CipherMode m_rCipherMode = CipherMode.CBC; + private const PaddingMode m_rCipherPadding = PaddingMode.PKCS7; +#endif + + private static PwUuid g_uuidAes = null; + + /// + /// UUID of the cipher engine. This ID uniquely identifies the + /// AES engine. Must not be used by other ciphers. + /// + public static PwUuid AesUuid + { + get + { + PwUuid pu = g_uuidAes; + if(pu == null) + { + pu = new PwUuid(new byte[] { + 0x31, 0xC1, 0xF2, 0xE6, 0xBF, 0x71, 0x43, 0x50, + 0xBE, 0x58, 0x05, 0x21, 0x6A, 0xFC, 0x5A, 0xFF }); + g_uuidAes = pu; + } + + return pu; + } + } + + /// + /// Get the UUID of this cipher engine as PwUuid object. + /// + public PwUuid CipherUuid + { + get { return StandardAesEngine.AesUuid; } + } + + /// + /// Get a displayable name describing this cipher engine. + /// + public string DisplayName + { + get + { + return ("AES/Rijndael (" + KLRes.KeyBits.Replace(@"{PARAM}", + "256") + ", FIPS 197)"); + } + } + + private static void ValidateArguments(Stream stream, bool bEncrypt, byte[] pbKey, byte[] pbIV) + { + Debug.Assert(stream != null); if(stream == null) throw new ArgumentNullException("stream"); + + Debug.Assert(pbKey != null); if(pbKey == null) throw new ArgumentNullException("pbKey"); + Debug.Assert(pbKey.Length == 32); + if(pbKey.Length != 32) throw new ArgumentException("Key must be 256 bits wide!"); + + Debug.Assert(pbIV != null); if(pbIV == null) throw new ArgumentNullException("pbIV"); + Debug.Assert(pbIV.Length == 16); + if(pbIV.Length != 16) throw new ArgumentException("Initialization vector must be 128 bits wide!"); + + if(bEncrypt) + { + Debug.Assert(stream.CanWrite); + if(!stream.CanWrite) throw new ArgumentException("Stream must be writable!"); + } + else // Decrypt + { + Debug.Assert(stream.CanRead); + if(!stream.CanRead) throw new ArgumentException("Encrypted stream must be readable!"); + } + } + + private static Stream CreateStream(Stream s, bool bEncrypt, byte[] pbKey, byte[] pbIV) + { + StandardAesEngine.ValidateArguments(s, bEncrypt, pbKey, pbIV); + + byte[] pbLocalIV = new byte[16]; + Array.Copy(pbIV, pbLocalIV, 16); + + byte[] pbLocalKey = new byte[32]; + Array.Copy(pbKey, pbLocalKey, 32); + +#if KeePassUAP + return StandardAesEngineExt.CreateStream(s, bEncrypt, pbLocalKey, pbLocalIV); +#else + RijndaelManaged r = new RijndaelManaged(); + if(r.BlockSize != 128) // AES block size + { + Debug.Assert(false); + r.BlockSize = 128; + } + + r.IV = pbLocalIV; + r.KeySize = 256; + r.Key = pbLocalKey; + r.Mode = m_rCipherMode; + r.Padding = m_rCipherPadding; + + ICryptoTransform iTransform = (bEncrypt ? r.CreateEncryptor() : r.CreateDecryptor()); + Debug.Assert(iTransform != null); + if(iTransform == null) throw new SecurityException("Unable to create Rijndael transform!"); + + return new CryptoStream(s, iTransform, bEncrypt ? CryptoStreamMode.Write : + CryptoStreamMode.Read); +#endif + } + + public Stream EncryptStream(Stream sPlainText, byte[] pbKey, byte[] pbIV) + { + return StandardAesEngine.CreateStream(sPlainText, true, pbKey, pbIV); + } + + public Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV) + { + return StandardAesEngine.CreateStream(sEncrypted, false, pbKey, pbIV); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/CryptoRandom.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/CryptoRandom.cs new file mode 100644 index 00000000..312c71be --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/CryptoRandom.cs @@ -0,0 +1,393 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; + +#if !KeePassUAP +using System.Drawing; +using System.Security.Cryptography; +#endif + +using KeePassLib.Native; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography +{ + /// + /// Cryptographically secure pseudo-random number generator. + /// The returned values are unpredictable and cannot be reproduced. + /// CryptoRandom is a singleton class. + /// + public sealed class CryptoRandom + { + private byte[] m_pbEntropyPool = new byte[64]; + private ulong m_uCounter; + private RNGCryptoServiceProvider m_rng = new RNGCryptoServiceProvider(); + private ulong m_uGeneratedBytesCount = 0; + + private static object g_oSyncRoot = new object(); + private object m_oSyncRoot = new object(); + + private static CryptoRandom g_pInstance = null; + public static CryptoRandom Instance + { + get + { + CryptoRandom cr; + lock(g_oSyncRoot) + { + cr = g_pInstance; + if(cr == null) + { + cr = new CryptoRandom(); + g_pInstance = cr; + } + } + + return cr; + } + } + + /// + /// Get the number of random bytes that this instance generated so far. + /// Note that this number can be higher than the number of random bytes + /// actually requested using the GetRandomBytes method. + /// + public ulong GeneratedBytesCount + { + get + { + ulong u; + lock(m_oSyncRoot) { u = m_uGeneratedBytesCount; } + return u; + } + } + + /// + /// Event that is triggered whenever the internal GenerateRandom256 + /// method is called to generate random bytes. + /// + public event EventHandler GenerateRandom256Pre; + + private CryptoRandom() + { + + // byte[] pb = new byte[8]; + // rWeak.NextBytes(pb); + // m_uCounter = MemUtil.BytesToUInt64(pb); + m_uCounter = (ulong)DateTime.UtcNow.ToBinary(); + + AddEntropy(GetSystemData()); + AddEntropy(GetCspData()); + } + + /// + /// Update the internal seed of the random number generator based + /// on entropy data. + /// This method is thread-safe. + /// + /// Entropy bytes. + public void AddEntropy(byte[] pbEntropy) + { + if(pbEntropy == null) { Debug.Assert(false); return; } + if(pbEntropy.Length == 0) { Debug.Assert(false); return; } + + byte[] pbNewData = pbEntropy; + if(pbEntropy.Length > 64) + { +#if KeePassLibSD + using(SHA256Managed shaNew = new SHA256Managed()) +#else + using(SHA512Managed shaNew = new SHA512Managed()) +#endif + { + pbNewData = shaNew.ComputeHash(pbEntropy); + } + } + + lock(m_oSyncRoot) + { + int cbPool = m_pbEntropyPool.Length; + int cbNew = pbNewData.Length; + + byte[] pbCmp = new byte[cbPool + cbNew]; + Array.Copy(m_pbEntropyPool, pbCmp, cbPool); + Array.Copy(pbNewData, 0, pbCmp, cbPool, cbNew); + + MemUtil.ZeroByteArray(m_pbEntropyPool); + +#if KeePassLibSD + using(SHA256Managed shaPool = new SHA256Managed()) +#else + using(SHA512Managed shaPool = new SHA512Managed()) +#endif + { + m_pbEntropyPool = shaPool.ComputeHash(pbCmp); + } + + MemUtil.ZeroByteArray(pbCmp); + } + } + + private static byte[] GetSystemData() + { + MemoryStream ms = new MemoryStream(); + byte[] pb; + + pb = MemUtil.Int32ToBytes(Environment.TickCount); + MemUtil.Write(ms, pb); + + pb = MemUtil.Int64ToBytes(DateTime.UtcNow.ToBinary()); + MemUtil.Write(ms, pb); + +#if !KeePassLibSD + /*Not supported on Android + // In try-catch for systems without GUI; + // https://sourceforge.net/p/keepass/discussion/329221/thread/20335b73/ + try + { + Point pt = Cursor.Position; + pb = MemUtil.Int32ToBytes(pt.X); + MemUtil.Write(ms, pb); + pb = MemUtil.Int32ToBytes(pt.Y); + MemUtil.Write(ms, pb); + } + catch(Exception) { } + */ +#endif + + pb = MemUtil.UInt32ToBytes((uint)NativeLib.GetPlatformID()); + MemUtil.Write(ms, pb); + + try + { +#if KeePassUAP + string strOS = EnvironmentExt.OSVersion.VersionString; +#else + string strOS = Environment.OSVersion.VersionString; +#endif + AddStrHash(ms, strOS); + + pb = MemUtil.Int32ToBytes(Environment.ProcessorCount); + MemUtil.Write(ms, pb); + +#if !KeePassUAP + AddStrHash(ms, Environment.CommandLine); + + pb = MemUtil.Int64ToBytes(Environment.WorkingSet); + MemUtil.Write(ms, pb); +#endif + } + catch(Exception) { Debug.Assert(false); } + + try + { + foreach(DictionaryEntry de in Environment.GetEnvironmentVariables()) + { + AddStrHash(ms, (de.Key as string)); + AddStrHash(ms, (de.Value as string)); + } + } + catch(Exception) { Debug.Assert(false); } + +#if KeePassUAP + pb = DiagnosticsExt.GetProcessEntropy(); + MemUtil.Write(ms, pb); +#elif !KeePassLibSD + Process p = null; + try + { + p = Process.GetCurrentProcess(); + // Not supported in Mono 1.2.6: + pb = MemUtil.Int64ToBytes(p.Handle.ToInt64()); + MemUtil.Write(ms, pb); + pb = MemUtil.Int32ToBytes(p.HandleCount); + MemUtil.Write(ms, pb); + pb = MemUtil.Int32ToBytes(p.Id); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.NonpagedSystemMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PagedMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PagedSystemMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PeakPagedMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PeakVirtualMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PeakWorkingSet64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PrivateMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.StartTime.ToBinary()); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.VirtualMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.WorkingSet64); + MemUtil.Write(ms, pb); + // pb = MemUtil.UInt32ToBytes((uint)p.SessionId); + // ms.Write(pb, 0, pb.Length); + // pb = MemUtil.UInt32ToBytes((uint)p.SessionId); + // MemUtil.Write(ms, pb); + } + catch(Exception) { Debug.Assert(NativeLib.IsUnix()); } + finally + { + try { if(p != null) p.Dispose(); } + catch(Exception) { Debug.Assert(false); } + } +#endif + + try + { + CultureInfo ci = CultureInfo.CurrentCulture; + if(ci != null) + { + pb = MemUtil.Int32ToBytes(ci.GetHashCode()); + MemUtil.Write(ms, pb); + } + else { Debug.Assert(false); } + } + catch(Exception) { Debug.Assert(false); } + + pb = Guid.NewGuid().ToByteArray(); + MemUtil.Write(ms, pb); + + byte[] pbAll = ms.ToArray(); + ms.Close(); + return pbAll; + } + + private static void AddStrHash(Stream s, string str) + { + if(s == null) { Debug.Assert(false); return; } + if(string.IsNullOrEmpty(str)) return; + + byte[] pbUtf8 = StrUtil.Utf8.GetBytes(str); + byte[] pbHash = CryptoUtil.HashSha256(pbUtf8); + MemUtil.Write(s, pbHash); + } + + private byte[] GetCspData() + { + byte[] pbCspRandom = new byte[32]; + m_rng.GetBytes(pbCspRandom); + return pbCspRandom; + } + + private byte[] GenerateRandom256() + { + if(this.GenerateRandom256Pre != null) + this.GenerateRandom256Pre(this, EventArgs.Empty); + + byte[] pbCmp; + lock(m_oSyncRoot) + { + m_uCounter += 0x74D8B29E4D38E161UL; // Prime number + byte[] pbCounter = MemUtil.UInt64ToBytes(m_uCounter); + + byte[] pbCspRandom = GetCspData(); + + int cbPool = m_pbEntropyPool.Length; + int cbCtr = pbCounter.Length; + int cbCsp = pbCspRandom.Length; + + pbCmp = new byte[cbPool + cbCtr + cbCsp]; + Array.Copy(m_pbEntropyPool, pbCmp, cbPool); + Array.Copy(pbCounter, 0, pbCmp, cbPool, cbCtr); + Array.Copy(pbCspRandom, 0, pbCmp, cbPool + cbCtr, cbCsp); + + MemUtil.ZeroByteArray(pbCspRandom); + + m_uGeneratedBytesCount += 32; + } + + byte[] pbRet = CryptoUtil.HashSha256(pbCmp); + MemUtil.ZeroByteArray(pbCmp); + return pbRet; + } + + /// + /// Get a number of cryptographically strong random bytes. + /// This method is thread-safe. + /// + /// Number of requested random bytes. + /// A byte array consisting of + /// random bytes. + public byte[] GetRandomBytes(uint uRequestedBytes) + { + if(uRequestedBytes == 0) return MemUtil.EmptyByteArray; + if(uRequestedBytes > (uint)int.MaxValue) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("uRequestedBytes"); + } + + int cbRem = (int)uRequestedBytes; + byte[] pbRes = new byte[cbRem]; + int iPos = 0; + + while(cbRem != 0) + { + byte[] pbRandom256 = GenerateRandom256(); + Debug.Assert(pbRandom256.Length == 32); + + int cbCopy = Math.Min(cbRem, pbRandom256.Length); + Array.Copy(pbRandom256, 0, pbRes, iPos, cbCopy); + + MemUtil.ZeroByteArray(pbRandom256); + + iPos += cbCopy; + cbRem -= cbCopy; + } + + Debug.Assert(iPos == pbRes.Length); + return pbRes; + } + + + private static int g_iWeakSeed = 0; + public static Random NewWeakRandom() + { + long s64 = DateTime.UtcNow.ToBinary(); + int s32 = (int)((s64 >> 32) ^ s64); + + lock (g_oSyncRoot) + { + unchecked + { + g_iWeakSeed += 0x78A8C4B7; // Prime number + s32 ^= g_iWeakSeed; + } + } + + // Prevent overflow in the Random constructor of .NET 2.0 + if (s32 == int.MinValue) s32 = int.MaxValue; + + return new Random(s32); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/CryptoRandomStream.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/CryptoRandomStream.cs new file mode 100644 index 00000000..b0081c4b --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/CryptoRandomStream.cs @@ -0,0 +1,258 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Diagnostics; + +#if !KeePassUAP +using System.Security.Cryptography; +#endif + +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography +{ + /// + /// Algorithms supported by CryptoRandomStream. + /// + public enum CrsAlgorithm + { + /// + /// Not supported. + /// + Null = 0, + + /// + /// A variant of the ARCFour algorithm (RC4 incompatible). + /// + /// + ArcFourVariant = 1, + + /// + /// Salsa20 stream cipher algorithm. + /// + Salsa20 = 2, + + /// + /// ChaCha20 stream cipher algorithm. + /// + ChaCha20 = 3, + + Count = 4 + } + + /// + /// A random stream class. The class is initialized using random + /// bytes provided by the caller. The produced stream has random + /// properties, but for the same seed always the same stream + /// is produced, i.e. this class can be used as stream cipher. + /// + public sealed class CryptoRandomStream : IDisposable + { + private readonly CrsAlgorithm m_crsAlgorithm; + + private byte[] m_pbState = null; + private byte m_i = 0; + private byte m_j = 0; + + private Salsa20Cipher m_salsa20 = null; + private ChaCha20Cipher m_chacha20 = null; + + /// + /// Construct a new cryptographically secure random stream object. + /// + /// Algorithm to use. + /// Initialization key. Must not be null and + /// must contain at least 1 byte. + public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey) + { + if(pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); } + /// Thrown if the + int cbKey = pbKey.Length; + if(cbKey <= 0) + { + Debug.Assert(false); // Need at least one byte + throw new ArgumentOutOfRangeException("pbKey"); + } + /// parameter is null. + m_crsAlgorithm = a; + /// Thrown if the + if(a == CrsAlgorithm.ChaCha20) + { + byte[] pbKey32 = new byte[32]; + byte[] pbIV12 = new byte[12]; + /// parameter contains no bytes or the + using(SHA512Managed h = new SHA512Managed()) + { + byte[] pbHash = h.ComputeHash(pbKey); + Array.Copy(pbHash, pbKey32, 32); + Array.Copy(pbHash, 32, pbIV12, 0, 12); + MemUtil.ZeroByteArray(pbHash); + } + /// algorithm is unknown. + m_chacha20 = new ChaCha20Cipher(pbKey32, pbIV12, true); + } + else if(a == CrsAlgorithm.Salsa20) + { + byte[] pbKey32 = CryptoUtil.HashSha256(pbKey); + byte[] pbIV8 = new byte[8] { 0xE8, 0x30, 0x09, 0x4B, + 0x97, 0x20, 0x5D, 0x2A }; // Unique constant + + m_salsa20 = new Salsa20Cipher(pbKey32, pbIV8); + } + else if(a == CrsAlgorithm.ArcFourVariant) + { + // Fill the state linearly + m_pbState = new byte[256]; + for(int w = 0; w < 256; ++w) m_pbState[w] = (byte)w; + + unchecked + { + byte j = 0, t; + int inxKey = 0; + for(int w = 0; w < 256; ++w) // Key setup + { + j += (byte)(m_pbState[w] + pbKey[inxKey]); + + t = m_pbState[0]; // Swap entries + m_pbState[0] = m_pbState[j]; + m_pbState[j] = t; + + ++inxKey; + if(inxKey >= cbKey) inxKey = 0; + } + } + + GetRandomBytes(512); // Increases security, see cryptanalysis + } + else // Unknown algorithm + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("a"); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if(disposing) + { + if(m_crsAlgorithm == CrsAlgorithm.ChaCha20) + m_chacha20.Dispose(); + else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) + m_salsa20.Dispose(); + else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) + { + MemUtil.ZeroByteArray(m_pbState); + m_i = 0; + m_j = 0; + } + else { Debug.Assert(false); } + } + } + + /// + /// Get random bytes. + /// + /// Number of random bytes to retrieve. + /// Returns random bytes. + public byte[] GetRandomBytes(uint uRequestedCount) + { + if(uRequestedCount == 0) return MemUtil.EmptyByteArray; + + if(uRequestedCount > (uint)int.MaxValue) + throw new ArgumentOutOfRangeException("uRequestedCount"); + int cb = (int)uRequestedCount; + + byte[] pbRet = new byte[cb]; + + if(m_crsAlgorithm == CrsAlgorithm.ChaCha20) + m_chacha20.Encrypt(pbRet, 0, cb); + else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) + m_salsa20.Encrypt(pbRet, 0, cb); + else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) + { + unchecked + { + for(int w = 0; w < cb; ++w) + { + ++m_i; + m_j += m_pbState[m_i]; + + byte t = m_pbState[m_i]; // Swap entries + m_pbState[m_i] = m_pbState[m_j]; + m_pbState[m_j] = t; + + t = (byte)(m_pbState[m_i] + m_pbState[m_j]); + pbRet[w] = m_pbState[t]; + } + } + } + else { Debug.Assert(false); } + + return pbRet; + } + + public ulong GetRandomUInt64() + { + byte[] pb = GetRandomBytes(8); + return MemUtil.BytesToUInt64(pb); + } + +#if CRSBENCHMARK + public static string Benchmark() + { + int nRounds = 2000000; + + string str = "ArcFour small: " + BenchTime(CrsAlgorithm.ArcFourVariant, + nRounds, 16).ToString() + "\r\n"; + str += "ArcFour big: " + BenchTime(CrsAlgorithm.ArcFourVariant, + 32, 2 * 1024 * 1024).ToString() + "\r\n"; + str += "Salsa20 small: " + BenchTime(CrsAlgorithm.Salsa20, + nRounds, 16).ToString() + "\r\n"; + str += "Salsa20 big: " + BenchTime(CrsAlgorithm.Salsa20, + 32, 2 * 1024 * 1024).ToString(); + return str; + } + + private static int BenchTime(CrsAlgorithm cra, int nRounds, int nDataSize) + { + byte[] pbKey = new byte[4] { 0x00, 0x01, 0x02, 0x03 }; + + int nStart = Environment.TickCount; + for(int i = 0; i < nRounds; ++i) + { + using(CryptoRandomStream c = new CryptoRandomStream(cra, pbKey)) + { + c.GetRandomBytes((uint)nDataSize); + } + } + int nEnd = Environment.TickCount; + + return (nEnd - nStart); + } +#endif + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/CryptoUtil.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/CryptoUtil.cs new file mode 100644 index 00000000..6401f0f8 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/CryptoUtil.cs @@ -0,0 +1,254 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2020 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text; + +#if !KeePassUAP +using System.Security.Cryptography; +#endif + +using KeePassLib.Native; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography +{ + public static class CryptoUtil + { + private static bool? g_obProtData = null; + public static bool IsProtectedDataSupported + { + get + { + if (g_obProtData.HasValue) return g_obProtData.Value; + + bool b = false; + try + { + Random r = CryptoRandom.NewWeakRandom(); + + byte[] pbData = new byte[137]; + r.NextBytes(pbData); + + byte[] pbEnt = new byte[41]; + r.NextBytes(pbEnt); + + byte[] pbEnc = ProtectedData.Protect(pbData, pbEnt, + DataProtectionScope.CurrentUser); + if ((pbEnc != null) && !MemUtil.ArraysEqual(pbEnc, pbData)) + { + byte[] pbDec = ProtectedData.Unprotect(pbEnc, pbEnt, + DataProtectionScope.CurrentUser); + if ((pbDec != null) && MemUtil.ArraysEqual(pbDec, pbData)) + b = true; + } + } + catch (Exception) { Debug.Assert(false); } + + Debug.Assert(b); // Should be supported on all systems + g_obProtData = b; + return b; + } + } + + public static byte[] HashSha256(byte[] pbData) + { + if (pbData == null) throw new ArgumentNullException("pbData"); + + return HashSha256(pbData, 0, pbData.Length); + } + + public static byte[] HashSha256(byte[] pbData, int iOffset, int cbCount) + { + if (pbData == null) throw new ArgumentNullException("pbData"); + +#if DEBUG + byte[] pbCopy = new byte[pbData.Length]; + Array.Copy(pbData, pbCopy, pbData.Length); +#endif + + byte[] pbHash; + using (SHA256Managed h = new SHA256Managed()) + { + pbHash = h.ComputeHash(pbData, iOffset, cbCount); + } + +#if DEBUG + // Ensure the data has not been modified + Debug.Assert(MemUtil.ArraysEqual(pbData, pbCopy)); + + Debug.Assert((pbHash != null) && (pbHash.Length == 32)); + byte[] pbZero = new byte[32]; + Debug.Assert(!MemUtil.ArraysEqual(pbHash, pbZero)); +#endif + + return pbHash; + } + + internal static byte[] HashSha256(string strFilePath) + { + byte[] pbHash = null; + + using (FileStream fs = new FileStream(strFilePath, FileMode.Open, + FileAccess.Read, FileShare.Read)) + { + using (SHA256Managed h = new SHA256Managed()) + { + pbHash = h.ComputeHash(fs); + } + } + + return pbHash; + } + + /// + /// Create a cryptographic key of length + /// (in bytes) from . + /// + public static byte[] ResizeKey(byte[] pbIn, int iInOffset, + int cbIn, int cbOut) + { + if (pbIn == null) throw new ArgumentNullException("pbIn"); + if (cbOut < 0) throw new ArgumentOutOfRangeException("cbOut"); + + if (cbOut == 0) return MemUtil.EmptyByteArray; + + byte[] pbHash; + if (cbOut <= 32) pbHash = HashSha256(pbIn, iInOffset, cbIn); + else + { + using (SHA512Managed h = new SHA512Managed()) + { + pbHash = h.ComputeHash(pbIn, iInOffset, cbIn); + } + } + + if (cbOut == pbHash.Length) return pbHash; + + byte[] pbRet = new byte[cbOut]; + if (cbOut < pbHash.Length) + Array.Copy(pbHash, pbRet, cbOut); + else + { + int iPos = 0; + ulong r = 0; + while (iPos < cbOut) + { + Debug.Assert(pbHash.Length == 64); + using (HMACSHA256 h = new HMACSHA256(pbHash)) + { + byte[] pbR = MemUtil.UInt64ToBytes(r); + byte[] pbPart = h.ComputeHash(pbR); + + int cbCopy = Math.Min(cbOut - iPos, pbPart.Length); + Debug.Assert(cbCopy > 0); + + Array.Copy(pbPart, 0, pbRet, iPos, cbCopy); + iPos += cbCopy; + ++r; + + MemUtil.ZeroByteArray(pbPart); + } + } + Debug.Assert(iPos == cbOut); + } + +#if DEBUG + byte[] pbZero = new byte[pbHash.Length]; + Debug.Assert(!MemUtil.ArraysEqual(pbHash, pbZero)); +#endif + MemUtil.ZeroByteArray(pbHash); + return pbRet; + } + +#if !KeePassUAP + private static bool? g_obAesCsp = null; + internal static SymmetricAlgorithm CreateAes() + { + if (g_obAesCsp.HasValue) + return (g_obAesCsp.Value ? CreateAesCsp() : new RijndaelManaged()); + + SymmetricAlgorithm a = CreateAesCsp(); + g_obAesCsp = (a != null); + return (a ?? new RijndaelManaged()); + } + + private static SymmetricAlgorithm CreateAesCsp() + { + try + { + // On Windows, the CSP implementation is only minimally + // faster (and for key derivations it's not used anyway, + // as KeePass uses a native implementation based on + // CNG/BCrypt, which is much faster) + if (!NativeLib.IsUnix()) return null; + + string strFqn = Assembly.CreateQualifiedName( + "System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", + "System.Security.Cryptography.AesCryptoServiceProvider"); + + Type t = Type.GetType(strFqn); + if (t == null) return null; + + return (Activator.CreateInstance(t) as SymmetricAlgorithm); + } + catch (Exception) { Debug.Assert(false); } + + return null; + } +#endif + + public static byte[] ProtectData(byte[] pb, byte[] pbOptEntropy, + DataProtectionScope s) + { + return ProtectDataPriv(pb, true, pbOptEntropy, s); + } + + public static byte[] UnprotectData(byte[] pb, byte[] pbOptEntropy, + DataProtectionScope s) + { + return ProtectDataPriv(pb, false, pbOptEntropy, s); + } + + private static byte[] ProtectDataPriv(byte[] pb, bool bProtect, + byte[] pbOptEntropy, DataProtectionScope s) + { + if (pb == null) throw new ArgumentNullException("pb"); + + if ((pbOptEntropy != null) && (pbOptEntropy.Length == 0)) + pbOptEntropy = null; + + if (CryptoUtil.IsProtectedDataSupported) + { + if (bProtect) + return ProtectedData.Protect(pb, pbOptEntropy, s); + return ProtectedData.Unprotect(pb, pbOptEntropy, s); + } + + Debug.Assert(false); + byte[] pbCopy = new byte[pb.Length]; + Array.Copy(pb, pbCopy, pb.Length); + return pbCopy; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/Hash/Blake2b.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/Hash/Blake2b.cs new file mode 100644 index 00000000..98aaa37e --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/Hash/Blake2b.cs @@ -0,0 +1,232 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// This implementation is based on the official reference C +// implementation by Samuel Neves (CC0 1.0 Universal). + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +#if !KeePassUAP +using System.Security.Cryptography; +#endif + +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.Hash +{ + public sealed class Blake2b : HashAlgorithm + { + private const int NbRounds = 12; + private const int NbBlockBytes = 128; + private const int NbMaxOutBytes = 64; + + private static readonly ulong[] g_vIV = new ulong[8] { + 0x6A09E667F3BCC908UL, 0xBB67AE8584CAA73BUL, + 0x3C6EF372FE94F82BUL, 0xA54FF53A5F1D36F1UL, + 0x510E527FADE682D1UL, 0x9B05688C2B3E6C1FUL, + 0x1F83D9ABFB41BD6BUL, 0x5BE0CD19137E2179UL + }; + + private static readonly int[] g_vSigma = new int[NbRounds * 16] { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3, + 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4, + 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, + 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, + 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9, + 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, + 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, + 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5, + 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 + }; + + private readonly int m_cbHashLength; + + private ulong[] m_h = new ulong[8]; + private ulong[] m_t = new ulong[2]; + private ulong[] m_f = new ulong[2]; + private byte[] m_buf = new byte[NbBlockBytes]; + private int m_cbBuf = 0; + + private ulong[] m_m = new ulong[16]; + private ulong[] m_v = new ulong[16]; + + public Blake2b() + { + m_cbHashLength = NbMaxOutBytes; + this.HashSizeValue = NbMaxOutBytes * 8; // Bits + + Initialize(); + } + + public Blake2b(int cbHashLength) + { + if((cbHashLength < 0) || (cbHashLength > NbMaxOutBytes)) + throw new ArgumentOutOfRangeException("cbHashLength"); + + m_cbHashLength = cbHashLength; + this.HashSizeValue = cbHashLength * 8; // Bits + + Initialize(); + } + + public override void Initialize() + { + Debug.Assert(m_h.Length == g_vIV.Length); + Array.Copy(g_vIV, m_h, m_h.Length); + + // Fan-out = 1, depth = 1 + m_h[0] ^= 0x0000000001010000UL ^ (ulong)m_cbHashLength; + + Array.Clear(m_t, 0, m_t.Length); + Array.Clear(m_f, 0, m_f.Length); + Array.Clear(m_buf, 0, m_buf.Length); + m_cbBuf = 0; + + Array.Clear(m_m, 0, m_m.Length); + Array.Clear(m_v, 0, m_v.Length); + } + + private static void G(ulong[] v, ulong[] m, int r16, int i, + int a, int b, int c, int d) + { + int p = r16 + i; + + v[a] += v[b] + m[g_vSigma[p]]; + v[d] = MemUtil.RotateRight64(v[d] ^ v[a], 32); + v[c] += v[d]; + v[b] = MemUtil.RotateRight64(v[b] ^ v[c], 24); + v[a] += v[b] + m[g_vSigma[p + 1]]; + v[d] = MemUtil.RotateRight64(v[d] ^ v[a], 16); + v[c] += v[d]; + v[b] = MemUtil.RotateRight64(v[b] ^ v[c], 63); + } + + private void Compress(byte[] pb, int iOffset) + { + ulong[] v = m_v; + ulong[] m = m_m; + ulong[] h = m_h; + + for(int i = 0; i < 16; ++i) + m[i] = MemUtil.BytesToUInt64(pb, iOffset + (i << 3)); + + Array.Copy(h, v, 8); + v[8] = g_vIV[0]; + v[9] = g_vIV[1]; + v[10] = g_vIV[2]; + v[11] = g_vIV[3]; + v[12] = g_vIV[4] ^ m_t[0]; + v[13] = g_vIV[5] ^ m_t[1]; + v[14] = g_vIV[6] ^ m_f[0]; + v[15] = g_vIV[7] ^ m_f[1]; + + for(int r = 0; r < NbRounds; ++r) + { + int r16 = r << 4; + + G(v, m, r16, 0, 0, 4, 8, 12); + G(v, m, r16, 2, 1, 5, 9, 13); + G(v, m, r16, 4, 2, 6, 10, 14); + G(v, m, r16, 6, 3, 7, 11, 15); + G(v, m, r16, 8, 0, 5, 10, 15); + G(v, m, r16, 10, 1, 6, 11, 12); + G(v, m, r16, 12, 2, 7, 8, 13); + G(v, m, r16, 14, 3, 4, 9, 14); + } + + for(int i = 0; i < 8; ++i) + h[i] ^= v[i] ^ v[i + 8]; + } + + private void IncrementCounter(ulong cb) + { + m_t[0] += cb; + if(m_t[0] < cb) ++m_t[1]; + } + + protected override void HashCore(byte[] array, int ibStart, int cbSize) + { + Debug.Assert(m_f[0] == 0); + + if((m_cbBuf + cbSize) > NbBlockBytes) // Not '>=' (buffer must not be empty) + { + int cbFill = NbBlockBytes - m_cbBuf; + if(cbFill > 0) Array.Copy(array, ibStart, m_buf, m_cbBuf, cbFill); + + IncrementCounter((ulong)NbBlockBytes); + Compress(m_buf, 0); + + m_cbBuf = 0; + cbSize -= cbFill; + ibStart += cbFill; + + while(cbSize > NbBlockBytes) // Not '>=' (buffer must not be empty) + { + IncrementCounter((ulong)NbBlockBytes); + Compress(array, ibStart); + + cbSize -= NbBlockBytes; + ibStart += NbBlockBytes; + } + } + + if(cbSize > 0) + { + Debug.Assert((m_cbBuf + cbSize) <= NbBlockBytes); + + Array.Copy(array, ibStart, m_buf, m_cbBuf, cbSize); + m_cbBuf += cbSize; + } + } + + protected override byte[] HashFinal() + { + if(m_f[0] != 0) { Debug.Assert(false); throw new InvalidOperationException(); } + Debug.Assert(((m_t[1] == 0) && (m_t[0] == 0)) || + (m_cbBuf > 0)); // Buffer must not be empty for last block processing + + m_f[0] = ulong.MaxValue; // Indicate last block + + int cbFill = NbBlockBytes - m_cbBuf; + if(cbFill > 0) Array.Clear(m_buf, m_cbBuf, cbFill); + + IncrementCounter((ulong)m_cbBuf); + Compress(m_buf, 0); + + byte[] pbHash = new byte[NbMaxOutBytes]; + for(int i = 0; i < m_h.Length; ++i) + MemUtil.UInt64ToBytesEx(m_h[i], pbHash, i << 3); + + if(m_cbHashLength == NbMaxOutBytes) return pbHash; + Debug.Assert(m_cbHashLength < NbMaxOutBytes); + + byte[] pbShort = new byte[m_cbHashLength]; + if(m_cbHashLength > 0) + Array.Copy(pbHash, pbShort, m_cbHashLength); + MemUtil.ZeroByteArray(pbHash); + return pbShort; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/HashingStreamEx.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/HashingStreamEx.cs new file mode 100644 index 00000000..c36b9d9d --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/HashingStreamEx.cs @@ -0,0 +1,184 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Security.Cryptography; +using System.Diagnostics; + +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography +{ + public sealed class HashingStreamEx : Stream + { + private readonly Stream m_sBaseStream; + private bool m_bWriting; + private HashAlgorithm m_hash; + + private byte[] m_pbFinalHash = null; + + public byte[] Hash + { + get { return m_pbFinalHash; } + } + + public override bool CanRead + { + get { return !m_bWriting; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return m_bWriting; } + } + + public override long Length + { + get { return m_sBaseStream.Length; } + } + + public override long Position + { + get { return m_sBaseStream.Position; } + set { throw new NotSupportedException(); } + } + + public HashingStreamEx(Stream sBaseStream, bool bWriting, HashAlgorithm hashAlgorithm) + { + if(sBaseStream == null) throw new ArgumentNullException("sBaseStream"); + + m_sBaseStream = sBaseStream; + m_bWriting = bWriting; + +#if !KeePassLibSD + m_hash = (hashAlgorithm ?? new SHA256Managed()); +#else // KeePassLibSD + m_hash = null; + + try { m_hash = HashAlgorithm.Create("SHA256"); } + catch(Exception) { } + try { if(m_hash == null) m_hash = HashAlgorithm.Create(); } + catch(Exception) { } +#endif + if(m_hash == null) { Debug.Assert(false); return; } + + // Validate hash algorithm + if((!m_hash.CanReuseTransform) || (!m_hash.CanTransformMultipleBlocks) || + (m_hash.InputBlockSize != 1) || (m_hash.OutputBlockSize != 1)) + { +#if DEBUG + MessageService.ShowWarning("Broken HashAlgorithm object in HashingStreamEx."); +#endif + m_hash = null; + } + } + protected override void Dispose(bool disposing) + { + if(disposing) + { + if(m_hash != null) + { + try + { + m_hash.TransformFinalBlock(new byte[0], 0, 0); + + m_pbFinalHash = m_hash.Hash; + } + catch(Exception) { Debug.Assert(false); } + + m_hash = null; + } + + m_sBaseStream.Close(); + } + + base.Dispose(disposing); + } + + public override void Flush() + { + m_sBaseStream.Flush(); + } + + public override long Seek(long lOffset, SeekOrigin soOrigin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long lValue) + { + throw new NotSupportedException(); + } + + public override int Read(byte[] pbBuffer, int nOffset, int nCount) + { + if(m_bWriting) throw new InvalidOperationException(); + + int nRead = m_sBaseStream.Read(pbBuffer, nOffset, nCount); + int nPartialRead = nRead; + while((nRead < nCount) && (nPartialRead != 0)) + { + nPartialRead = m_sBaseStream.Read(pbBuffer, nOffset + nRead, + nCount - nRead); + nRead += nPartialRead; + } + +#if DEBUG + byte[] pbOrg = new byte[pbBuffer.Length]; + Array.Copy(pbBuffer, pbOrg, pbBuffer.Length); +#endif + + if((m_hash != null) && (nRead > 0)) + m_hash.TransformBlock(pbBuffer, nOffset, nRead, pbBuffer, nOffset); + +#if DEBUG + Debug.Assert(MemUtil.ArraysEqual(pbBuffer, pbOrg)); +#endif + + return nRead; + } + + public override void Write(byte[] pbBuffer, int nOffset, int nCount) + { + if(!m_bWriting) throw new InvalidOperationException(); + +#if DEBUG + byte[] pbOrg = new byte[pbBuffer.Length]; + Array.Copy(pbBuffer, pbOrg, pbBuffer.Length); +#endif + + if((m_hash != null) && (nCount > 0)) + m_hash.TransformBlock(pbBuffer, nOffset, nCount, pbBuffer, nOffset); + +#if DEBUG + Debug.Assert(MemUtil.ArraysEqual(pbBuffer, pbOrg)); +#endif + + m_sBaseStream.Write(pbBuffer, nOffset, nCount); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/HmacOtp.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/HmacOtp.cs new file mode 100644 index 00000000..d401e83f --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/HmacOtp.cs @@ -0,0 +1,92 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Security.Cryptography; +using System.Globalization; + +using KeePassLib.Utility; + +#if (!KeePassLibSD && !KeePassRT) +namespace KeePassLib.Cryptography +{ + /// + /// Generate HMAC-based one-time passwords as specified in RFC 4226. + /// + public static class HmacOtp + { + private static readonly uint[] vDigitsPower = new uint[]{ 1, 10, 100, + 1000, 10000, 100000, 1000000, 10000000, 100000000 }; + + public static string Generate(byte[] pbSecret, ulong uFactor, + uint uCodeDigits, bool bAddChecksum, int iTruncationOffset) + { + byte[] pbText = MemUtil.UInt64ToBytes(uFactor); + Array.Reverse(pbText); // Big-Endian + + HMACSHA1 hsha1 = new HMACSHA1(pbSecret); + byte[] pbHash = hsha1.ComputeHash(pbText); + + uint uOffset = (uint)(pbHash[pbHash.Length - 1] & 0xF); + if((iTruncationOffset >= 0) && (iTruncationOffset < (pbHash.Length - 4))) + uOffset = (uint)iTruncationOffset; + + uint uBinary = (uint)(((pbHash[uOffset] & 0x7F) << 24) | + ((pbHash[uOffset + 1] & 0xFF) << 16) | + ((pbHash[uOffset + 2] & 0xFF) << 8) | + (pbHash[uOffset + 3] & 0xFF)); + + uint uOtp = (uBinary % vDigitsPower[uCodeDigits]); + if(bAddChecksum) + uOtp = ((uOtp * 10) + CalculateChecksum(uOtp, uCodeDigits)); + + uint uDigits = (bAddChecksum ? (uCodeDigits + 1) : uCodeDigits); + return uOtp.ToString(NumberFormatInfo.InvariantInfo).PadLeft( + (int)uDigits, '0'); + } + + private static readonly uint[] vDoubleDigits = new uint[]{ 0, 2, 4, 6, 8, + 1, 3, 5, 7, 9 }; + + private static uint CalculateChecksum(uint uNum, uint uDigits) + { + bool bDoubleDigit = true; + uint uTotal = 0; + + while(0 < uDigits--) + { + uint uDigit = (uNum % 10); + uNum /= 10; + + if(bDoubleDigit) uDigit = vDoubleDigits[uDigit]; + + uTotal += uDigit; + bDoubleDigit = !bDoubleDigit; + } + + uint uResult = (uTotal % 10); + if(uResult != 0) uResult = 10 - uResult; + + return uResult; + } + } +} +#endif diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/AesKdf.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/AesKdf.cs new file mode 100644 index 00000000..cbd2cc56 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/AesKdf.cs @@ -0,0 +1,281 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using keepass2android; +#if KeePassUAP +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +#else +using System.Security.Cryptography; +#endif + +using KeePassLib.Cryptography; +using KeePassLib.Native; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public sealed class AesKdf : KdfEngine + { + private static readonly PwUuid g_uuid = new PwUuid(new byte[] { + 0xC9, 0xD9, 0xF3, 0x9A, 0x62, 0x8A, 0x44, 0x60, + 0xBF, 0x74, 0x0D, 0x08, 0xC1, 0x8A, 0x4F, 0xEA }); + + public const string ParamRounds = "R"; // UInt64 + public const string ParamSeed = "S"; // Byte[32] + + public override PwUuid Uuid + { + get { return g_uuid; } + } + + public override string Name + { + get { return "AES-KDF"; } + } + + public override byte[] GetSeed(KdfParameters p) + { return p.GetByteArray(ParamSeed); } + + public AesKdf() + { + } + + public override KdfParameters GetDefaultParameters() + { + KdfParameters p = base.GetDefaultParameters(); + p.SetUInt64(ParamRounds, PwDefs.DefaultKeyEncryptionRounds); + return p; + } + + public override void Randomize(KdfParameters p) + { + if(p == null) { Debug.Assert(false); return; } + Debug.Assert(g_uuid.Equals(p.KdfUuid)); + + byte[] pbSeed = CryptoRandom.Instance.GetRandomBytes(32); + p.SetByteArray(ParamSeed, pbSeed); + } + + public override byte[] Transform(byte[] pbMsg, KdfParameters p) + { + if(pbMsg == null) throw new ArgumentNullException("pbMsg"); + if(p == null) throw new ArgumentNullException("p"); + + Type tRounds = p.GetTypeOf(ParamRounds); + if(tRounds == null) throw new ArgumentNullException("p.Rounds"); + if(tRounds != typeof(ulong)) throw new ArgumentOutOfRangeException("p.Rounds"); + ulong uRounds = p.GetUInt64(ParamRounds, 0); + + byte[] pbSeed = p.GetByteArray(ParamSeed); + if(pbSeed == null) throw new ArgumentNullException("p.Seed"); + + if(pbMsg.Length != 32) + { + Debug.Assert(false); + pbMsg = CryptoUtil.HashSha256(pbMsg); + } + + if(pbSeed.Length != 32) + { + Debug.Assert(false); + pbSeed = CryptoUtil.HashSha256(pbSeed); + } + + return TransformKey(pbMsg, pbSeed, uRounds); + } + + private static byte[] TransformKey(byte[] pbOriginalKey32, byte[] pbKeySeed32, + ulong uNumRounds) + { + Debug.Assert((pbOriginalKey32 != null) && (pbOriginalKey32.Length == 32)); + if(pbOriginalKey32 == null) throw new ArgumentNullException("pbOriginalKey32"); + if(pbOriginalKey32.Length != 32) throw new ArgumentException(); + + Debug.Assert((pbKeySeed32 != null) && (pbKeySeed32.Length == 32)); + if(pbKeySeed32 == null) throw new ArgumentNullException("pbKeySeed32"); + if(pbKeySeed32.Length != 32) throw new ArgumentException(); + + byte[] pbNewKey = new byte[32]; + Array.Copy(pbOriginalKey32, pbNewKey, pbNewKey.Length); + + try + { + // Try to use the native library first + if (NativeLib.TransformKey256(pbNewKey, pbKeySeed32, uNumRounds)) + { + //no need to hash, this is already done in the native library. + byte[] pbKey = new byte[32]; + Array.Copy(pbNewKey, pbKey, pbNewKey.Length); + + return pbKey; + + } + + if(TransformKeyManaged(pbNewKey, pbKeySeed32, uNumRounds)) + return CryptoUtil.HashSha256(pbNewKey); + } + finally { MemUtil.ZeroByteArray(pbNewKey); } + + return null; + } + + public static bool TransformKeyManaged(byte[] pbNewKey32, byte[] pbKeySeed32, + ulong uNumRounds) + { + Kp2aLog.Log("Warning: transforming key managed. Expect this to be slow!"); +#if KeePassUAP + KeyParameter kp = new KeyParameter(pbKeySeed32); + AesEngine aes = new AesEngine(); + aes.Init(true, kp); + + for(ulong i = 0; i < uNumRounds; ++i) + { + aes.ProcessBlock(pbNewKey32, 0, pbNewKey32, 0); + aes.ProcessBlock(pbNewKey32, 16, pbNewKey32, 16); + } +#else + byte[] pbIV = new byte[16]; + Array.Clear(pbIV, 0, pbIV.Length); + + RijndaelManaged r = new RijndaelManaged(); + if(r.BlockSize != 128) // AES block size + { + Debug.Assert(false); + r.BlockSize = 128; + } + + r.IV = pbIV; + r.Mode = CipherMode.ECB; + r.KeySize = 256; + r.Key = pbKeySeed32; + ICryptoTransform iCrypt = r.CreateEncryptor(); + + // !iCrypt.CanReuseTransform -- doesn't work with Mono + if((iCrypt == null) || (iCrypt.InputBlockSize != 16) || + (iCrypt.OutputBlockSize != 16)) + { + Debug.Assert(false, "Invalid ICryptoTransform."); + Debug.Assert((iCrypt.InputBlockSize == 16), "Invalid input block size!"); + Debug.Assert((iCrypt.OutputBlockSize == 16), "Invalid output block size!"); + return false; + } + + for(ulong i = 0; i < uNumRounds; ++i) + { + iCrypt.TransformBlock(pbNewKey32, 0, 16, pbNewKey32, 0); + iCrypt.TransformBlock(pbNewKey32, 16, 16, pbNewKey32, 16); + } +#endif + + return true; + } + + public override KdfParameters GetBestParameters(uint uMilliseconds) + { + const ulong uStep = 3001; + ulong uRounds; + + KdfParameters p = GetDefaultParameters(); + + // Try native method + if(NativeLib.TransformKeyBenchmark256(uMilliseconds, out uRounds)) + { + p.SetUInt64(ParamRounds, uRounds); + return p; + } + + byte[] pbKey = new byte[32]; + byte[] pbNewKey = new byte[32]; + for(int i = 0; i < pbKey.Length; ++i) + { + pbKey[i] = (byte)i; + pbNewKey[i] = (byte)i; + } + +#if KeePassUAP + KeyParameter kp = new KeyParameter(pbKey); + AesEngine aes = new AesEngine(); + aes.Init(true, kp); +#else + byte[] pbIV = new byte[16]; + Array.Clear(pbIV, 0, pbIV.Length); + + RijndaelManaged r = new RijndaelManaged(); + if(r.BlockSize != 128) // AES block size + { + Debug.Assert(false); + r.BlockSize = 128; + } + + r.IV = pbIV; + r.Mode = CipherMode.ECB; + r.KeySize = 256; + r.Key = pbKey; + ICryptoTransform iCrypt = r.CreateEncryptor(); + + // !iCrypt.CanReuseTransform -- doesn't work with Mono + if((iCrypt == null) || (iCrypt.InputBlockSize != 16) || + (iCrypt.OutputBlockSize != 16)) + { + Debug.Assert(false, "Invalid ICryptoTransform."); + Debug.Assert(iCrypt.InputBlockSize == 16, "Invalid input block size!"); + Debug.Assert(iCrypt.OutputBlockSize == 16, "Invalid output block size!"); + + p.SetUInt64(ParamRounds, PwDefs.DefaultKeyEncryptionRounds); + return p; + } +#endif + + uRounds = 0; + int tStart = Environment.TickCount; + while(true) + { + for(ulong j = 0; j < uStep; ++j) + { +#if KeePassUAP + aes.ProcessBlock(pbNewKey, 0, pbNewKey, 0); + aes.ProcessBlock(pbNewKey, 16, pbNewKey, 16); +#else + iCrypt.TransformBlock(pbNewKey, 0, 16, pbNewKey, 0); + iCrypt.TransformBlock(pbNewKey, 16, 16, pbNewKey, 16); +#endif + } + + uRounds += uStep; + if(uRounds < uStep) // Overflow check + { + uRounds = ulong.MaxValue; + break; + } + + uint tElapsed = (uint)(Environment.TickCount - tStart); + if(tElapsed > uMilliseconds) break; + } + + p.SetUInt64(ParamRounds, uRounds); + return p; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/Argon2Kdf.Core.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/Argon2Kdf.Core.cs new file mode 100644 index 00000000..bf1dc7a7 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/Argon2Kdf.Core.cs @@ -0,0 +1,682 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// This implementation is based on the official reference C +// implementation by Daniel Dinu and Dmitry Khovratovich (CC0 1.0). + +// Relative iterations (* = B2ROUND_ARRAYS \\ G_INLINED): +// * | false true +// ------+----------- +// false | 8885 9618 +// true | 9009 9636 +#define ARGON2_B2ROUND_ARRAYS +#define ARGON2_G_INLINED + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +using KeePassLib.Cryptography.Hash; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public sealed partial class Argon2Kdf : KdfEngine + { + private const ulong NbBlockSize = 1024; + private const ulong NbBlockSizeInQW = NbBlockSize / 8UL; + private const ulong NbSyncPoints = 4; + + private const ulong NbAddressesInBlock = 128; + + private const int NbPreHashDigestLength = 64; + private const int NbPreHashSeedLength = NbPreHashDigestLength + 8; + +#if ARGON2_B2ROUND_ARRAYS + private static int[][] g_vFBCols = null; + private static int[][] g_vFBRows = null; +#endif + + private sealed class Argon2Ctx + { + public Argon2Type Type = Argon2Type.D; + public uint Version = 0; + + public ulong Lanes = 0; + public ulong TCost = 0; + public ulong MCost = 0; + public ulong MemoryBlocks = 0; + public ulong SegmentLength = 0; + public ulong LaneLength = 0; + + public ulong[] Mem = null; + } + + private sealed class Argon2ThreadInfo + { + public Argon2Ctx Context = null; + public ManualResetEvent Finished = new ManualResetEvent(false); + + public ulong Pass = 0; + public ulong Lane = 0; + public ulong Slice = 0; + public ulong Index = 0; + + public void Release() + { + if(this.Finished != null) + { + this.Finished.Close(); + this.Finished = null; + } + else { Debug.Assert(false); } + } + } + + private byte[] Argon2Transform(byte[] pbMsg, byte[] pbSalt, uint uParallel, + ulong uMem, ulong uIt, int cbOut, uint uVersion, byte[] pbSecretKey, + byte[] pbAssocData) + { + pbSecretKey = (pbSecretKey ?? MemUtil.EmptyByteArray); + pbAssocData = (pbAssocData ?? MemUtil.EmptyByteArray); + +#if ARGON2_B2ROUND_ARRAYS + InitB2RoundIndexArrays(); +#endif + + Argon2Ctx ctx = new Argon2Ctx(); + ctx.Type = m_t; + ctx.Version = uVersion; + + ctx.Lanes = uParallel; + ctx.TCost = uIt; + ctx.MCost = uMem / NbBlockSize; + ctx.MemoryBlocks = Math.Max(ctx.MCost, 2UL * NbSyncPoints * ctx.Lanes); + + ctx.SegmentLength = ctx.MemoryBlocks / (ctx.Lanes * NbSyncPoints); + ctx.MemoryBlocks = ctx.SegmentLength * ctx.Lanes * NbSyncPoints; + + ctx.LaneLength = ctx.SegmentLength * NbSyncPoints; + + Debug.Assert(NbBlockSize == (NbBlockSizeInQW * +#if KeePassUAP + (ulong)Marshal.SizeOf() +#else + (ulong)Marshal.SizeOf(typeof(ulong)) +#endif + )); + ctx.Mem = new ulong[ctx.MemoryBlocks * NbBlockSizeInQW]; + + Blake2b h = new Blake2b(); + + // Initial hash + Debug.Assert(h.HashSize == (NbPreHashDigestLength * 8)); + byte[] pbBuf = new byte[4]; + MemUtil.UInt32ToBytesEx(uParallel, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)cbOut, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)ctx.MCost, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)uIt, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx(uVersion, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)m_t, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)pbMsg.Length, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + h.TransformBlock(pbMsg, 0, pbMsg.Length, pbMsg, 0); + MemUtil.UInt32ToBytesEx((uint)pbSalt.Length, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + h.TransformBlock(pbSalt, 0, pbSalt.Length, pbSalt, 0); + MemUtil.UInt32ToBytesEx((uint)pbSecretKey.Length, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + h.TransformBlock(pbSecretKey, 0, pbSecretKey.Length, pbSecretKey, 0); + MemUtil.UInt32ToBytesEx((uint)pbAssocData.Length, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + h.TransformBlock(pbAssocData, 0, pbAssocData.Length, pbAssocData, 0); + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + byte[] pbH0 = h.Hash; + Debug.Assert(pbH0.Length == 64); + + byte[] pbBlockHash = new byte[NbPreHashSeedLength]; + Array.Copy(pbH0, pbBlockHash, pbH0.Length); + MemUtil.ZeroByteArray(pbH0); + + FillFirstBlocks(ctx, pbBlockHash, h); + MemUtil.ZeroByteArray(pbBlockHash); + + FillMemoryBlocks(ctx); + + byte[] pbOut = FinalHash(ctx, cbOut, h); + + h.Clear(); + MemUtil.ZeroArray(ctx.Mem); + return pbOut; + } + + private static void LoadBlock(ulong[] pqDst, ulong uDstOffset, byte[] pbIn) + { + // for(ulong i = 0; i < NbBlockSizeInQW; ++i) + // pqDst[uDstOffset + i] = MemUtil.BytesToUInt64(pbIn, (int)(i << 3)); + + Debug.Assert((uDstOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + int iDstOffset = (int)uDstOffset; + for(int i = 0; i < (int)NbBlockSizeInQW; ++i) + pqDst[iDstOffset + i] = MemUtil.BytesToUInt64(pbIn, i << 3); + } + + private static void StoreBlock(byte[] pbDst, ulong[] pqSrc) + { + for(int i = 0; i < (int)NbBlockSizeInQW; ++i) + MemUtil.UInt64ToBytesEx(pqSrc[i], pbDst, i << 3); + } + + private static void CopyBlock(ulong[] vDst, ulong uDstOffset, ulong[] vSrc, + ulong uSrcOffset) + { + // for(ulong i = 0; i < NbBlockSizeInQW; ++i) + // vDst[uDstOffset + i] = vSrc[uSrcOffset + i]; + + // Debug.Assert((uDstOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + // Debug.Assert((uSrcOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + // int iDstOffset = (int)uDstOffset; + // int iSrcOffset = (int)uSrcOffset; + // for(int i = 0; i < (int)NbBlockSizeInQW; ++i) + // vDst[iDstOffset + i] = vSrc[iSrcOffset + i]; + +#if KeePassUAP + Array.Copy(vSrc, (int)uSrcOffset, vDst, (int)uDstOffset, + (int)NbBlockSizeInQW); +#else + Array.Copy(vSrc, (long)uSrcOffset, vDst, (long)uDstOffset, + (long)NbBlockSizeInQW); +#endif + } + + private static void XorBlock(ulong[] vDst, ulong uDstOffset, ulong[] vSrc, + ulong uSrcOffset) + { + // for(ulong i = 0; i < NbBlockSizeInQW; ++i) + // vDst[uDstOffset + i] ^= vSrc[uSrcOffset + i]; + + Debug.Assert((uDstOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + Debug.Assert((uSrcOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + int iDstOffset = (int)uDstOffset; + int iSrcOffset = (int)uSrcOffset; + for(int i = 0; i < (int)NbBlockSizeInQW; ++i) + vDst[iDstOffset + i] ^= vSrc[iSrcOffset + i]; + } + + private static void Blake2bLong(byte[] pbOut, int cbOut, + byte[] pbIn, int cbIn, Blake2b h) + { + Debug.Assert((h != null) && (h.HashSize == (64 * 8))); + + byte[] pbOutLen = new byte[4]; + MemUtil.UInt32ToBytesEx((uint)cbOut, pbOutLen, 0); + + if(cbOut <= 64) + { + Blake2b hOut = ((cbOut == 64) ? h : new Blake2b(cbOut)); + if(cbOut == 64) hOut.Initialize(); + + hOut.TransformBlock(pbOutLen, 0, pbOutLen.Length, pbOutLen, 0); + hOut.TransformBlock(pbIn, 0, cbIn, pbIn, 0); + hOut.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + Array.Copy(hOut.Hash, pbOut, cbOut); + + if(cbOut < 64) hOut.Clear(); + return; + } + + h.Initialize(); + h.TransformBlock(pbOutLen, 0, pbOutLen.Length, pbOutLen, 0); + h.TransformBlock(pbIn, 0, cbIn, pbIn, 0); + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + byte[] pbOutBuffer = new byte[64]; + Array.Copy(h.Hash, pbOutBuffer, pbOutBuffer.Length); + + int ibOut = 64 / 2; + Array.Copy(pbOutBuffer, pbOut, ibOut); + int cbToProduce = cbOut - ibOut; + + h.Initialize(); + while(cbToProduce > 64) + { + byte[] pbHash = h.ComputeHash(pbOutBuffer); + Array.Copy(pbHash, pbOutBuffer, 64); + + Array.Copy(pbHash, 0, pbOut, ibOut, 64 / 2); + ibOut += 64 / 2; + cbToProduce -= 64 / 2; + + MemUtil.ZeroByteArray(pbHash); + } + + using(Blake2b hOut = new Blake2b(cbToProduce)) + { + byte[] pbHash = hOut.ComputeHash(pbOutBuffer); + Array.Copy(pbHash, 0, pbOut, ibOut, cbToProduce); + + MemUtil.ZeroByteArray(pbHash); + } + + MemUtil.ZeroByteArray(pbOutBuffer); + } + +#if !ARGON2_G_INLINED + private static ulong BlaMka(ulong x, ulong y) + { + ulong xy = (x & 0xFFFFFFFFUL) * (y & 0xFFFFFFFFUL); + return (x + y + (xy << 1)); + } + + private static void G(ulong[] v, int a, int b, int c, int d) + { + ulong va = v[a], vb = v[b], vc = v[c], vd = v[d]; + + va = BlaMka(va, vb); + vd = MemUtil.RotateRight64(vd ^ va, 32); + vc = BlaMka(vc, vd); + vb = MemUtil.RotateRight64(vb ^ vc, 24); + va = BlaMka(va, vb); + vd = MemUtil.RotateRight64(vd ^ va, 16); + vc = BlaMka(vc, vd); + vb = MemUtil.RotateRight64(vb ^ vc, 63); + + v[a] = va; + v[b] = vb; + v[c] = vc; + v[d] = vd; + } +#else + private static void G(ulong[] v, int a, int b, int c, int d) + { + ulong va = v[a], vb = v[b], vc = v[c], vd = v[d]; + + ulong xy = (va & 0xFFFFFFFFUL) * (vb & 0xFFFFFFFFUL); + va += vb + (xy << 1); + + vd = MemUtil.RotateRight64(vd ^ va, 32); + + xy = (vc & 0xFFFFFFFFUL) * (vd & 0xFFFFFFFFUL); + vc += vd + (xy << 1); + + vb = MemUtil.RotateRight64(vb ^ vc, 24); + + xy = (va & 0xFFFFFFFFUL) * (vb & 0xFFFFFFFFUL); + va += vb + (xy << 1); + + vd = MemUtil.RotateRight64(vd ^ va, 16); + + xy = (vc & 0xFFFFFFFFUL) * (vd & 0xFFFFFFFFUL); + vc += vd + (xy << 1); + + vb = MemUtil.RotateRight64(vb ^ vc, 63); + + v[a] = va; + v[b] = vb; + v[c] = vc; + v[d] = vd; + } +#endif + +#if ARGON2_B2ROUND_ARRAYS + private static void Blake2RoundNoMsg(ulong[] pbR, int[] v) + { + G(pbR, v[0], v[4], v[8], v[12]); + G(pbR, v[1], v[5], v[9], v[13]); + G(pbR, v[2], v[6], v[10], v[14]); + G(pbR, v[3], v[7], v[11], v[15]); + G(pbR, v[0], v[5], v[10], v[15]); + G(pbR, v[1], v[6], v[11], v[12]); + G(pbR, v[2], v[7], v[8], v[13]); + G(pbR, v[3], v[4], v[9], v[14]); + } +#else + private static void Blake2RoundNoMsgCols16i(ulong[] pbR, int i) + { + G(pbR, i, i + 4, i + 8, i + 12); + G(pbR, i + 1, i + 5, i + 9, i + 13); + G(pbR, i + 2, i + 6, i + 10, i + 14); + G(pbR, i + 3, i + 7, i + 11, i + 15); + G(pbR, i, i + 5, i + 10, i + 15); + G(pbR, i + 1, i + 6, i + 11, i + 12); + G(pbR, i + 2, i + 7, i + 8, i + 13); + G(pbR, i + 3, i + 4, i + 9, i + 14); + } + + private static void Blake2RoundNoMsgRows2i(ulong[] pbR, int i) + { + G(pbR, i, i + 32, i + 64, i + 96); + G(pbR, i + 1, i + 33, i + 65, i + 97); + G(pbR, i + 16, i + 48, i + 80, i + 112); + G(pbR, i + 17, i + 49, i + 81, i + 113); + G(pbR, i, i + 33, i + 80, i + 113); + G(pbR, i + 1, i + 48, i + 81, i + 96); + G(pbR, i + 16, i + 49, i + 64, i + 97); + G(pbR, i + 17, i + 32, i + 65, i + 112); + } +#endif + + private static void FillFirstBlocks(Argon2Ctx ctx, byte[] pbBlockHash, + Blake2b h) + { + byte[] pbBlock = new byte[NbBlockSize]; + + for(ulong l = 0; l < ctx.Lanes; ++l) + { + MemUtil.UInt32ToBytesEx(0, pbBlockHash, NbPreHashDigestLength); + MemUtil.UInt32ToBytesEx((uint)l, pbBlockHash, NbPreHashDigestLength + 4); + + Blake2bLong(pbBlock, (int)NbBlockSize, pbBlockHash, + NbPreHashSeedLength, h); + LoadBlock(ctx.Mem, l * ctx.LaneLength * NbBlockSizeInQW, pbBlock); + + MemUtil.UInt32ToBytesEx(1, pbBlockHash, NbPreHashDigestLength); + + Blake2bLong(pbBlock, (int)NbBlockSize, pbBlockHash, + NbPreHashSeedLength, h); + LoadBlock(ctx.Mem, (l * ctx.LaneLength + 1UL) * NbBlockSizeInQW, pbBlock); + } + + MemUtil.ZeroByteArray(pbBlock); + } + + private static ulong IndexAlpha(Argon2Ctx ctx, Argon2ThreadInfo ti, + uint uPseudoRand, bool bSameLane) + { + ulong uRefAreaSize; + if(ti.Pass == 0) + { + if(ti.Slice == 0) + { + Debug.Assert(ti.Index > 0); + uRefAreaSize = ti.Index - 1UL; + } + else + { + if(bSameLane) + uRefAreaSize = ti.Slice * ctx.SegmentLength + + ti.Index - 1UL; + else + uRefAreaSize = ti.Slice * ctx.SegmentLength - + ((ti.Index == 0UL) ? 1UL : 0UL); + } + } + else + { + if(bSameLane) + uRefAreaSize = ctx.LaneLength - ctx.SegmentLength + + ti.Index - 1UL; + else + uRefAreaSize = ctx.LaneLength - ctx.SegmentLength - + ((ti.Index == 0) ? 1UL : 0UL); + } + Debug.Assert(uRefAreaSize <= (ulong)uint.MaxValue); + + ulong uRelPos = uPseudoRand; + uRelPos = (uRelPos * uRelPos) >> 32; + uRelPos = uRefAreaSize - 1UL - ((uRefAreaSize * uRelPos) >> 32); + + ulong uStart = 0; + if(ti.Pass != 0) + uStart = (((ti.Slice + 1UL) == NbSyncPoints) ? 0UL : + ((ti.Slice + 1UL) * ctx.SegmentLength)); + Debug.Assert(uStart <= (ulong)uint.MaxValue); + + Debug.Assert(ctx.LaneLength <= (ulong)uint.MaxValue); + return ((uStart + uRelPos) % ctx.LaneLength); + } + + private static void FillMemoryBlocks(Argon2Ctx ctx) + { + int np = (int)ctx.Lanes; + Argon2ThreadInfo[] v = new Argon2ThreadInfo[np]; + + for(ulong r = 0; r < ctx.TCost; ++r) + { + for(ulong s = 0; s < NbSyncPoints; ++s) + { + for(int l = 0; l < np; ++l) + { + Argon2ThreadInfo ti = new Argon2ThreadInfo(); + ti.Context = ctx; + + ti.Pass = r; + ti.Lane = (ulong)l; + ti.Slice = s; + + if(!ThreadPool.QueueUserWorkItem(FillSegmentThr, ti)) + { + Debug.Assert(false); + throw new OutOfMemoryException(); + } + + v[l] = ti; + } + + for(int l = 0; l < np; ++l) + { + v[l].Finished.WaitOne(); + v[l].Release(); + } + } + } + } + + private static void FillSegmentThr(object o) + { + Argon2ThreadInfo ti = (o as Argon2ThreadInfo); + if (ti == null) { Debug.Assert(false); return; } + + try + { + Argon2Ctx ctx = ti.Context; + if (ctx == null) { Debug.Assert(false); return; } + + Debug.Assert(ctx.Version >= MinVersion); + bool bCanXor = (ctx.Version >= 0x13U); + + ulong[] pbR = new ulong[NbBlockSizeInQW]; + ulong[] pbTmp = new ulong[NbBlockSizeInQW]; + ulong[] pbAddrInputZero = null; + + bool bDataIndependentAddr = ((ctx.Type == Argon2Type.ID) && + (ti.Pass == 0) && (ti.Slice < (NbSyncPoints / 2))); + if (bDataIndependentAddr) + { + pbAddrInputZero = new ulong[NbBlockSizeInQW * 3]; + + const int iInput = (int)NbBlockSizeInQW; + pbAddrInputZero[iInput] = ti.Pass; + pbAddrInputZero[iInput + 1] = ti.Lane; + pbAddrInputZero[iInput + 2] = ti.Slice; + pbAddrInputZero[iInput + 3] = ctx.MemoryBlocks; + pbAddrInputZero[iInput + 4] = ctx.TCost; + pbAddrInputZero[iInput + 5] = (ulong)ctx.Type; + } + + ulong uStart = 0; + if ((ti.Pass == 0) && (ti.Slice == 0)) + { + uStart = 2; + + if (bDataIndependentAddr) + NextAddresses(pbAddrInputZero, pbR, pbTmp); + } + + ulong uCur = (ti.Lane * ctx.LaneLength) + (ti.Slice * + ctx.SegmentLength) + uStart; + + ulong uPrev = (((uCur % ctx.LaneLength) == 0) ? + (uCur + ctx.LaneLength - 1UL) : (uCur - 1UL)); + + for (ulong i = uStart; i < ctx.SegmentLength; ++i) + { + if ((uCur % ctx.LaneLength) == 1) + uPrev = uCur - 1UL; + + ulong uPseudoRand; + if (bDataIndependentAddr) + { + ulong iMod = i % NbAddressesInBlock; + if (iMod == 0) + NextAddresses(pbAddrInputZero, pbR, pbTmp); + uPseudoRand = pbAddrInputZero[iMod]; + } + else uPseudoRand = ctx.Mem[uPrev * NbBlockSizeInQW]; + + ulong uRefLane = (uPseudoRand >> 32) % ctx.Lanes; + if ((ti.Pass == 0) && (ti.Slice == 0)) + uRefLane = ti.Lane; + + ti.Index = i; + ulong uRefIndex = IndexAlpha(ctx, ti, (uint)uPseudoRand, + (uRefLane == ti.Lane)); + + ulong uRefBlockIndex = (ctx.LaneLength * uRefLane + + uRefIndex) * NbBlockSizeInQW; + ulong uCurBlockIndex = uCur * NbBlockSizeInQW; + + FillBlock(ctx.Mem, uPrev * NbBlockSizeInQW, uRefBlockIndex, + uCurBlockIndex, ((ti.Pass != 0) && bCanXor), pbR, pbTmp); + + ++uCur; + ++uPrev; + } + + MemUtil.ZeroArray(pbR); + MemUtil.ZeroArray(pbTmp); + if (pbAddrInputZero != null) MemUtil.ZeroArray(pbAddrInputZero); + } + catch (Exception) { Debug.Assert(false); } + + try { ti.Finished.Set(); } + catch (Exception) { Debug.Assert(false); } + } + +#if ARGON2_B2ROUND_ARRAYS + private static void InitB2RoundIndexArrays() + { + int[][] vCols = g_vFBCols; + if(vCols == null) + { + vCols = new int[8][]; + Debug.Assert(vCols.Length == 8); + int e = 0; + for(int i = 0; i < 8; ++i) + { + vCols[i] = new int[16]; + for(int j = 0; j < 16; ++j) + { + vCols[i][j] = e; + ++e; + } + } + + g_vFBCols = vCols; + } + + int[][] vRows = g_vFBRows; + if(vRows == null) + { + vRows = new int[8][]; + for(int i = 0; i < 8; ++i) + { + vRows[i] = new int[16]; + for(int j = 0; j < 16; ++j) + { + int jh = j / 2; + vRows[i][j] = (2 * i) + (16 * jh) + (j & 1); + } + } + + g_vFBRows = vRows; + } + } +#endif + + private static void FillBlock(ulong[] pMem, ulong uPrev, ulong uRef, + ulong uNext, bool bXor, ulong[] pbR, ulong[] pbTmp) + { + CopyBlock(pbR, 0, pMem, uRef); + XorBlock(pbR, 0, pMem, uPrev); + CopyBlock(pbTmp, 0, pbR, 0); + if(bXor) XorBlock(pbTmp, 0, pMem, uNext); + +#if ARGON2_B2ROUND_ARRAYS + int[][] vCols = g_vFBCols; + int[][] vRows = g_vFBRows; + for(int i = 0; i < 8; ++i) + Blake2RoundNoMsg(pbR, vCols[i]); + for(int i = 0; i < 8; ++i) + Blake2RoundNoMsg(pbR, vRows[i]); +#else + for(int i = 0; i < (8 * 16); i += 16) + Blake2RoundNoMsgCols16i(pbR, i); + for(int i = 0; i < (8 * 2); i += 2) + Blake2RoundNoMsgRows2i(pbR, i); +#endif + + CopyBlock(pMem, uNext, pbTmp, 0); + XorBlock(pMem, uNext, pbR, 0); + } + + private static void NextAddresses(ulong[] pbAddrInputZero, ulong[] pbR, + ulong[] pbTmp) + { + // pbAddrInputZero contains an address block, an input block and a zero block + const ulong uAddr = 0; + const ulong uInput = NbBlockSizeInQW; + const ulong uZero = NbBlockSizeInQW * 2; + + ++pbAddrInputZero[uInput + 6]; + FillBlock(pbAddrInputZero, uZero, uInput, uAddr, false, pbR, pbTmp); + FillBlock(pbAddrInputZero, uZero, uAddr, uAddr, false, pbR, pbTmp); + } + + private static byte[] FinalHash(Argon2Ctx ctx, int cbOut, Blake2b h) + { + ulong[] pqBlockHash = new ulong[NbBlockSizeInQW]; + CopyBlock(pqBlockHash, 0, ctx.Mem, (ctx.LaneLength - 1UL) * + NbBlockSizeInQW); + for(ulong l = 1; l < ctx.Lanes; ++l) + XorBlock(pqBlockHash, 0, ctx.Mem, (l * ctx.LaneLength + + ctx.LaneLength - 1UL) * NbBlockSizeInQW); + + byte[] pbBlockHashBytes = new byte[NbBlockSize]; + StoreBlock(pbBlockHashBytes, pqBlockHash); + + byte[] pbOut = new byte[cbOut]; + Blake2bLong(pbOut, cbOut, pbBlockHashBytes, (int)NbBlockSize, h); + + MemUtil.ZeroArray(pqBlockHash); + MemUtil.ZeroByteArray(pbBlockHashBytes); + return pbOut; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/Argon2Kdf.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/Argon2Kdf.cs new file mode 100644 index 00000000..7b0d1183 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/Argon2Kdf.cs @@ -0,0 +1,226 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public enum Argon2Type + { + // The values must be the same as in the Argon2 specification + D = 0, + ID = 2 + } + public sealed partial class Argon2Kdf : KdfEngine + { + + + private static readonly PwUuid g_uuidD = new PwUuid(new byte[] { + 0xEF, 0x63, 0x6D, 0xDF, 0x8C, 0x29, 0x44, 0x4B, + 0x91, 0xF7, 0xA9, 0xA4, 0x03, 0xE3, 0x0A, 0x0C }); + private static readonly PwUuid g_uuidID = new PwUuid(new byte[] { + 0x9E, 0x29, 0x8B, 0x19, 0x56, 0xDB, 0x47, 0x73, + 0xB2, 0x3D, 0xFC, 0x3E, 0xC6, 0xF0, 0xA1, 0xE6 }); + + public const string ParamSalt = "S"; // Byte[] + public const string ParamParallelism = "P"; // UInt32 + public const string ParamMemory = "M"; // UInt64 + public const string ParamIterations = "I"; // UInt64 + public const string ParamVersion = "V"; // UInt32 + public const string ParamSecretKey = "K"; // Byte[] + public const string ParamAssocData = "A"; // Byte[] + + private const uint MinVersion = 0x10; + private const uint MaxVersion = 0x13; + + private const int MinSalt = 8; + 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 = int.MaxValue; // .NET limit + + internal const uint MinParallelism = 1; + internal const uint MaxParallelism = (1 << 24) - 1; + + internal const ulong DefaultIterations = 2; + internal const ulong DefaultMemory = 64 * 1024 * 1024; // 64 MB + internal const uint DefaultParallelism = 2; + + private readonly Argon2Type m_t; + + public override PwUuid Uuid + { + get { return ((m_t == Argon2Type.D) ? g_uuidD : g_uuidID); } + } + + public override string Name + { + get { return ((m_t == Argon2Type.D) ? "Argon2d" : "Argon2id"); } + } + + public Argon2Kdf() : this(Argon2Type.D) + { + } + + public Argon2Kdf(Argon2Type t) + { + if ((t != Argon2Type.D) && (t != Argon2Type.ID)) + throw new NotSupportedException(); + + m_t = t; + } + public override byte[] GetSeed(KdfParameters p) + { return p.GetByteArray(ParamSalt); } + + public override KdfParameters GetDefaultParameters() + { + KdfParameters p = base.GetDefaultParameters(); + + p.SetUInt32(ParamVersion, MaxVersion); + + p.SetUInt64(ParamIterations, DefaultIterations); + p.SetUInt64(ParamMemory, DefaultMemory); + p.SetUInt32(ParamParallelism, DefaultParallelism); + + return p; + } + + public override void Randomize(KdfParameters p) + { + if(p == null) { Debug.Assert(false); return; } + Debug.Assert(p.KdfUuid.Equals(this.Uuid)); + + byte[] pb = CryptoRandom.Instance.GetRandomBytes(32); + p.SetByteArray(ParamSalt, pb); + } + + public override byte[] Transform(byte[] pbMsg, KdfParameters p) + { + if(pbMsg == null) throw new ArgumentNullException("pbMsg"); + if(p == null) throw new ArgumentNullException("p"); + + byte[] pbSalt = p.GetByteArray(ParamSalt); + if(pbSalt == null) + throw new ArgumentNullException("p.Salt"); + if((pbSalt.Length < MinSalt) || (pbSalt.Length > MaxSalt)) + throw new ArgumentOutOfRangeException("p.Salt"); + + uint uPar = p.GetUInt32(ParamParallelism, 0); + if((uPar < MinParallelism) || (uPar > MaxParallelism)) + throw new ArgumentOutOfRangeException("p.Parallelism"); + + ulong uMem = p.GetUInt64(ParamMemory, 0); + if((uMem < MinMemory) || (uMem > MaxMemory)) + throw new ArgumentOutOfRangeException("p.Memory"); + + ulong uIt = p.GetUInt64(ParamIterations, 0); + if((uIt < MinIterations) || (uIt > MaxIterations)) + throw new ArgumentOutOfRangeException("p.Iterations"); + + uint v = p.GetUInt32(ParamVersion, 0); + if((v < MinVersion) || (v > MaxVersion)) + throw new ArgumentOutOfRangeException("p.Version"); + + byte[] pbSecretKey = p.GetByteArray(ParamSecretKey); + byte[] pbAssocData = p.GetByteArray(ParamAssocData); + + byte[] pbRet; + + if (m_t == Argon2Type.ID) + { + pbRet = Argon2Transform(pbMsg, pbSalt, uPar, uMem, + uIt, 32, v, pbSecretKey, pbAssocData); + } + else + { + if (pbSecretKey != null) + { + throw new ArgumentOutOfRangeException("Unsupported configuration: non-null pbSecretKey"); + } + + if (pbAssocData != null) + { + throw new ArgumentOutOfRangeException("Unsupported configuration: non-null pbAssocData"); + } + + /* + byte[] pbRet = Argon2d(pbMsg, pbSalt, uPar, uMem, uIt, + 32, v, pbSecretKey, pbAssocData); + */ + + IntPtr msgPtr = Marshal.AllocHGlobal(pbMsg.Length); + IntPtr saltPtr = Marshal.AllocHGlobal(pbSalt.Length); + IntPtr retPtr = Marshal.AllocHGlobal(32); + Marshal.Copy(pbMsg, 0, msgPtr, pbMsg.Length); + Marshal.Copy(pbSalt, 0, saltPtr, pbSalt.Length); + + const UInt32 Argon2_d = 0; + + int ret = argon2_hash( + (UInt32)uIt, (UInt32)(uMem / 1024), uPar, + msgPtr, (IntPtr)pbMsg.Length, + saltPtr, (IntPtr)pbSalt.Length, + retPtr, (IntPtr)32, + (IntPtr)0, (IntPtr)0, Argon2_d, v); + + if (ret != 0) + { + throw new Exception("argon2_hash failed with " + ret); + } + + pbRet = new byte[32]; + Marshal.Copy(retPtr, pbRet, 0, 32); + + Marshal.FreeHGlobal(msgPtr); + Marshal.FreeHGlobal(saltPtr); + Marshal.FreeHGlobal(retPtr); + } + + if(uMem > (100UL * 1024UL * 1024UL)) GC.Collect(); + return pbRet; + } + + public override KdfParameters GetBestParameters(uint uMilliseconds) + { + KdfParameters p = GetDefaultParameters(); + Randomize(p); + + MaximizeParamUInt64(p, ParamIterations, MinIterations, + MaxIterations, uMilliseconds, true); + return p; + } + + [DllImport("argon2")] + static extern int argon2_hash( + UInt32 t_cost, UInt32 m_cost, UInt32 parallelism, + IntPtr pwd, IntPtr pwdlen, + IntPtr salt, IntPtr saltlen, + IntPtr hash, IntPtr hashlen, + IntPtr encoded, IntPtr encodedlen, + UInt32 type, UInt32 version); + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/KdfEngine.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/KdfEngine.cs new file mode 100644 index 00000000..7e4f5b5c --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/KdfEngine.cs @@ -0,0 +1,144 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public abstract class KdfEngine + { + public abstract PwUuid Uuid + { + get; + } + + public abstract string Name + { + get; + } + + public abstract byte[] GetSeed(KdfParameters p); + + public virtual KdfParameters GetDefaultParameters() + { + return new KdfParameters(this.Uuid); + } + + /// + /// Generate random seeds and store them in . + /// + public virtual void Randomize(KdfParameters p) + { + Debug.Assert(p != null); + Debug.Assert(p.KdfUuid.Equals(this.Uuid)); + } + + public abstract byte[] Transform(byte[] pbMsg, KdfParameters p); + + public virtual KdfParameters GetBestParameters(uint uMilliseconds) + { + throw new NotImplementedException(); + } + + protected void MaximizeParamUInt64(KdfParameters p, string strName, + ulong uMin, ulong uMax, uint uMilliseconds, bool bInterpSearch) + { + if(p == null) { Debug.Assert(false); return; } + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; } + if(uMin > uMax) { Debug.Assert(false); return; } + + if(uMax > (ulong.MaxValue >> 1)) + { + Debug.Assert(false); + uMax = ulong.MaxValue >> 1; + + if(uMin > uMax) { p.SetUInt64(strName, uMin); return; } + } + + byte[] pbMsg = new byte[32]; + for(int i = 0; i < pbMsg.Length; ++i) pbMsg[i] = (byte)i; + + ulong uLow = uMin; + ulong uHigh = uMin + 1UL; + long tLow = 0; + long tHigh = 0; + long tTarget = (long)uMilliseconds; + + // Determine range + while(uHigh <= uMax) + { + p.SetUInt64(strName, uHigh); + + // GC.Collect(); + Stopwatch sw = Stopwatch.StartNew(); + Transform(pbMsg, p); + sw.Stop(); + + tHigh = sw.ElapsedMilliseconds; + if(tHigh > tTarget) break; + + uLow = uHigh; + tLow = tHigh; + uHigh <<= 1; + } + if(uHigh > uMax) { uHigh = uMax; tHigh = 0; } + if(uLow > uHigh) uLow = uHigh; // Skips to end + + // Find optimal number of iterations + while((uHigh - uLow) >= 2UL) + { + ulong u = (uHigh + uLow) >> 1; // Binary search + // Interpolation search, if possible + if(bInterpSearch && (tLow > 0) && (tHigh > tTarget) && + (tLow <= tTarget)) + { + u = uLow + (((uHigh - uLow) * (ulong)(tTarget - tLow)) / + (ulong)(tHigh - tLow)); + if((u >= uLow) && (u <= uHigh)) + { + u = Math.Max(u, uLow + 1UL); + u = Math.Min(u, uHigh - 1UL); + } + else + { + Debug.Assert(false); + u = (uHigh + uLow) >> 1; + } + } + + p.SetUInt64(strName, u); + + // GC.Collect(); + Stopwatch sw = Stopwatch.StartNew(); + Transform(pbMsg, p); + sw.Stop(); + + long t = sw.ElapsedMilliseconds; + if(t == tTarget) { uLow = u; break; } + else if(t > tTarget) { uHigh = u; tHigh = t; } + else { uLow = u; tLow = t; } + } + + p.SetUInt64(strName, uLow); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/KdfParameters.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/KdfParameters.cs new file mode 100644 index 00000000..600c1f5c --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/KdfParameters.cs @@ -0,0 +1,80 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2020 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +using KeePassLib.Collections; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public sealed class KdfParameters : VariantDictionary + { + private const string ParamUuid = @"$UUID"; + + private readonly PwUuid m_puKdf; + public PwUuid KdfUuid + { + get { return m_puKdf; } + } + + public KdfParameters(PwUuid puKdf) + { + if(puKdf == null) throw new ArgumentNullException("puKdf"); + + m_puKdf = puKdf; + SetByteArray(ParamUuid, puKdf.UuidBytes); + } + + /// + /// Unsupported. + /// + public override object Clone() + { + throw new NotSupportedException(); + } + + public static byte[] SerializeExt(KdfParameters p) + { + return VariantDictionary.Serialize(p); + } + + public static KdfParameters DeserializeExt(byte[] pb) + { + VariantDictionary d = VariantDictionary.Deserialize(pb); + if(d == null) { Debug.Assert(false); return null; } + + byte[] pbUuid = d.GetByteArray(ParamUuid); + if((pbUuid == null) || (pbUuid.Length != (int)PwUuid.UuidSize)) + { + Debug.Assert(false); + return null; + } + + PwUuid pu = new PwUuid(pbUuid); + KdfParameters p = new KdfParameters(pu); + d.CopyTo(p); + return p; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/KdfPool.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/KdfPool.cs new file mode 100644 index 00000000..8f8bfa4a --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/KeyDerivation/KdfPool.cs @@ -0,0 +1,97 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2020 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public static class KdfPool + { + private static List g_l = new List(); + + public static IEnumerable Engines + { + get + { + EnsureInitialized(); + return g_l; + } + } + + private static void EnsureInitialized() + { + if(g_l.Count != 0) return; + + g_l.Add(new AesKdf()); + g_l.Add(new Argon2Kdf(Argon2Type.D)); + g_l.Add(new Argon2Kdf(Argon2Type.ID)); + } + + internal static KdfParameters GetDefaultParameters() + { + EnsureInitialized(); + return g_l[0].GetDefaultParameters(); + } + + public static KdfEngine Get(PwUuid pu) + { + if(pu == null) { Debug.Assert(false); return null; } + + EnsureInitialized(); + + foreach(KdfEngine kdf in g_l) + { + if(pu.Equals(kdf.Uuid)) return kdf; + } + + return null; + } + + public static KdfEngine Get(string strName) + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; } + + EnsureInitialized(); + + foreach(KdfEngine kdf in g_l) + { + if(strName.Equals(kdf.Name, StrUtil.CaseIgnoreCmp)) return kdf; + } + + return null; + } + + public static void Add(KdfEngine kdf) + { + if(kdf == null) { Debug.Assert(false); return; } + + EnsureInitialized(); + + if(Get(kdf.Uuid) != null) { Debug.Assert(false); return; } + if(Get(kdf.Name) != null) { Debug.Assert(false); return; } + + g_l.Add(kdf); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs new file mode 100644 index 00000000..f7cec856 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs @@ -0,0 +1,65 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.PasswordGenerator +{ + internal static class CharSetBasedGenerator + { + internal static PwgError Generate(out ProtectedString psOut, + PwProfile pwProfile, CryptoRandomStream crsRandomSource) + { + psOut = ProtectedString.Empty; + if(pwProfile.Length == 0) return PwgError.Success; + + PwCharSet pcs = new PwCharSet(pwProfile.CharSet.ToString()); + char[] vGenerated = new char[pwProfile.Length]; + + PwGenerator.PrepareCharSet(pcs, pwProfile); + + for(int nIndex = 0; nIndex < (int)pwProfile.Length; ++nIndex) + { + char ch = PwGenerator.GenerateCharacter(pwProfile, pcs, + crsRandomSource); + + if(ch == char.MinValue) + { + MemUtil.ZeroArray(vGenerated); + return PwgError.TooFewCharacters; + } + + vGenerated[nIndex] = ch; + } + + byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vGenerated); + psOut = new ProtectedString(true, pbUtf8); + MemUtil.ZeroByteArray(pbUtf8); + MemUtil.ZeroArray(vGenerated); + + return PwgError.Success; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/CustomPwGenerator.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/CustomPwGenerator.cs new file mode 100644 index 00000000..943bcf47 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/CustomPwGenerator.cs @@ -0,0 +1,66 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +using KeePassLib; +using KeePassLib.Security; + +namespace KeePassLib.Cryptography.PasswordGenerator +{ + public abstract class CustomPwGenerator + { + /// + /// Each custom password generation algorithm must have + /// its own unique UUID. + /// + public abstract PwUuid Uuid { get; } + + /// + /// Displayable name of the password generation algorithm. + /// + public abstract string Name { get; } + + public virtual bool SupportsOptions + { + get { return false; } + } + + /// + /// Password generation function. + /// + /// Password generation options chosen + /// by the user. This may be null, if the default + /// options should be used. + /// Source that the algorithm + /// can use to generate random numbers. + /// Generated password or null in case + /// of failure. If returning null, the caller assumes + /// that an error message has already been shown to the user. + public abstract ProtectedString Generate(PwProfile prf, + CryptoRandomStream crsRandomSource); + + public virtual string GetOptions(string strCurrentOptions) + { + return string.Empty; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs new file mode 100644 index 00000000..b0ae5d14 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs @@ -0,0 +1,110 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace KeePassLib.Cryptography.PasswordGenerator +{ + public sealed class CustomPwGeneratorPool : IEnumerable + { + private List m_vGens = new List(); + + public int Count + { + get { return m_vGens.Count; } + } + + public CustomPwGeneratorPool() + { + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_vGens.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return m_vGens.GetEnumerator(); + } + + public void Add(CustomPwGenerator pwg) + { + if(pwg == null) throw new ArgumentNullException("pwg"); + + PwUuid uuid = pwg.Uuid; + if(uuid == null) throw new ArgumentException(); + + int nIndex = FindIndex(uuid); + + if(nIndex >= 0) m_vGens[nIndex] = pwg; // Replace + else m_vGens.Add(pwg); + } + + public CustomPwGenerator Find(PwUuid uuid) + { + if(uuid == null) throw new ArgumentNullException("uuid"); + + foreach(CustomPwGenerator pwg in m_vGens) + { + if(uuid.Equals(pwg.Uuid)) return pwg; + } + + return null; + } + + public CustomPwGenerator Find(string strName) + { + if(strName == null) throw new ArgumentNullException("strName"); + + foreach(CustomPwGenerator pwg in m_vGens) + { + if(pwg.Name == strName) return pwg; + } + + return null; + } + + private int FindIndex(PwUuid uuid) + { + if(uuid == null) throw new ArgumentNullException("uuid"); + + for(int i = 0; i < m_vGens.Count; ++i) + { + if(uuid.Equals(m_vGens[i].Uuid)) return i; + } + + return -1; + } + + public bool Remove(PwUuid uuid) + { + if(uuid == null) throw new ArgumentNullException("uuid"); + + int nIndex = FindIndex(uuid); + if(nIndex < 0) return false; + + m_vGens.RemoveAt(nIndex); + return true; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/PatternBasedGenerator.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/PatternBasedGenerator.cs new file mode 100644 index 00000000..bb7e7302 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/PatternBasedGenerator.cs @@ -0,0 +1,173 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.PasswordGenerator +{ + internal static class PatternBasedGenerator + { + internal static PwgError Generate(out ProtectedString psOut, + PwProfile pwProfile, CryptoRandomStream crsRandomSource) + { + psOut = ProtectedString.Empty; + LinkedList vGenerated = new LinkedList(); + PwCharSet pcsCurrent = new PwCharSet(); + PwCharSet pcsCustom = new PwCharSet(); + PwCharSet pcsUsed = new PwCharSet(); + bool bInCharSetDef = false; + + 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) + { + pcsCurrent.Clear(); + + bool bGenerateChar = false; + + if(ch == '\\') + { + ch = csStream.ReadChar(); + if(ch == char.MinValue) // Backslash at the end + { + vGenerated.AddLast('\\'); + break; + } + + if(bInCharSetDef) pcsCustom.Add(ch); + else + { + vGenerated.AddLast(ch); + pcsUsed.Add(ch); + } + } + else if(ch == '[') + { + pcsCustom.Clear(); + bInCharSetDef = true; + } + else if(ch == ']') + { + pcsCurrent.Add(pcsCustom.ToString()); + + bInCharSetDef = false; + bGenerateChar = true; + } + else if(bInCharSetDef) + { + if(pcsCustom.AddCharSet(ch) == false) + pcsCustom.Add(ch); + } + else if(pcsCurrent.AddCharSet(ch) == false) + { + vGenerated.AddLast(ch); + pcsUsed.Add(ch); + } + else bGenerateChar = true; + + if(bGenerateChar) + { + PwGenerator.PrepareCharSet(pcsCurrent, pwProfile); + + if(pwProfile.NoRepeatingCharacters) + pcsCurrent.Remove(pcsUsed.ToString()); + + char chGen = PwGenerator.GenerateCharacter(pwProfile, + pcsCurrent, crsRandomSource); + + if(chGen == char.MinValue) return PwgError.TooFewCharacters; + + vGenerated.AddLast(chGen); + pcsUsed.Add(chGen); + } + + ch = csStream.ReadChar(); + } + + if(vGenerated.Count == 0) return PwgError.Success; + + char[] vArray = new char[vGenerated.Count]; + vGenerated.CopyTo(vArray, 0); + + if(pwProfile.PatternPermutePassword) + PwGenerator.ShufflePassword(vArray, crsRandomSource); + + byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vArray); + psOut = new ProtectedString(true, pbUtf8); + MemUtil.ZeroByteArray(pbUtf8); + MemUtil.ZeroArray(vArray); + vGenerated.Clear(); + + return PwgError.Success; + } + + private static string ExpandPattern(string strPattern) + { + Debug.Assert(strPattern != null); if(strPattern == null) return string.Empty; + string str = strPattern; + + while(true) + { + int nOpen = FindFirstUnescapedChar(str, '{'); + int nClose = FindFirstUnescapedChar(str, '}'); + + if((nOpen >= 0) && (nOpen < nClose)) + { + string strCount = str.Substring(nOpen + 1, nClose - nOpen - 1); + str = str.Remove(nOpen, nClose - nOpen + 1); + + 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)); + } + } + else break; + } + + return str; + } + + private static int FindFirstUnescapedChar(string str, char ch) + { + for(int i = 0; i < str.Length; ++i) + { + char chCur = str[i]; + + if(chCur == '\\') ++i; // Next is escaped, skip it + else if(chCur == ch) return i; + } + + return -1; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/PwCharSet.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/PwCharSet.cs new file mode 100644 index 00000000..97f34248 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/PwCharSet.cs @@ -0,0 +1,351 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace KeePassLib.Cryptography.PasswordGenerator +{ + public sealed class PwCharSet + { + public const string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + public const string LowerCase = "abcdefghijklmnopqrstuvwxyz"; + public const string Digits = "0123456789"; + + public const string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ"; + public const string LowerConsonants = "bcdfghjklmnpqrstvwxyz"; + public const string UpperVowels = "AEIOU"; + public const string LowerVowels = "aeiou"; + + public const string Punctuation = @",.;:"; + public const string Brackets = @"[]{}()<>"; + + public const string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; + + public const string UpperHex = "0123456789ABCDEF"; + public const string LowerHex = "0123456789abcdef"; + + public const string Invalid = "\t\r\n"; + public const string LookAlike = @"O0l1I|"; + + internal const string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits; + + private const int CharTabSize = (0x10000 / 8); + + private List m_vChars = new List(); + private byte[] m_vTab = new byte[CharTabSize]; + + private static string m_strHighAnsi = null; + public static string HighAnsiChars + { + get + { + if(m_strHighAnsi == null) { new PwCharSet(); } // Create string + Debug.Assert(m_strHighAnsi != null); + return m_strHighAnsi; + } + } + + private static string m_strSpecial = null; + public static string SpecialChars + { + get + { + if(m_strSpecial == null) { new PwCharSet(); } // Create string + Debug.Assert(m_strSpecial != null); + return m_strSpecial; + } + } + + /// + /// Create a new, empty character set collection object. + /// + public PwCharSet() + { + Initialize(true); + } + + public PwCharSet(string strCharSet) + { + Initialize(true); + Add(strCharSet); + } + + private PwCharSet(bool bFullInitialize) + { + Initialize(bFullInitialize); + } + + private void Initialize(bool bFullInitialize) + { + Clear(); + + if(!bFullInitialize) return; + + if(m_strHighAnsi == null) + { + StringBuilder sbHighAnsi = new StringBuilder(); + // [U+0080, U+009F] are C1 control characters, + // U+00A0 is non-breaking space + for(char ch = '\u00A1'; ch <= '\u00AC'; ++ch) + sbHighAnsi.Append(ch); + // U+00AD is soft hyphen (format character) + for(char ch = '\u00AE'; ch < '\u00FF'; ++ch) + sbHighAnsi.Append(ch); + sbHighAnsi.Append('\u00FF'); + + m_strHighAnsi = sbHighAnsi.ToString(); + } + + if(m_strSpecial == null) + { + PwCharSet pcs = new PwCharSet(false); + pcs.AddRange('!', '/'); + pcs.AddRange(':', '@'); + pcs.AddRange('[', '`'); + pcs.Add(@"|~"); + pcs.Remove(@"-_ "); + pcs.Remove(PwCharSet.Brackets); + + m_strSpecial = pcs.ToString(); + } + } + + /// + /// Number of characters in this set. + /// + public uint Size + { + get { return (uint)m_vChars.Count; } + } + + /// + /// Get a character of the set using an index. + /// + /// Index of the character to get. + /// Character at the specified position. If the index is invalid, + /// an ArgumentOutOfRangeException is thrown. + public char this[uint uPos] + { + get + { + if(uPos >= (uint)m_vChars.Count) + throw new ArgumentOutOfRangeException("uPos"); + + return m_vChars[(int)uPos]; + } + } + + /// + /// Remove all characters from this set. + /// + public void Clear() + { + m_vChars.Clear(); + Array.Clear(m_vTab, 0, m_vTab.Length); + } + + public bool Contains(char ch) + { + return (((m_vTab[ch / 8] >> (ch % 8)) & 1) != char.MinValue); + } + + public bool Contains(string strCharacters) + { + Debug.Assert(strCharacters != null); + if(strCharacters == null) throw new ArgumentNullException("strCharacters"); + + foreach(char ch in strCharacters) + { + if(!Contains(ch)) return false; + } + + return true; + } + + /// + /// Add characters to the set. + /// + /// Character to add. + public void Add(char ch) + { + if(ch == char.MinValue) { Debug.Assert(false); return; } + + if(!Contains(ch)) + { + m_vChars.Add(ch); + m_vTab[ch / 8] |= (byte)(1 << (ch % 8)); + } + } + + /// + /// Add characters to the set. + /// + /// String containing characters to add. + public void Add(string strCharSet) + { + Debug.Assert(strCharSet != null); + if(strCharSet == null) throw new ArgumentNullException("strCharSet"); + + m_vChars.Capacity = m_vChars.Count + strCharSet.Length; + + foreach(char ch in strCharSet) + Add(ch); + } + + public void Add(string strCharSet1, string strCharSet2) + { + Add(strCharSet1); + Add(strCharSet2); + } + + public void Add(string strCharSet1, string strCharSet2, string strCharSet3) + { + Add(strCharSet1); + Add(strCharSet2); + Add(strCharSet3); + } + + public void AddRange(char chMin, char chMax) + { + m_vChars.Capacity = m_vChars.Count + (chMax - chMin) + 1; + + for(char ch = chMin; ch < chMax; ++ch) + Add(ch); + + Add(chMax); + } + + public bool AddCharSet(char chCharSetIdentifier) + { + bool bResult = true; + + switch(chCharSetIdentifier) + { + case 'a': Add(PwCharSet.LowerCase, PwCharSet.Digits); break; + case 'A': Add(PwCharSet.LowerCase, PwCharSet.UpperCase, + PwCharSet.Digits); break; + case 'U': Add(PwCharSet.UpperCase, PwCharSet.Digits); break; + case 'c': Add(PwCharSet.LowerConsonants); break; + case 'C': Add(PwCharSet.LowerConsonants, + PwCharSet.UpperConsonants); break; + case 'z': Add(PwCharSet.UpperConsonants); break; + case 'd': Add(PwCharSet.Digits); break; // Digit + case 'h': Add(PwCharSet.LowerHex); break; + case 'H': Add(PwCharSet.UpperHex); break; + case 'l': Add(PwCharSet.LowerCase); break; + case 'L': Add(PwCharSet.LowerCase, PwCharSet.UpperCase); break; + case 'u': Add(PwCharSet.UpperCase); break; + case 'p': Add(PwCharSet.Punctuation); break; + case 'b': Add(PwCharSet.Brackets); break; + case 's': Add(PwCharSet.PrintableAsciiSpecial); break; + case 'S': Add(PwCharSet.UpperCase, PwCharSet.LowerCase); + Add(PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial); break; + case 'v': Add(PwCharSet.LowerVowels); break; + case 'V': Add(PwCharSet.LowerVowels, PwCharSet.UpperVowels); break; + case 'Z': Add(PwCharSet.UpperVowels); break; + case 'x': Add(m_strHighAnsi); break; + default: bResult = false; break; + } + + return bResult; + } + + public bool Remove(char ch) + { + m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8))); + return m_vChars.Remove(ch); + } + + public bool Remove(string strCharacters) + { + Debug.Assert(strCharacters != null); + if(strCharacters == null) throw new ArgumentNullException("strCharacters"); + + bool bResult = true; + foreach(char ch in strCharacters) + { + if(!Remove(ch)) bResult = false; + } + + return bResult; + } + + public bool RemoveIfAllExist(string strCharacters) + { + Debug.Assert(strCharacters != null); + if(strCharacters == null) throw new ArgumentNullException("strCharacters"); + + if(!Contains(strCharacters)) + return false; + + return Remove(strCharacters); + } + + /// + /// Convert the character set to a string containing all its characters. + /// + /// String containing all character set characters. + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + foreach(char ch in m_vChars) + sb.Append(ch); + + return sb.ToString(); + } + + public string PackAndRemoveCharRanges() + { + StringBuilder sb = new StringBuilder(); + + sb.Append(RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_'); + sb.Append(RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_'); + sb.Append(RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_'); + sb.Append(RemoveIfAllExist(m_strSpecial) ? 'S' : '_'); + sb.Append(RemoveIfAllExist(PwCharSet.Punctuation) ? 'P' : '_'); + sb.Append(RemoveIfAllExist(@"-") ? 'm' : '_'); + sb.Append(RemoveIfAllExist(@"_") ? 'u' : '_'); + sb.Append(RemoveIfAllExist(@" ") ? 's' : '_'); + sb.Append(RemoveIfAllExist(PwCharSet.Brackets) ? 'B' : '_'); + sb.Append(RemoveIfAllExist(m_strHighAnsi) ? 'H' : '_'); + + return sb.ToString(); + } + + public void UnpackCharRanges(string strRanges) + { + if(strRanges == null) { Debug.Assert(false); return; } + if(strRanges.Length < 10) { Debug.Assert(false); return; } + + if(strRanges[0] != '_') Add(PwCharSet.UpperCase); + if(strRanges[1] != '_') Add(PwCharSet.LowerCase); + if(strRanges[2] != '_') Add(PwCharSet.Digits); + if(strRanges[3] != '_') Add(m_strSpecial); + if(strRanges[4] != '_') Add(PwCharSet.Punctuation); + if(strRanges[5] != '_') Add('-'); + if(strRanges[6] != '_') Add('_'); + if(strRanges[7] != '_') Add(' '); + if(strRanges[8] != '_') Add(PwCharSet.Brackets); + if(strRanges[9] != '_') Add(m_strHighAnsi); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/PwGenerator.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/PwGenerator.cs new file mode 100644 index 00000000..73f9284c --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/PwGenerator.cs @@ -0,0 +1,152 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Security.Cryptography; +using System.Text; + +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.PasswordGenerator +{ + public enum PwgError + { + Success = 0, + Unknown = 1, + TooFewCharacters = 2, + UnknownAlgorithm = 3 + } + + /// + /// Utility functions for generating random passwords. + /// + public static class PwGenerator + { + public static PwgError Generate(out ProtectedString psOut, + PwProfile pwProfile, byte[] pbUserEntropy, + CustomPwGeneratorPool pwAlgorithmPool) + { + Debug.Assert(pwProfile != null); + if (pwProfile == null) throw new ArgumentNullException("pwProfile"); + + CryptoRandomStream crs = CreateCryptoStream(pbUserEntropy); + PwgError e = PwgError.Unknown; + + if (pwProfile.GeneratorType == PasswordGeneratorType.CharSet) + e = CharSetBasedGenerator.Generate(out psOut, pwProfile, crs); + else if (pwProfile.GeneratorType == PasswordGeneratorType.Pattern) + e = PatternBasedGenerator.Generate(out psOut, pwProfile, crs); + else if (pwProfile.GeneratorType == PasswordGeneratorType.Custom) + e = GenerateCustom(out psOut, pwProfile, crs, pwAlgorithmPool); + else { Debug.Assert(false); psOut = ProtectedString.Empty; } + + return e; + } + + private static CryptoRandomStream CreateCryptoStream(byte[] pbAdditionalEntropy) + { + byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(128); + + // Mix in additional entropy + Debug.Assert(pbKey.Length >= 64); + if ((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0)) + { + using (SHA512Managed h = new SHA512Managed()) + { + byte[] pbHash = h.ComputeHash(pbAdditionalEntropy); + MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length); + } + } + + return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey); + } + + internal static char GenerateCharacter(PwProfile pwProfile, + PwCharSet pwCharSet, CryptoRandomStream crsRandomSource) + { + if (pwCharSet.Size == 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; + } + + internal static void PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile) + { + pwCharSet.Remove(PwCharSet.Invalid); + + if (pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike); + + if (pwProfile.ExcludeCharacters.Length > 0) + pwCharSet.Remove(pwProfile.ExcludeCharacters); + } + + internal static void ShufflePassword(char[] pPassword, + CryptoRandomStream crsRandomSource) + { + Debug.Assert(pPassword != null); if (pPassword == null) return; + Debug.Assert(crsRandomSource != null); if (crsRandomSource == null) return; + + if (pPassword.Length <= 1) return; // Nothing to shuffle + + for (int nSelect = 0; nSelect < pPassword.Length; ++nSelect) + { + ulong uRandomIndex = crsRandomSource.GetRandomUInt64(); + uRandomIndex %= (ulong)(pPassword.Length - nSelect); + + char chTemp = pPassword[nSelect]; + pPassword[nSelect] = pPassword[nSelect + (int)uRandomIndex]; + pPassword[nSelect + (int)uRandomIndex] = chTemp; + } + } + + private static PwgError GenerateCustom(out ProtectedString psOut, + PwProfile pwProfile, CryptoRandomStream crs, + CustomPwGeneratorPool pwAlgorithmPool) + { + psOut = ProtectedString.Empty; + + Debug.Assert(pwProfile.GeneratorType == PasswordGeneratorType.Custom); + if (pwAlgorithmPool == null) return PwgError.UnknownAlgorithm; + + string strID = pwProfile.CustomAlgorithmUuid; + if (string.IsNullOrEmpty(strID)) { Debug.Assert(false); return PwgError.UnknownAlgorithm; } + + byte[] pbUuid = Convert.FromBase64String(strID); + PwUuid uuid = new PwUuid(pbUuid); + CustomPwGenerator pwg = pwAlgorithmPool.Find(uuid); + if (pwg == null) { Debug.Assert(false); return PwgError.UnknownAlgorithm; } + + ProtectedString pwd = pwg.Generate(pwProfile.CloneDeep(), crs); + if (pwd == null) return PwgError.Unknown; + + psOut = pwd; + return PwgError.Success; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/PwProfile.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/PwProfile.cs new file mode 100644 index 00000000..90c99b2b --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/PasswordGenerator/PwProfile.cs @@ -0,0 +1,278 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml.Serialization; +using System.ComponentModel; +using System.Diagnostics; + +using KeePassLib.Interfaces; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.PasswordGenerator +{ + /// + /// Type of the password generator. Different types like generators + /// based on given patterns, based on character sets, etc. are + /// available. + /// + public enum PasswordGeneratorType + { + /// + /// Generator based on character spaces/sets, i.e. groups + /// of characters like lower-case, upper-case or numeric characters. + /// + CharSet = 0, + + /// + /// Password generation based on a pattern. The user has provided + /// a pattern, which describes how the generated password has to + /// look like. + /// + Pattern = 1, + + Custom = 2 + } + + public sealed class PwProfile : IDeepCloneable + { + private string m_strName = string.Empty; + [DefaultValue("")] + public string Name + { + get { return m_strName; } + set { m_strName = value; } + } + + private PasswordGeneratorType m_type = PasswordGeneratorType.CharSet; + public PasswordGeneratorType GeneratorType + { + get { return m_type; } + set { m_type = value; } + } + + private bool m_bUserEntropy = false; + [DefaultValue(false)] + public bool CollectUserEntropy + { + get { return m_bUserEntropy; } + set { m_bUserEntropy = value; } + } + + private uint m_uLength = 20; + public uint Length + { + get { return m_uLength; } + set { m_uLength = value; } + } + + private PwCharSet m_pwCharSet = new PwCharSet(PwCharSet.UpperCase + + PwCharSet.LowerCase + PwCharSet.Digits); + [XmlIgnore] + public PwCharSet CharSet + { + get { return m_pwCharSet; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_pwCharSet = value; + } + } + + private string m_strCharSetRanges = string.Empty; + [DefaultValue("")] + public string CharSetRanges + { + get { this.UpdateCharSet(true); return m_strCharSetRanges; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strCharSetRanges = value; + this.UpdateCharSet(false); + } + } + + private string m_strCharSetAdditional = string.Empty; + [DefaultValue("")] + public string CharSetAdditional + { + get { this.UpdateCharSet(true); return m_strCharSetAdditional; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strCharSetAdditional = value; + this.UpdateCharSet(false); + } + } + + private string m_strPattern = string.Empty; + [DefaultValue("")] + public string Pattern + { + get { return m_strPattern; } + set { m_strPattern = value; } + } + + private bool m_bPatternPermute = false; + [DefaultValue(false)] + public bool PatternPermutePassword + { + get { return m_bPatternPermute; } + set { m_bPatternPermute = value; } + } + + private bool m_bNoLookAlike = false; + [DefaultValue(false)] + public bool ExcludeLookAlike + { + get { return m_bNoLookAlike; } + set { m_bNoLookAlike = value; } + } + + private bool m_bNoRepeat = false; + [DefaultValue(false)] + public bool NoRepeatingCharacters + { + get { return m_bNoRepeat; } + set { m_bNoRepeat = value; } + } + + private string m_strExclude = string.Empty; + [DefaultValue("")] + public string ExcludeCharacters + { + get { return m_strExclude; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strExclude = value; + } + } + + private string m_strCustomID = string.Empty; + [DefaultValue("")] + public string CustomAlgorithmUuid + { + get { return m_strCustomID; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strCustomID = value; + } + } + + private string m_strCustomOpt = string.Empty; + [DefaultValue("")] + public string CustomAlgorithmOptions + { + get { return m_strCustomOpt; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strCustomOpt = value; + } + } + + public PwProfile() + { + } + + public PwProfile CloneDeep() + { + PwProfile p = new PwProfile(); + + p.m_strName = m_strName; + p.m_type = m_type; + p.m_bUserEntropy = m_bUserEntropy; + p.m_uLength = m_uLength; + p.m_pwCharSet = new PwCharSet(m_pwCharSet.ToString()); + p.m_strCharSetRanges = m_strCharSetRanges; + p.m_strCharSetAdditional = m_strCharSetAdditional; + p.m_strPattern = m_strPattern; + p.m_bPatternPermute = m_bPatternPermute; + p.m_bNoLookAlike = m_bNoLookAlike; + p.m_bNoRepeat = m_bNoRepeat; + p.m_strExclude = m_strExclude; + p.m_strCustomID = m_strCustomID; + p.m_strCustomOpt = m_strCustomOpt; + + return p; + } + + private void UpdateCharSet(bool bSetXml) + { + if(bSetXml) + { + PwCharSet pcs = new PwCharSet(m_pwCharSet.ToString()); + m_strCharSetRanges = pcs.PackAndRemoveCharRanges(); + m_strCharSetAdditional = pcs.ToString(); + } + else + { + PwCharSet pcs = new PwCharSet(m_strCharSetAdditional); + pcs.UnpackCharRanges(m_strCharSetRanges); + m_pwCharSet = pcs; + } + } + + public static PwProfile DeriveFromPassword(ProtectedString psPassword) + { + PwProfile pp = new PwProfile(); + Debug.Assert(psPassword != null); if(psPassword == null) return pp; + + byte[] pbUtf8 = psPassword.ReadUtf8(); + char[] vChars = StrUtil.Utf8.GetChars(pbUtf8); + + pp.GeneratorType = PasswordGeneratorType.CharSet; + pp.Length = (uint)vChars.Length; + + PwCharSet pcs = pp.CharSet; + pcs.Clear(); + + foreach(char ch in vChars) + { + if((ch >= 'A') && (ch <= 'Z')) pcs.Add(PwCharSet.UpperCase); + else if((ch >= 'a') && (ch <= 'z')) pcs.Add(PwCharSet.LowerCase); + else if((ch >= '0') && (ch <= '9')) pcs.Add(PwCharSet.Digits); + else if(PwCharSet.SpecialChars.IndexOf(ch) >= 0) + pcs.Add(PwCharSet.SpecialChars); + else if(ch == ' ') pcs.Add(' '); + else if(ch == '-') pcs.Add('-'); + else if(ch == '_') pcs.Add('_'); + else if(PwCharSet.Brackets.IndexOf(ch) >= 0) + pcs.Add(PwCharSet.Brackets); + else if(PwCharSet.HighAnsiChars.IndexOf(ch) >= 0) + pcs.Add(PwCharSet.HighAnsiChars); + else pcs.Add(ch); + } + + MemUtil.ZeroArray(vChars); + MemUtil.ZeroByteArray(pbUtf8); + return pp; + } + + public bool HasSecurityReducingOption() + { + return (m_bNoLookAlike || m_bNoRepeat || (m_strExclude.Length > 0)); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/PopularPasswords.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/PopularPasswords.cs new file mode 100644 index 00000000..6083520d --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/PopularPasswords.cs @@ -0,0 +1,134 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography +{ + public static class PopularPasswords + { + private static Dictionary> m_dicts = + new Dictionary>(); + + internal static int MaxLength + { + get + { + Debug.Assert(m_dicts.Count > 0); // Should be initialized + + int iMaxLen = 0; + foreach(int iLen in m_dicts.Keys) + { + if(iLen > iMaxLen) iMaxLen = iLen; + } + + return iMaxLen; + } + } + + internal static bool ContainsLength(int nLength) + { + Dictionary dDummy; + return m_dicts.TryGetValue(nLength, out dDummy); + } + + public static bool IsPopularPassword(char[] vPassword) + { + ulong uDummy; + return IsPopularPassword(vPassword, out uDummy); + } + + public static bool IsPopularPassword(char[] vPassword, out ulong uDictSize) + { + if(vPassword == null) throw new ArgumentNullException("vPassword"); + if(vPassword.Length == 0) { uDictSize = 0; return false; } + + string str = new string(vPassword); + + try { return IsPopularPasswordPriv(str, out uDictSize); } + catch(Exception) { Debug.Assert(false); } + + uDictSize = 0; + return false; + } + + private static bool IsPopularPasswordPriv(string str, out ulong uDictSize) + { + Debug.Assert(m_dicts.Count > 0); // Should be initialized with data + + Dictionary d; + if(!m_dicts.TryGetValue(str.Length, out d)) + { + uDictSize = 0; + return false; + } + + uDictSize = (ulong)d.Count; + return d.ContainsKey(str); + } + + public static void Add(byte[] pbData, bool bGZipped) + { + try + { + if(bGZipped) + pbData = MemUtil.Decompress(pbData); + + string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length); + if(string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; } + + if(!char.IsWhiteSpace(strData[strData.Length - 1])) + strData += "\n"; + + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < strData.Length; ++i) + { + char ch = strData[i]; + + if(char.IsWhiteSpace(ch)) + { + int cc = sb.Length; + if(cc > 0) + { + string strWord = sb.ToString(); + Debug.Assert(strWord.Length == cc); + + Dictionary d; + if(!m_dicts.TryGetValue(cc, out d)) + { + d = new Dictionary(); + m_dicts[cc] = d; + } + + d[strWord] = true; + sb.Remove(0, cc); + } + } + else sb.Append(char.ToLower(ch)); + } + } + catch(Exception) { Debug.Assert(false); } + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/QualityEstimation.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/QualityEstimation.cs new file mode 100644 index 00000000..cf835825 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/QualityEstimation.cs @@ -0,0 +1,768 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +using KeePassLib.Cryptography.PasswordGenerator; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography +{ + /// + /// A class that offers static functions to estimate the quality of + /// passwords. + /// + public static class QualityEstimation + { + private static class PatternID + { + public const char LowerAlpha = 'L'; + public const char UpperAlpha = 'U'; + public const char Digit = 'D'; + public const char Special = 'S'; + public const char High = 'H'; + public const char Other = 'X'; + + public const char Dictionary = 'W'; + public const char Repetition = 'R'; + public const char Number = 'N'; + public const char DiffSeq = 'C'; + + public const string All = "LUDSHXWRNC"; + } + + // private static class CharDistrib + // { + // public static readonly ulong[] LowerAlpha = new ulong[26] { + // 884, 211, 262, 249, 722, 98, 172, 234, 556, 124, 201, 447, 321, + // 483, 518, 167, 18, 458, 416, 344, 231, 105, 80, 48, 238, 76 + // }; + // public static readonly ulong[] UpperAlpha = new ulong[26] { + // 605, 188, 209, 200, 460, 81, 130, 163, 357, 122, 144, 332, 260, + // 317, 330, 132, 18, 320, 315, 250, 137, 76, 60, 36, 161, 54 + // }; + // public static readonly ulong[] Digit = new ulong[10] { + // 574, 673, 524, 377, 339, 336, 312, 310, 357, 386 + // }; + // } + + private sealed class QeCharType + { + private readonly char m_chTypeID; + public char TypeID { get { return m_chTypeID; } } + + private readonly string m_strAlph; + public string Alphabet { get { return m_strAlph; } } + + private readonly int m_nChars; + public int CharCount { get { return m_nChars; } } + + private readonly char m_chFirst; + private readonly char m_chLast; + + private readonly double m_dblCharSize; + public double CharSize { get { return m_dblCharSize; } } + + public QeCharType(char chTypeID, string strAlphabet, bool bIsConsecutive) + { + if(strAlphabet == null) throw new ArgumentNullException(); + if(strAlphabet.Length == 0) throw new ArgumentException(); + + m_chTypeID = chTypeID; + m_strAlph = strAlphabet; + m_nChars = m_strAlph.Length; + m_chFirst = (bIsConsecutive ? m_strAlph[0] : char.MinValue); + m_chLast = (bIsConsecutive ? m_strAlph[m_nChars - 1] : char.MinValue); + + m_dblCharSize = Log2(m_nChars); + + Debug.Assert(((int)(m_chLast - m_chFirst) == (m_nChars - 1)) || + !bIsConsecutive); + } + + public QeCharType(char chTypeID, int nChars) // Catch-none set + { + if(nChars <= 0) throw new ArgumentOutOfRangeException(); + + m_chTypeID = chTypeID; + m_strAlph = string.Empty; + m_nChars = nChars; + m_chFirst = char.MinValue; + m_chLast = char.MinValue; + + m_dblCharSize = Log2(m_nChars); + } + + public bool Contains(char ch) + { + if(m_chLast != char.MinValue) + return ((ch >= m_chFirst) && (ch <= m_chLast)); + + Debug.Assert(m_strAlph.Length > 0); // Don't call for catch-none set + return (m_strAlph.IndexOf(ch) >= 0); + } + } + + private sealed class EntropyEncoder + { + private readonly string m_strAlph; + private Dictionary m_dHisto = new Dictionary(); + private readonly ulong m_uBaseWeight; + private readonly ulong m_uCharWeight; + private readonly ulong m_uOccExclThreshold; + + public EntropyEncoder(string strAlphabet, ulong uBaseWeight, + ulong uCharWeight, ulong uOccExclThreshold) + { + if(strAlphabet == null) throw new ArgumentNullException(); + if(strAlphabet.Length == 0) throw new ArgumentException(); + + m_strAlph = strAlphabet; + m_uBaseWeight = uBaseWeight; + m_uCharWeight = uCharWeight; + m_uOccExclThreshold = uOccExclThreshold; + +#if DEBUG + Dictionary d = new Dictionary(); + foreach(char ch in m_strAlph) { d[ch] = true; } + Debug.Assert(d.Count == m_strAlph.Length); // No duplicates +#endif + } + + public void Reset() + { + m_dHisto.Clear(); + } + + public void Write(char ch) + { + Debug.Assert(m_strAlph.IndexOf(ch) >= 0); + + ulong uOcc; + m_dHisto.TryGetValue(ch, out uOcc); + Debug.Assert(m_dHisto.ContainsKey(ch) || (uOcc == 0)); + m_dHisto[ch] = uOcc + 1; + } + + public double GetOutputSize() + { + ulong uTotalWeight = m_uBaseWeight * (ulong)m_strAlph.Length; + foreach(ulong u in m_dHisto.Values) + { + Debug.Assert(u >= 1); + if(u > m_uOccExclThreshold) + uTotalWeight += (u - m_uOccExclThreshold) * m_uCharWeight; + } + + double dSize = 0.0, dTotalWeight = (double)uTotalWeight; + foreach(ulong u in m_dHisto.Values) + { + ulong uWeight = m_uBaseWeight; + if(u > m_uOccExclThreshold) + uWeight += (u - m_uOccExclThreshold) * m_uCharWeight; + + dSize -= (double)u * Log2((double)uWeight / dTotalWeight); + } + + return dSize; + } + } + + private sealed class MultiEntropyEncoder + { + private Dictionary m_dEncs = + new Dictionary(); + + public MultiEntropyEncoder() + { + } + + public void AddEncoder(char chTypeID, EntropyEncoder ec) + { + if(ec == null) { Debug.Assert(false); return; } + + Debug.Assert(!m_dEncs.ContainsKey(chTypeID)); + m_dEncs[chTypeID] = ec; + } + + public void Reset() + { + foreach(EntropyEncoder ec in m_dEncs.Values) { ec.Reset(); } + } + + public bool Write(char chTypeID, char chData) + { + EntropyEncoder ec; + if(!m_dEncs.TryGetValue(chTypeID, out ec)) + return false; + + ec.Write(chData); + return true; + } + + public double GetOutputSize() + { + double d = 0.0; + + foreach(EntropyEncoder ec in m_dEncs.Values) + { + d += ec.GetOutputSize(); + } + + return d; + } + } + + private sealed class QePatternInstance + { + private readonly int m_iPos; + public int Position { get { return m_iPos; } } + + private readonly int m_nLen; + public int Length { get { return m_nLen; } } + + private readonly char m_chPatternID; + public char PatternID { get { return m_chPatternID; } } + + private readonly double m_dblCost; + public double Cost { get { return m_dblCost; } } + + private readonly QeCharType m_ctSingle; + public QeCharType SingleCharType { get { return m_ctSingle; } } + + public QePatternInstance(int iPosition, int nLength, char chPatternID, + double dblCost) + { + m_iPos = iPosition; + m_nLen = nLength; + m_chPatternID = chPatternID; + m_dblCost = dblCost; + m_ctSingle = null; + } + + public QePatternInstance(int iPosition, int nLength, QeCharType ctSingle) + { + m_iPos = iPosition; + m_nLen = nLength; + m_chPatternID = ctSingle.TypeID; + m_dblCost = ctSingle.CharSize; + m_ctSingle = ctSingle; + } + } + + private sealed class QePathState + { + public readonly int Position; + public readonly List Path; + + public QePathState(int iPosition, List lPath) + { + this.Position = iPosition; + this.Path = lPath; + } + } + + private static object m_objSyncInit = new object(); + private static List m_lCharTypes = null; + + private static void EnsureInitialized() + { + lock(m_objSyncInit) + { + if(m_lCharTypes == null) + { + string strSpecial = PwCharSet.PrintableAsciiSpecial; + if(strSpecial.IndexOf(' ') >= 0) { Debug.Assert(false); } + else strSpecial = strSpecial + " "; + + int nSp = strSpecial.Length; + int nHi = PwCharSet.HighAnsiChars.Length; + + m_lCharTypes = new List(); + + m_lCharTypes.Add(new QeCharType(PatternID.LowerAlpha, + PwCharSet.LowerCase, true)); + m_lCharTypes.Add(new QeCharType(PatternID.UpperAlpha, + PwCharSet.UpperCase, true)); + m_lCharTypes.Add(new QeCharType(PatternID.Digit, + PwCharSet.Digits, true)); + m_lCharTypes.Add(new QeCharType(PatternID.Special, + strSpecial, false)); + m_lCharTypes.Add(new QeCharType(PatternID.High, + PwCharSet.HighAnsiChars, false)); + m_lCharTypes.Add(new QeCharType(PatternID.Other, + 0x10000 - (2 * 26) - 10 - nSp - nHi)); + } + } + } + + /// + /// Estimate the quality of a password. + /// + /// Password to check. + /// Estimated bit-strength of the password. + public static uint EstimatePasswordBits(char[] vPasswordChars) + { + if(vPasswordChars == null) { Debug.Assert(false); return 0; } + if(vPasswordChars.Length == 0) return 0; + + EnsureInitialized(); + + int n = vPasswordChars.Length; + List[] vPatterns = new List[n]; + for(int i = 0; i < n; ++i) + { + vPatterns[i] = new List(); + + QePatternInstance piChar = new QePatternInstance(i, 1, + GetCharType(vPasswordChars[i])); + vPatterns[i].Add(piChar); + } + + FindRepetitions(vPasswordChars, vPatterns); + FindNumbers(vPasswordChars, vPatterns); + FindDiffSeqs(vPasswordChars, vPatterns); + FindPopularPasswords(vPasswordChars, vPatterns); + + // Encoders must not be static, because the entropy estimation + // may run concurrently in multiple threads and the encoders are + // not read-only + EntropyEncoder ecPattern = new EntropyEncoder(PatternID.All, 0, 1, 0); + MultiEntropyEncoder mcData = new MultiEntropyEncoder(); + for(int i = 0; i < (m_lCharTypes.Count - 1); ++i) + { + // Let m be the alphabet size. In order to ensure that two same + // characters cost at least as much as a single character, for + // the probability p and weight w of the character it must hold: + // -log(1/m) >= -2*log(p) + // <=> log(1/m) <= log(p^2) <=> 1/m <= p^2 <=> p >= sqrt(1/m); + // sqrt(1/m) = (1+w)/(m+w) + // <=> m+w = (1+w)*sqrt(m) <=> m+w = sqrt(m) + w*sqrt(m) + // <=> w*(1-sqrt(m)) = sqrt(m) - m <=> w = (sqrt(m)-m)/(1-sqrt(m)) + // <=> w = (sqrt(m)-m)*(1+sqrt(m))/(1-m) + // <=> w = (sqrt(m)-m+m-m*sqrt(m))/(1-m) <=> w = sqrt(m) + ulong uw = (ulong)Math.Sqrt((double)m_lCharTypes[i].CharCount); + + mcData.AddEncoder(m_lCharTypes[i].TypeID, new EntropyEncoder( + m_lCharTypes[i].Alphabet, 1, uw, 1)); + } + + double dblMinCost = (double)int.MaxValue; + int tStart = Environment.TickCount; + + Stack sRec = new Stack(); + sRec.Push(new QePathState(0, new List())); + while(sRec.Count > 0) + { + int tDiff = Environment.TickCount - tStart; + if(tDiff > 500) break; + + QePathState s = sRec.Pop(); + + if(s.Position >= n) + { + Debug.Assert(s.Position == n); + + double dblCost = ComputePathCost(s.Path, vPasswordChars, + ecPattern, mcData); + if(dblCost < dblMinCost) dblMinCost = dblCost; + } + else + { + List lSubs = vPatterns[s.Position]; + for(int i = lSubs.Count - 1; i >= 0; --i) + { + QePatternInstance pi = lSubs[i]; + Debug.Assert(pi.Position == s.Position); + Debug.Assert(pi.Length >= 1); + + List lNewPath = + new List(s.Path.Count + 1); + lNewPath.AddRange(s.Path); + lNewPath.Add(pi); + Debug.Assert(lNewPath.Capacity == (s.Path.Count + 1)); + + QePathState sNew = new QePathState(s.Position + + pi.Length, lNewPath); + sRec.Push(sNew); + } + } + } + + return (uint)Math.Ceiling(dblMinCost); + } + + /// + /// Estimate the quality of a password. + /// + /// Password to check, UTF-8 encoded. + /// Estimated bit-strength of the password. + public static uint EstimatePasswordBits(byte[] pbUnprotectedUtf8) + { + if(pbUnprotectedUtf8 == null) { Debug.Assert(false); return 0; } + + char[] vChars = StrUtil.Utf8.GetChars(pbUnprotectedUtf8); + uint uResult = EstimatePasswordBits(vChars); + MemUtil.ZeroArray(vChars); + + return uResult; + } + + private static QeCharType GetCharType(char ch) + { + int nTypes = m_lCharTypes.Count; + Debug.Assert((nTypes > 0) && (m_lCharTypes[nTypes - 1].CharCount > 256)); + + for(int i = 0; i < (nTypes - 1); ++i) + { + if(m_lCharTypes[i].Contains(ch)) + return m_lCharTypes[i]; + } + + return m_lCharTypes[nTypes - 1]; + } + + private static double ComputePathCost(List l, + char[] vPassword, EntropyEncoder ecPattern, MultiEntropyEncoder mcData) + { + ecPattern.Reset(); + for(int i = 0; i < l.Count; ++i) + ecPattern.Write(l[i].PatternID); + double dblPatternCost = ecPattern.GetOutputSize(); + + mcData.Reset(); + double dblDataCost = 0.0; + foreach(QePatternInstance pi in l) + { + QeCharType tChar = pi.SingleCharType; + if(tChar != null) + { + char ch = vPassword[pi.Position]; + if(!mcData.Write(tChar.TypeID, ch)) + dblDataCost += pi.Cost; + } + else dblDataCost += pi.Cost; + } + dblDataCost += mcData.GetOutputSize(); + + return (dblPatternCost + dblDataCost); + } + + private static void FindPopularPasswords(char[] vPassword, + List[] vPatterns) + { + int n = vPassword.Length; + + char[] vLower = new char[n]; + char[] vLeet = new char[n]; + for(int i = 0; i < n; ++i) + { + char ch = vPassword[i]; + + vLower[i] = char.ToLower(ch); + vLeet[i] = char.ToLower(DecodeLeetChar(ch)); + } + + char chErased = default(char); + Debug.Assert(chErased == char.MinValue); + + int nMaxLen = Math.Min(n, PopularPasswords.MaxLength); + for(int nSubLen = nMaxLen; nSubLen >= 3; --nSubLen) + { + if(!PopularPasswords.ContainsLength(nSubLen)) continue; + + char[] vSub = new char[nSubLen]; + + for(int i = 0; i <= (n - nSubLen); ++i) + { + if(Array.IndexOf(vLower, chErased, i, nSubLen) >= 0) + continue; + + Array.Copy(vLower, i, vSub, 0, nSubLen); + if(!EvalAddPopularPasswordPattern(vPatterns, vPassword, + i, vSub, 0.0)) + { + Array.Copy(vLeet, i, vSub, 0, nSubLen); + if(EvalAddPopularPasswordPattern(vPatterns, vPassword, + i, vSub, 1.5)) + { + Array.Clear(vLower, i, nSubLen); // Not vLeet + Debug.Assert(vLower[i] == chErased); + } + } + else + { + Array.Clear(vLower, i, nSubLen); + Debug.Assert(vLower[i] == chErased); + } + } + } + } + + private static bool EvalAddPopularPasswordPattern(List[] vPatterns, + char[] vPassword, int i, char[] vSub, double dblCostPerMod) + { + ulong uDictSize; + if(!PopularPasswords.IsPopularPassword(vSub, out uDictSize)) + return false; + + int n = vSub.Length; + int d = HammingDist(vSub, 0, vPassword, i, n); + + double dblCost = Log2((double)uDictSize); + + // dblCost += log2(n binom d) + int k = Math.Min(d, n - d); + for(int j = n; j > (n - k); --j) + dblCost += Log2(j); + for(int j = k; j >= 2; --j) + dblCost -= Log2(j); + + dblCost += dblCostPerMod * (double)d; + + vPatterns[i].Add(new QePatternInstance(i, n, PatternID.Dictionary, + dblCost)); + return true; + } + + private static char DecodeLeetChar(char chLeet) + { + if((chLeet >= '\u00C0') && (chLeet <= '\u00C6')) return 'a'; + if((chLeet >= '\u00C8') && (chLeet <= '\u00CB')) return 'e'; + if((chLeet >= '\u00CC') && (chLeet <= '\u00CF')) return 'i'; + if((chLeet >= '\u00D2') && (chLeet <= '\u00D6')) return 'o'; + if((chLeet >= '\u00D9') && (chLeet <= '\u00DC')) return 'u'; + if((chLeet >= '\u00E0') && (chLeet <= '\u00E6')) return 'a'; + if((chLeet >= '\u00E8') && (chLeet <= '\u00EB')) return 'e'; + if((chLeet >= '\u00EC') && (chLeet <= '\u00EF')) return 'i'; + if((chLeet >= '\u00F2') && (chLeet <= '\u00F6')) return 'o'; + if((chLeet >= '\u00F9') && (chLeet <= '\u00FC')) return 'u'; + + char ch; + switch(chLeet) + { + case '4': + case '@': + case '?': + case '^': + case '\u00AA': ch = 'a'; break; + case '8': + case '\u00DF': ch = 'b'; break; + case '(': + case '{': + case '[': + case '<': + case '\u00A2': + case '\u00A9': + case '\u00C7': + case '\u00E7': ch = 'c'; break; + case '\u00D0': + case '\u00F0': ch = 'd'; break; + case '3': + case '\u20AC': + case '&': + case '\u00A3': ch = 'e'; break; + case '6': + case '9': ch = 'g'; break; + case '#': ch = 'h'; break; + case '1': + case '!': + case '|': + case '\u00A1': + case '\u00A6': ch = 'i'; break; + case '\u00D1': + case '\u00F1': ch = 'n'; break; + case '0': + case '*': + case '\u00A4': // Currency + case '\u00B0': // Degree + case '\u00D8': + case '\u00F8': ch = 'o'; break; + case '\u00AE': ch = 'r'; break; + case '$': + case '5': + case '\u00A7': ch = 's'; break; + case '+': + case '7': ch = 't'; break; + case '\u00B5': ch = 'u'; break; + case '%': + case '\u00D7': ch = 'x'; break; + case '\u00A5': + case '\u00DD': + case '\u00FD': + case '\u00FF': ch = 'y'; break; + case '2': ch = 'z'; break; + default: ch = chLeet; break; + } + + return ch; + } + + private static int HammingDist(char[] v1, int iOffset1, + char[] v2, int iOffset2, int nLength) + { + int nDist = 0; + for(int i = 0; i < nLength; ++i) + { + if(v1[iOffset1 + i] != v2[iOffset2 + i]) ++nDist; + } + + return nDist; + } + + private static void FindRepetitions(char[] vPassword, + List[] vPatterns) + { + int n = vPassword.Length; + char[] v = new char[n]; + Array.Copy(vPassword, v, n); + + char chErased = char.MaxValue; + for(int m = (n / 2); m >= 3; --m) + { + for(int x1 = 0; x1 <= (n - (2 * m)); ++x1) + { + bool bFoundRep = false; + + for(int x2 = (x1 + m); x2 <= (n - m); ++x2) + { + if(PartsEqual(v, x1, x2, m)) + { + double dblCost = Log2(x1 + 1) + Log2(m); + vPatterns[x2].Add(new QePatternInstance(x2, m, + PatternID.Repetition, dblCost)); + + ErasePart(v, x2, m, ref chErased); + bFoundRep = true; + } + } + + if(bFoundRep) ErasePart(v, x1, m, ref chErased); + } + } + } + + private static bool PartsEqual(char[] v, int x1, int x2, int nLength) + { + for(int i = 0; i < nLength; ++i) + { + if(v[x1 + i] != v[x2 + i]) return false; + } + + return true; + } + + private static void ErasePart(char[] v, int i, int n, ref char chErased) + { + for(int j = 0; j < n; ++j) + { + v[i + j] = chErased; + --chErased; + } + } + + private static void FindNumbers(char[] vPassword, + List[] vPatterns) + { + int n = vPassword.Length; + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < n; ++i) + { + char ch = vPassword[i]; + if((ch >= '0') && (ch <= '9')) sb.Append(ch); + else + { + AddNumberPattern(vPatterns, sb.ToString(), i - sb.Length); + sb.Remove(0, sb.Length); + } + } + AddNumberPattern(vPatterns, sb.ToString(), n - sb.Length); + } + + private static void AddNumberPattern(List[] vPatterns, + string strNumber, int i) + { + if(strNumber.Length <= 2) return; + + int nZeros = 0; + for(int j = 0; j < strNumber.Length; ++j) + { + if(strNumber[j] != '0') break; + ++nZeros; + } + + double dblCost = Log2(nZeros + 1); + if(nZeros < strNumber.Length) + { + string strNonZero = strNumber.Substring(nZeros); + +#if KeePassLibSD + try { dblCost += Log2(double.Parse(strNonZero)); } + catch(Exception) { Debug.Assert(false); return; } +#else + double d; + if(double.TryParse(strNonZero, out d)) + dblCost += Log2(d); + else { Debug.Assert(false); return; } +#endif + } + + vPatterns[i].Add(new QePatternInstance(i, strNumber.Length, + PatternID.Number, dblCost)); + } + + private static void FindDiffSeqs(char[] vPassword, + List[] vPatterns) + { + int d = int.MinValue, p = 0; + string str = new string(vPassword) + new string(char.MaxValue, 1); + + for(int i = 1; i < str.Length; ++i) + { + int dCur = (int)str[i] - (int)str[i - 1]; + if(dCur != d) + { + if((i - p) >= 3) // At least 3 chars involved + { + QeCharType ct = GetCharType(str[p]); + double dblCost = ct.CharSize + Log2(i - p - 1); + + vPatterns[p].Add(new QePatternInstance(p, + i - p, PatternID.DiffSeq, dblCost)); + } + + d = dCur; + p = i - 1; + } + } + } + + private static double Log2(double dblValue) + { +#if KeePassLibSD + return (Math.Log(dblValue) / Math.Log(2.0)); +#else + return Math.Log(dblValue, 2.0); +#endif + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Cryptography/SelfTest.cs b/src/KeePassLib2AndroidSdkStyle/Cryptography/SelfTest.cs new file mode 100644 index 00000000..43ef45b8 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Cryptography/SelfTest.cs @@ -0,0 +1,1051 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Security; +using System.Text; + +#if KeePassUAP +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +#else +using System.Security.Cryptography; +#endif + +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.Hash; +using KeePassLib.Cryptography.KeyDerivation; +using KeePassLib.Keys; +using KeePassLib.Native; +using KeePassLib.Resources; +using KeePassLib.Security; +using KeePassLib.Utility; + +#if (KeePassUAP && KeePassLibSD) +#error KeePassUAP and KeePassLibSD are mutually exclusive. +#endif + +namespace KeePassLib.Cryptography +{ + /// + /// Class containing self-test methods. + /// + public static class SelfTest + { + /// + /// Perform a self-test. + /// + public static void Perform() + { + TestFipsComplianceProblems(); // Must be the first test + + TestRijndael(); + TestSalsa20(); + TestChaCha20(); + TestBlake2b(); + TestArgon2(); + TestHmac(); + + TestNativeKeyTransform(); + + TestHmacOtp(); + + TestProtectedObjects(); + TestMemUtil(); + TestStrUtil(); + TestUrlUtil(); + + Debug.Assert((int)PwIcon.World == 1); + Debug.Assert((int)PwIcon.Warning == 2); + Debug.Assert((int)PwIcon.BlackBerry == 68); + +#if KeePassUAP + SelfTestEx.Perform(); +#endif + } + + internal static void TestFipsComplianceProblems() + { +#if !KeePassUAP + try { using(RijndaelManaged r = new RijndaelManaged()) { } } + catch(Exception exAes) + { + throw new SecurityException("AES/Rijndael: " + exAes.Message); + } +#endif + + try { using(SHA256Managed h = new SHA256Managed()) { } } + catch(Exception exSha256) + { + throw new SecurityException("SHA-256: " + exSha256.Message); + } + } + + private static void TestRijndael() + { + // Test vector (official ECB test vector #356) + byte[] pbIV = new byte[16]; + byte[] pbTestKey = new byte[32]; + byte[] pbTestData = new byte[16]; + byte[] pbReferenceCT = new byte[16] { + 0x75, 0xD1, 0x1B, 0x0E, 0x3A, 0x68, 0xC4, 0x22, + 0x3D, 0x88, 0xDB, 0xF0, 0x17, 0x97, 0x7D, 0xD7 }; + int i; + + for(i = 0; i < 16; ++i) pbIV[i] = 0; + for(i = 0; i < 32; ++i) pbTestKey[i] = 0; + for(i = 0; i < 16; ++i) pbTestData[i] = 0; + pbTestData[0] = 0x04; + +#if KeePassUAP + AesEngine r = new AesEngine(); + r.Init(true, new KeyParameter(pbTestKey)); + if(r.GetBlockSize() != pbTestData.Length) + throw new SecurityException("AES (BC)"); + r.ProcessBlock(pbTestData, 0, pbTestData, 0); +#else + RijndaelManaged r = new RijndaelManaged(); + + if(r.BlockSize != 128) // AES block size + { + Debug.Assert(false); + r.BlockSize = 128; + } + + r.IV = pbIV; + r.KeySize = 256; + r.Key = pbTestKey; + r.Mode = CipherMode.ECB; + ICryptoTransform iCrypt = r.CreateEncryptor(); + + iCrypt.TransformBlock(pbTestData, 0, 16, pbTestData, 0); +#endif + + if(!MemUtil.ArraysEqual(pbTestData, pbReferenceCT)) + throw new SecurityException("AES"); + } + + private static void TestSalsa20() + { +#if DEBUG + // Test values from official set 6, vector 3 + byte[] pbKey = new byte[32] { + 0x0F, 0x62, 0xB5, 0x08, 0x5B, 0xAE, 0x01, 0x54, + 0xA7, 0xFA, 0x4D, 0xA0, 0xF3, 0x46, 0x99, 0xEC, + 0x3F, 0x92, 0xE5, 0x38, 0x8B, 0xDE, 0x31, 0x84, + 0xD7, 0x2A, 0x7D, 0xD0, 0x23, 0x76, 0xC9, 0x1C + }; + byte[] pbIV = new byte[8] { 0x28, 0x8F, 0xF6, 0x5D, + 0xC4, 0x2B, 0x92, 0xF9 }; + byte[] pbExpected = new byte[16] { + 0x5E, 0x5E, 0x71, 0xF9, 0x01, 0x99, 0x34, 0x03, + 0x04, 0xAB, 0xB2, 0x2A, 0x37, 0xB6, 0x62, 0x5B + }; + + byte[] pb = new byte[16]; + Salsa20Cipher c = new Salsa20Cipher(pbKey, pbIV); + c.Encrypt(pb, 0, pb.Length); + if(!MemUtil.ArraysEqual(pb, pbExpected)) + throw new SecurityException("Salsa20-1"); + + // Extended test + byte[] pbExpected2 = new byte[16] { + 0xAB, 0xF3, 0x9A, 0x21, 0x0E, 0xEE, 0x89, 0x59, + 0x8B, 0x71, 0x33, 0x37, 0x70, 0x56, 0xC2, 0xFE + }; + byte[] pbExpected3 = new byte[16] { + 0x1B, 0xA8, 0x9D, 0xBD, 0x3F, 0x98, 0x83, 0x97, + 0x28, 0xF5, 0x67, 0x91, 0xD5, 0xB7, 0xCE, 0x23 + }; + + Random r = new Random(); + int nPos = Salsa20ToPos(c, r, pb.Length, 65536); + Array.Clear(pb, 0, pb.Length); + c.Encrypt(pb, 0, pb.Length); + if(!MemUtil.ArraysEqual(pb, pbExpected2)) + throw new SecurityException("Salsa20-2"); + + nPos = Salsa20ToPos(c, r, nPos + pb.Length, 131008); + Array.Clear(pb, 0, pb.Length); + c.Encrypt(pb, 0, pb.Length); + if(!MemUtil.ArraysEqual(pb, pbExpected3)) + throw new SecurityException("Salsa20-3"); + + Dictionary d = new Dictionary(); + const int nRounds = 100; + for(int i = 0; i < nRounds; ++i) + { + byte[] z = new byte[32]; + c = new Salsa20Cipher(z, MemUtil.Int64ToBytes(i)); + c.Encrypt(z, 0, z.Length); + d[MemUtil.ByteArrayToHexString(z)] = true; + } + if(d.Count != nRounds) throw new SecurityException("Salsa20-4"); +#endif + } + +#if DEBUG + private static int Salsa20ToPos(Salsa20Cipher c, Random r, int nPos, + int nTargetPos) + { + byte[] pb = new byte[512]; + + while(nPos < nTargetPos) + { + int x = r.Next(1, 513); + int nGen = Math.Min(nTargetPos - nPos, x); + c.Encrypt(pb, 0, nGen); + nPos += nGen; + } + + return nTargetPos; + } +#endif + + private static void TestChaCha20() + { + // ====================================================== + // Test vector from RFC 7539, section 2.3.2 + + byte[] pbKey = new byte[32]; + for(int i = 0; i < 32; ++i) pbKey[i] = (byte)i; + + byte[] pbIV = new byte[12]; + pbIV[3] = 0x09; + pbIV[7] = 0x4A; + + byte[] pbExpc = new byte[64] { + 0x10, 0xF1, 0xE7, 0xE4, 0xD1, 0x3B, 0x59, 0x15, + 0x50, 0x0F, 0xDD, 0x1F, 0xA3, 0x20, 0x71, 0xC4, + 0xC7, 0xD1, 0xF4, 0xC7, 0x33, 0xC0, 0x68, 0x03, + 0x04, 0x22, 0xAA, 0x9A, 0xC3, 0xD4, 0x6C, 0x4E, + 0xD2, 0x82, 0x64, 0x46, 0x07, 0x9F, 0xAA, 0x09, + 0x14, 0xC2, 0xD7, 0x05, 0xD9, 0x8B, 0x02, 0xA2, + 0xB5, 0x12, 0x9C, 0xD1, 0xDE, 0x16, 0x4E, 0xB9, + 0xCB, 0xD0, 0x83, 0xE8, 0xA2, 0x50, 0x3C, 0x4E + }; + + byte[] pb = new byte[64]; + + using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV)) + { + c.Seek(64, SeekOrigin.Begin); // Skip first block + c.Encrypt(pb, 0, pb.Length); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("ChaCha20-1"); + } + +#if DEBUG + // ====================================================== + // Test vector from RFC 7539, section 2.4.2 + + pbIV[3] = 0; + + pb = StrUtil.Utf8.GetBytes("Ladies and Gentlemen of the clas" + + @"s of '99: If I could offer you only one tip for " + + @"the future, sunscreen would be it."); + + pbExpc = new byte[] { + 0x6E, 0x2E, 0x35, 0x9A, 0x25, 0x68, 0xF9, 0x80, + 0x41, 0xBA, 0x07, 0x28, 0xDD, 0x0D, 0x69, 0x81, + 0xE9, 0x7E, 0x7A, 0xEC, 0x1D, 0x43, 0x60, 0xC2, + 0x0A, 0x27, 0xAF, 0xCC, 0xFD, 0x9F, 0xAE, 0x0B, + 0xF9, 0x1B, 0x65, 0xC5, 0x52, 0x47, 0x33, 0xAB, + 0x8F, 0x59, 0x3D, 0xAB, 0xCD, 0x62, 0xB3, 0x57, + 0x16, 0x39, 0xD6, 0x24, 0xE6, 0x51, 0x52, 0xAB, + 0x8F, 0x53, 0x0C, 0x35, 0x9F, 0x08, 0x61, 0xD8, + 0x07, 0xCA, 0x0D, 0xBF, 0x50, 0x0D, 0x6A, 0x61, + 0x56, 0xA3, 0x8E, 0x08, 0x8A, 0x22, 0xB6, 0x5E, + 0x52, 0xBC, 0x51, 0x4D, 0x16, 0xCC, 0xF8, 0x06, + 0x81, 0x8C, 0xE9, 0x1A, 0xB7, 0x79, 0x37, 0x36, + 0x5A, 0xF9, 0x0B, 0xBF, 0x74, 0xA3, 0x5B, 0xE6, + 0xB4, 0x0B, 0x8E, 0xED, 0xF2, 0x78, 0x5E, 0x42, + 0x87, 0x4D + }; + + byte[] pb64 = new byte[64]; + + using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV)) + { + c.Encrypt(pb64, 0, pb64.Length); // Skip first block + c.Encrypt(pb, 0, pb.Length); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("ChaCha20-2"); + } + + // ====================================================== + // Test vector from RFC 7539, appendix A.2 #2 + + Array.Clear(pbKey, 0, pbKey.Length); + pbKey[31] = 1; + + Array.Clear(pbIV, 0, pbIV.Length); + pbIV[11] = 2; + + pb = StrUtil.Utf8.GetBytes("Any submission to the IETF inten" + + "ded by the Contributor for publication as all or" + + " part of an IETF Internet-Draft or RFC and any s" + + "tatement made within the context of an IETF acti" + + "vity is considered an \"IETF Contribution\". Such " + + "statements include oral statements in IETF sessi" + + "ons, as well as written and electronic communica" + + "tions made at any time or place, which are addressed to"); + + pbExpc = MemUtil.HexStringToByteArray( + "A3FBF07DF3FA2FDE4F376CA23E82737041605D9F4F4F57BD8CFF2C1D4B7955EC" + + "2A97948BD3722915C8F3D337F7D370050E9E96D647B7C39F56E031CA5EB6250D" + + "4042E02785ECECFA4B4BB5E8EAD0440E20B6E8DB09D881A7C6132F420E527950" + + "42BDFA7773D8A9051447B3291CE1411C680465552AA6C405B7764D5E87BEA85A" + + "D00F8449ED8F72D0D662AB052691CA66424BC86D2DF80EA41F43ABF937D3259D" + + "C4B2D0DFB48A6C9139DDD7F76966E928E635553BA76C5C879D7B35D49EB2E62B" + + "0871CDAC638939E25E8A1E0EF9D5280FA8CA328B351C3C765989CBCF3DAA8B6C" + + "CC3AAF9F3979C92B3720FC88DC95ED84A1BE059C6499B9FDA236E7E818B04B0B" + + "C39C1E876B193BFE5569753F88128CC08AAA9B63D1A16F80EF2554D7189C411F" + + "5869CA52C5B83FA36FF216B9C1D30062BEBCFD2DC5BCE0911934FDA79A86F6E6" + + "98CED759C3FF9B6477338F3DA4F9CD8514EA9982CCAFB341B2384DD902F3D1AB" + + "7AC61DD29C6F21BA5B862F3730E37CFDC4FD806C22F221"); + + Random r = new Random(); + using(MemoryStream msEnc = new MemoryStream()) + { + using(ChaCha20Stream c = new ChaCha20Stream(msEnc, true, pbKey, pbIV)) + { + r.NextBytes(pb64); + c.Write(pb64, 0, pb64.Length); // Skip first block + + int p = 0; + while(p < pb.Length) + { + int cb = r.Next(1, pb.Length - p + 1); + c.Write(pb, p, cb); + p += cb; + } + Debug.Assert(p == pb.Length); + } + + byte[] pbEnc0 = msEnc.ToArray(); + byte[] pbEnc = MemUtil.Mid(pbEnc0, 64, pbEnc0.Length - 64); + if(!MemUtil.ArraysEqual(pbEnc, pbExpc)) + throw new SecurityException("ChaCha20-3"); + + using(MemoryStream msCT = new MemoryStream(pbEnc0, false)) + { + using(ChaCha20Stream cDec = new ChaCha20Stream(msCT, false, + pbKey, pbIV)) + { + byte[] pbPT = MemUtil.Read(cDec, pbEnc0.Length); + if(cDec.ReadByte() >= 0) + throw new SecurityException("ChaCha20-4"); + if(!MemUtil.ArraysEqual(MemUtil.Mid(pbPT, 0, 64), pb64)) + throw new SecurityException("ChaCha20-5"); + if(!MemUtil.ArraysEqual(MemUtil.Mid(pbPT, 64, pbEnc.Length), pb)) + throw new SecurityException("ChaCha20-6"); + } + } + } + + // ====================================================== + // Test vector TC8 from RFC draft by J. Strombergson: + // https://tools.ietf.org/html/draft-strombergson-chacha-test-vectors-01 + + pbKey = new byte[32] { + 0xC4, 0x6E, 0xC1, 0xB1, 0x8C, 0xE8, 0xA8, 0x78, + 0x72, 0x5A, 0x37, 0xE7, 0x80, 0xDF, 0xB7, 0x35, + 0x1F, 0x68, 0xED, 0x2E, 0x19, 0x4C, 0x79, 0xFB, + 0xC6, 0xAE, 0xBE, 0xE1, 0xA6, 0x67, 0x97, 0x5D + }; + + // The first 4 bytes are set to zero and a large counter + // is used; this makes the RFC 7539 version of ChaCha20 + // compatible with the original specification by + // D. J. Bernstein. + pbIV = new byte[12] { 0x00, 0x00, 0x00, 0x00, + 0x1A, 0xDA, 0x31, 0xD5, 0xCF, 0x68, 0x82, 0x21 + }; + + pb = new byte[128]; + + pbExpc = new byte[128] { + 0xF6, 0x3A, 0x89, 0xB7, 0x5C, 0x22, 0x71, 0xF9, + 0x36, 0x88, 0x16, 0x54, 0x2B, 0xA5, 0x2F, 0x06, + 0xED, 0x49, 0x24, 0x17, 0x92, 0x30, 0x2B, 0x00, + 0xB5, 0xE8, 0xF8, 0x0A, 0xE9, 0xA4, 0x73, 0xAF, + 0xC2, 0x5B, 0x21, 0x8F, 0x51, 0x9A, 0xF0, 0xFD, + 0xD4, 0x06, 0x36, 0x2E, 0x8D, 0x69, 0xDE, 0x7F, + 0x54, 0xC6, 0x04, 0xA6, 0xE0, 0x0F, 0x35, 0x3F, + 0x11, 0x0F, 0x77, 0x1B, 0xDC, 0xA8, 0xAB, 0x92, + + 0xE5, 0xFB, 0xC3, 0x4E, 0x60, 0xA1, 0xD9, 0xA9, + 0xDB, 0x17, 0x34, 0x5B, 0x0A, 0x40, 0x27, 0x36, + 0x85, 0x3B, 0xF9, 0x10, 0xB0, 0x60, 0xBD, 0xF1, + 0xF8, 0x97, 0xB6, 0x29, 0x0F, 0x01, 0xD1, 0x38, + 0xAE, 0x2C, 0x4C, 0x90, 0x22, 0x5B, 0xA9, 0xEA, + 0x14, 0xD5, 0x18, 0xF5, 0x59, 0x29, 0xDE, 0xA0, + 0x98, 0xCA, 0x7A, 0x6C, 0xCF, 0xE6, 0x12, 0x27, + 0x05, 0x3C, 0x84, 0xE4, 0x9A, 0x4A, 0x33, 0x32 + }; + + using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV, true)) + { + c.Decrypt(pb, 0, pb.Length); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("ChaCha20-7"); + } +#endif + } + + private static void TestBlake2b() + { +#if DEBUG + Blake2b h = new Blake2b(); + + // ====================================================== + // From https://tools.ietf.org/html/rfc7693 + + byte[] pbData = StrUtil.Utf8.GetBytes("abc"); + byte[] pbExpc = new byte[64] { + 0xBA, 0x80, 0xA5, 0x3F, 0x98, 0x1C, 0x4D, 0x0D, + 0x6A, 0x27, 0x97, 0xB6, 0x9F, 0x12, 0xF6, 0xE9, + 0x4C, 0x21, 0x2F, 0x14, 0x68, 0x5A, 0xC4, 0xB7, + 0x4B, 0x12, 0xBB, 0x6F, 0xDB, 0xFF, 0xA2, 0xD1, + 0x7D, 0x87, 0xC5, 0x39, 0x2A, 0xAB, 0x79, 0x2D, + 0xC2, 0x52, 0xD5, 0xDE, 0x45, 0x33, 0xCC, 0x95, + 0x18, 0xD3, 0x8A, 0xA8, 0xDB, 0xF1, 0x92, 0x5A, + 0xB9, 0x23, 0x86, 0xED, 0xD4, 0x00, 0x99, 0x23 + }; + + byte[] pbC = h.ComputeHash(pbData); + if(!MemUtil.ArraysEqual(pbC, pbExpc)) + throw new SecurityException("Blake2b-1"); + + // ====================================================== + // Computed using the official b2sum tool + + pbExpc = new byte[64] { + 0x78, 0x6A, 0x02, 0xF7, 0x42, 0x01, 0x59, 0x03, + 0xC6, 0xC6, 0xFD, 0x85, 0x25, 0x52, 0xD2, 0x72, + 0x91, 0x2F, 0x47, 0x40, 0xE1, 0x58, 0x47, 0x61, + 0x8A, 0x86, 0xE2, 0x17, 0xF7, 0x1F, 0x54, 0x19, + 0xD2, 0x5E, 0x10, 0x31, 0xAF, 0xEE, 0x58, 0x53, + 0x13, 0x89, 0x64, 0x44, 0x93, 0x4E, 0xB0, 0x4B, + 0x90, 0x3A, 0x68, 0x5B, 0x14, 0x48, 0xB7, 0x55, + 0xD5, 0x6F, 0x70, 0x1A, 0xFE, 0x9B, 0xE2, 0xCE + }; + + pbC = h.ComputeHash(MemUtil.EmptyByteArray); + if(!MemUtil.ArraysEqual(pbC, pbExpc)) + throw new SecurityException("Blake2b-2"); + + // ====================================================== + // Computed using the official b2sum tool + + string strS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.:,;_-\r\n"; + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < 1000; ++i) sb.Append(strS); + pbData = StrUtil.Utf8.GetBytes(sb.ToString()); + + pbExpc = new byte[64] { + 0x59, 0x69, 0x8D, 0x3B, 0x83, 0xF4, 0x02, 0x4E, + 0xD8, 0x99, 0x26, 0x0E, 0xF4, 0xE5, 0x9F, 0x20, + 0xDC, 0x31, 0xEE, 0x5B, 0x45, 0xEA, 0xBB, 0xFC, + 0x1C, 0x0A, 0x8E, 0xED, 0xAA, 0x7A, 0xFF, 0x50, + 0x82, 0xA5, 0x8F, 0xBC, 0x4A, 0x46, 0xFC, 0xC5, + 0xEF, 0x44, 0x4E, 0x89, 0x80, 0x7D, 0x3F, 0x1C, + 0xC1, 0x94, 0x45, 0xBB, 0xC0, 0x2C, 0x95, 0xAA, + 0x3F, 0x08, 0x8A, 0x93, 0xF8, 0x75, 0x91, 0xB0 + }; + + Random r = new Random(); + int p = 0; + while(p < pbData.Length) + { + int cb = r.Next(1, pbData.Length - p + 1); + h.TransformBlock(pbData, p, cb, pbData, p); + p += cb; + } + Debug.Assert(p == pbData.Length); + + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + if(!MemUtil.ArraysEqual(h.Hash, pbExpc)) + throw new SecurityException("Blake2b-3"); + + h.Clear(); +#endif + } + + private static void TestArgon2() + { +#if DEBUG + Argon2Kdf kdf = new Argon2Kdf(); + + // ====================================================== + // From the official Argon2 1.3 reference code package + // (test vector for Argon2d 1.3); also on + // https://tools.ietf.org/html/draft-irtf-cfrg-argon2-00 + + KdfParameters p = kdf.GetDefaultParameters(); + kdf.Randomize(p); + + Debug.Assert(p.GetUInt32(Argon2Kdf.ParamVersion, 0) == 0x13U); + + byte[] pbMsg = new byte[32]; + for(int i = 0; i < pbMsg.Length; ++i) pbMsg[i] = 1; + + p.SetUInt64(Argon2Kdf.ParamMemory, 32 * 1024); + p.SetUInt64(Argon2Kdf.ParamIterations, 3); + p.SetUInt32(Argon2Kdf.ParamParallelism, 4); + + byte[] pbSalt = new byte[16]; + for(int i = 0; i < pbSalt.Length; ++i) pbSalt[i] = 2; + p.SetByteArray(Argon2Kdf.ParamSalt, pbSalt); + + byte[] pbKey = new byte[8]; + for(int i = 0; i < pbKey.Length; ++i) pbKey[i] = 3; + p.SetByteArray(Argon2Kdf.ParamSecretKey, pbKey); + + byte[] pbAssoc = new byte[12]; + for(int i = 0; i < pbAssoc.Length; ++i) pbAssoc[i] = 4; + p.SetByteArray(Argon2Kdf.ParamAssocData, pbAssoc); + + byte[] pbExpc = new byte[32] { + 0x51, 0x2B, 0x39, 0x1B, 0x6F, 0x11, 0x62, 0x97, + 0x53, 0x71, 0xD3, 0x09, 0x19, 0x73, 0x42, 0x94, + 0xF8, 0x68, 0xE3, 0xBE, 0x39, 0x84, 0xF3, 0xC1, + 0xA1, 0x3A, 0x4D, 0xB9, 0xFA, 0xBE, 0x4A, 0xCB + }; + + byte[] pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-1"); + + // ====================================================== + // From the official Argon2 1.3 reference code package + // (test vector for Argon2d 1.0) + + p.SetUInt32(Argon2Kdf.ParamVersion, 0x10); + + pbExpc = new byte[32] { + 0x96, 0xA9, 0xD4, 0xE5, 0xA1, 0x73, 0x40, 0x92, + 0xC8, 0x5E, 0x29, 0xF4, 0x10, 0xA4, 0x59, 0x14, + 0xA5, 0xDD, 0x1F, 0x5C, 0xBF, 0x08, 0xB2, 0x67, + 0x0D, 0xA6, 0x8A, 0x02, 0x85, 0xAB, 0xF3, 0x2B + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-2"); + + // ====================================================== + // From the official 'phc-winner-argon2-20151206.zip' + // (test vector for Argon2d 1.0) + + p.SetUInt64(Argon2Kdf.ParamMemory, 16 * 1024); + + pbExpc = new byte[32] { + 0x57, 0xB0, 0x61, 0x3B, 0xFD, 0xD4, 0x13, 0x1A, + 0x0C, 0x34, 0x88, 0x34, 0xC6, 0x72, 0x9C, 0x2C, + 0x72, 0x29, 0x92, 0x1E, 0x6B, 0xBA, 0x37, 0x66, + 0x5D, 0x97, 0x8C, 0x4F, 0xE7, 0x17, 0x5E, 0xD2 + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-3"); + +#if SELFTEST_ARGON2_LONG + // ====================================================== + // Computed using the official 'argon2' application + // (test vectors for Argon2d 1.3) + + p = kdf.GetDefaultParameters(); + + pbMsg = StrUtil.Utf8.GetBytes("ABC1234"); + + p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 11) * 1024); // 2 MB + p.SetUInt64(Argon2Kdf.ParamIterations, 2); + p.SetUInt32(Argon2Kdf.ParamParallelism, 2); + + pbSalt = StrUtil.Utf8.GetBytes("somesalt"); + p.SetByteArray(Argon2Kdf.ParamSalt, pbSalt); + + pbExpc = new byte[32] { + 0x29, 0xCB, 0xD3, 0xA1, 0x93, 0x76, 0xF7, 0xA2, + 0xFC, 0xDF, 0xB0, 0x68, 0xAC, 0x0B, 0x99, 0xBA, + 0x40, 0xAC, 0x09, 0x01, 0x73, 0x42, 0xCE, 0xF1, + 0x29, 0xCC, 0xA1, 0x4F, 0xE1, 0xC1, 0xB7, 0xA3 + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-4"); + + p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 10) * 1024); // 1 MB + p.SetUInt64(Argon2Kdf.ParamIterations, 3); + + pbExpc = new byte[32] { + 0x7A, 0xBE, 0x1C, 0x1C, 0x8D, 0x7F, 0xD6, 0xDC, + 0x7C, 0x94, 0x06, 0x3E, 0xD8, 0xBC, 0xD8, 0x1C, + 0x2F, 0x87, 0x84, 0x99, 0x12, 0x83, 0xFE, 0x76, + 0x00, 0x64, 0xC4, 0x58, 0xA4, 0xDA, 0x35, 0x70 + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-5"); + +#if SELFTEST_ARGON2_LONGER + p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 20) * 1024); // 1 GB + p.SetUInt64(Argon2Kdf.ParamIterations, 2); + p.SetUInt32(Argon2Kdf.ParamParallelism, 3); + + pbExpc = new byte[32] { + 0xE6, 0xE7, 0xCB, 0xF5, 0x5A, 0x06, 0x93, 0x05, + 0x32, 0xBA, 0x86, 0xC6, 0x1F, 0x45, 0x17, 0x99, + 0x65, 0x41, 0x77, 0xF9, 0x30, 0x55, 0x9A, 0xE8, + 0x3D, 0x21, 0x48, 0xC6, 0x2D, 0x0C, 0x49, 0x11 + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-6"); +#endif // SELFTEST_ARGON2_LONGER +#endif // SELFTEST_ARGON2_LONG +#endif // DEBUG + } + + private static void TestHmac() + { +#if DEBUG + // Test vectors from RFC 4231 + + byte[] pbKey = new byte[20]; + for(int i = 0; i < pbKey.Length; ++i) pbKey[i] = 0x0B; + byte[] pbMsg = StrUtil.Utf8.GetBytes("Hi There"); + byte[] pbExpc = new byte[32] { + 0xB0, 0x34, 0x4C, 0x61, 0xD8, 0xDB, 0x38, 0x53, + 0x5C, 0xA8, 0xAF, 0xCE, 0xAF, 0x0B, 0xF1, 0x2B, + 0x88, 0x1D, 0xC2, 0x00, 0xC9, 0x83, 0x3D, 0xA7, + 0x26, 0xE9, 0x37, 0x6C, 0x2E, 0x32, 0xCF, 0xF7 + }; + HmacEval(pbKey, pbMsg, pbExpc, "1"); + + pbKey = new byte[131]; + for(int i = 0; i < pbKey.Length; ++i) pbKey[i] = 0xAA; + pbMsg = StrUtil.Utf8.GetBytes( + "This is a test using a larger than block-size key and " + + "a larger than block-size data. The key needs to be " + + "hashed before being used by the HMAC algorithm."); + pbExpc = new byte[32] { + 0x9B, 0x09, 0xFF, 0xA7, 0x1B, 0x94, 0x2F, 0xCB, + 0x27, 0x63, 0x5F, 0xBC, 0xD5, 0xB0, 0xE9, 0x44, + 0xBF, 0xDC, 0x63, 0x64, 0x4F, 0x07, 0x13, 0x93, + 0x8A, 0x7F, 0x51, 0x53, 0x5C, 0x3A, 0x35, 0xE2 + }; + HmacEval(pbKey, pbMsg, pbExpc, "2"); +#endif + } + +#if DEBUG + private static void HmacEval(byte[] pbKey, byte[] pbMsg, + byte[] pbExpc, string strID) + { + using(HMACSHA256 h = new HMACSHA256(pbKey)) + { + h.TransformBlock(pbMsg, 0, pbMsg.Length, pbMsg, 0); + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + byte[] pbHash = h.Hash; + if(!MemUtil.ArraysEqual(pbHash, pbExpc)) + throw new SecurityException("HMAC-SHA-256-" + strID); + + // Reuse the object + h.Initialize(); + h.TransformBlock(pbMsg, 0, pbMsg.Length, pbMsg, 0); + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + pbHash = h.Hash; + if(!MemUtil.ArraysEqual(pbHash, pbExpc)) + throw new SecurityException("HMAC-SHA-256-" + strID + "-R"); + } + } +#endif + + private static void TestNativeKeyTransform() + { +#if DEBUG + byte[] pbOrgKey = CryptoRandom.Instance.GetRandomBytes(32); + byte[] pbSeed = CryptoRandom.Instance.GetRandomBytes(32); + ulong uRounds = (ulong)((new Random()).Next(1, 0x3FFF)); + + byte[] pbManaged = new byte[32]; + Array.Copy(pbOrgKey, pbManaged, 32); + if(!AesKdf.TransformKeyManaged(pbManaged, pbSeed, uRounds)) + throw new SecurityException("AES-KDF-1"); + + byte[] pbNative = new byte[32]; + Array.Copy(pbOrgKey, pbNative, 32); + if(!NativeLib.TransformKey256(pbNative, pbSeed, uRounds)) + return; // Native library not available ("success") + + if(!MemUtil.ArraysEqual(pbManaged, pbNative)) + throw new SecurityException("AES-KDF-2"); +#endif + } + + private static void TestMemUtil() + { +#if DEBUG + Random r = new Random(); + byte[] pb = CryptoRandom.Instance.GetRandomBytes((uint)r.Next( + 0, 0x2FFFF)); + + byte[] pbCompressed = MemUtil.Compress(pb); + if(!MemUtil.ArraysEqual(MemUtil.Decompress(pbCompressed), pb)) + throw new InvalidOperationException("GZip"); + + Encoding enc = StrUtil.Utf8; + pb = enc.GetBytes("012345678901234567890a"); + byte[] pbN = enc.GetBytes("9012"); + if(MemUtil.IndexOf(pb, pbN) != 9) + throw new InvalidOperationException("MemUtil-1"); + pbN = enc.GetBytes("01234567890123"); + if(MemUtil.IndexOf(pb, pbN) != 0) + throw new InvalidOperationException("MemUtil-2"); + pbN = enc.GetBytes("a"); + if(MemUtil.IndexOf(pb, pbN) != 21) + throw new InvalidOperationException("MemUtil-3"); + pbN = enc.GetBytes("0a"); + if(MemUtil.IndexOf(pb, pbN) != 20) + throw new InvalidOperationException("MemUtil-4"); + pbN = enc.GetBytes("1"); + if(MemUtil.IndexOf(pb, pbN) != 1) + throw new InvalidOperationException("MemUtil-5"); + pbN = enc.GetBytes("b"); + if(MemUtil.IndexOf(pb, pbN) >= 0) + throw new InvalidOperationException("MemUtil-6"); + pbN = enc.GetBytes("012b"); + if(MemUtil.IndexOf(pb, pbN) >= 0) + throw new InvalidOperationException("MemUtil-7"); + + byte[] pbRes = MemUtil.ParseBase32("MY======"); + byte[] pbExp = Encoding.ASCII.GetBytes("f"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-1"); + + pbRes = MemUtil.ParseBase32("MZXQ===="); + pbExp = Encoding.ASCII.GetBytes("fo"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-2"); + + pbRes = MemUtil.ParseBase32("MZXW6==="); + pbExp = Encoding.ASCII.GetBytes("foo"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-3"); + + pbRes = MemUtil.ParseBase32("MZXW6YQ="); + pbExp = Encoding.ASCII.GetBytes("foob"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-4"); + + pbRes = MemUtil.ParseBase32("MZXW6YTB"); + pbExp = Encoding.ASCII.GetBytes("fooba"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-5"); + + pbRes = MemUtil.ParseBase32("MZXW6YTBOI======"); + pbExp = Encoding.ASCII.GetBytes("foobar"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-6"); + + pbRes = MemUtil.ParseBase32("JNSXSIDQOJXXM2LEMVZCAYTBONSWIIDPNYQG63TFFV2GS3LFEBYGC43TO5XXEZDTFY======"); + pbExp = Encoding.ASCII.GetBytes("Key provider based on one-time passwords."); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-7"); + + int i = 0 - 0x10203040; + pbRes = MemUtil.Int32ToBytes(i); + if(MemUtil.ByteArrayToHexString(pbRes) != "C0CFDFEF") + throw new Exception("MemUtil-8"); // Must be little-endian + if(MemUtil.BytesToUInt32(pbRes) != (uint)i) + throw new Exception("MemUtil-9"); + if(MemUtil.BytesToInt32(pbRes) != i) + throw new Exception("MemUtil-10"); +#endif + } + + private static void TestHmacOtp() + { +#if (DEBUG && !KeePassLibSD) + byte[] pbSecret = StrUtil.Utf8.GetBytes("12345678901234567890"); + string[] vExp = new string[]{ "755224", "287082", "359152", + "969429", "338314", "254676", "287922", "162583", "399871", + "520489" }; + + for(int i = 0; i < vExp.Length; ++i) + { + if(HmacOtp.Generate(pbSecret, (ulong)i, 6, false, -1) != vExp[i]) + throw new InvalidOperationException("HmacOtp"); + } +#endif + } + + private static void TestProtectedObjects() + { +#if DEBUG + Encoding enc = StrUtil.Utf8; + + byte[] pbData = enc.GetBytes("Test Test Test Test"); + ProtectedBinary pb = new ProtectedBinary(true, pbData); + if(!pb.IsProtected) throw new SecurityException("ProtectedBinary-1"); + + byte[] pbDec = pb.ReadData(); + if(!MemUtil.ArraysEqual(pbData, pbDec)) + throw new SecurityException("ProtectedBinary-2"); + if(!pb.IsProtected) throw new SecurityException("ProtectedBinary-3"); + + byte[] pbData2 = enc.GetBytes("Test Test Test Test"); + byte[] pbData3 = enc.GetBytes("Test Test Test Test Test"); + ProtectedBinary pb2 = new ProtectedBinary(true, pbData2); + ProtectedBinary pb3 = new ProtectedBinary(true, pbData3); + if(!pb.Equals(pb2)) throw new SecurityException("ProtectedBinary-4"); + if(pb.Equals(pb3)) throw new SecurityException("ProtectedBinary-5"); + if(pb2.Equals(pb3)) throw new SecurityException("ProtectedBinary-6"); + + if(pb.GetHashCode() != pb2.GetHashCode()) + throw new SecurityException("ProtectedBinary-7"); + if(!((object)pb).Equals((object)pb2)) + throw new SecurityException("ProtectedBinary-8"); + if(((object)pb).Equals((object)pb3)) + throw new SecurityException("ProtectedBinary-9"); + if(((object)pb2).Equals((object)pb3)) + throw new SecurityException("ProtectedBinary-10"); + + ProtectedString ps = new ProtectedString(); + if(ps.Length != 0) throw new SecurityException("ProtectedString-1"); + if(!ps.IsEmpty) throw new SecurityException("ProtectedString-2"); + if(ps.ReadString().Length != 0) + throw new SecurityException("ProtectedString-3"); + + ps = new ProtectedString(true, "Test"); + ProtectedString ps2 = new ProtectedString(true, enc.GetBytes("Test")); + if(ps.IsEmpty) throw new SecurityException("ProtectedString-4"); + pbData = ps.ReadUtf8(); + pbData2 = ps2.ReadUtf8(); + if(!MemUtil.ArraysEqual(pbData, pbData2)) + throw new SecurityException("ProtectedString-5"); + if(pbData.Length != 4) + throw new SecurityException("ProtectedString-6"); + if(ps.ReadString() != ps2.ReadString()) + throw new SecurityException("ProtectedString-7"); + pbData = ps.ReadUtf8(); + pbData2 = ps2.ReadUtf8(); + if(!MemUtil.ArraysEqual(pbData, pbData2)) + throw new SecurityException("ProtectedString-8"); + if(!ps.IsProtected) throw new SecurityException("ProtectedString-9"); + if(!ps2.IsProtected) throw new SecurityException("ProtectedString-10"); + + Random r = new Random(); + string str = string.Empty; + ps = new ProtectedString(); + for(int i = 0; i < 100; ++i) + { + bool bProt = ((r.Next() % 4) != 0); + ps = ps.WithProtection(bProt); + + int x = r.Next(str.Length + 1); + int c = r.Next(20); + char ch = (char)r.Next(1, 256); + + string strIns = new string(ch, c); + str = str.Insert(x, strIns); + ps = ps.Insert(x, strIns); + + if(ps.IsProtected != bProt) + throw new SecurityException("ProtectedString-11"); + if(ps.ReadString() != str) + throw new SecurityException("ProtectedString-12"); + + ps = ps.WithProtection(bProt); + + x = r.Next(str.Length); + c = r.Next(str.Length - x + 1); + + str = str.Remove(x, c); + ps = ps.Remove(x, c); + + if(ps.IsProtected != bProt) + throw new SecurityException("ProtectedString-13"); + if(ps.ReadString() != str) + throw new SecurityException("ProtectedString-14"); + } +#endif + } + + private static void TestStrUtil() + { +#if DEBUG + string[] vSeps = new string[]{ "ax", "b", "c" }; + const string str1 = "axbqrstcdeax"; + List v1 = StrUtil.SplitWithSep(str1, vSeps, true); + + if(v1.Count != 9) throw new InvalidOperationException("StrUtil-1"); + if(v1[0].Length > 0) throw new InvalidOperationException("StrUtil-2"); + if(!v1[1].Equals("ax")) throw new InvalidOperationException("StrUtil-3"); + if(v1[2].Length > 0) throw new InvalidOperationException("StrUtil-4"); + if(!v1[3].Equals("b")) throw new InvalidOperationException("StrUtil-5"); + if(!v1[4].Equals("qrst")) throw new InvalidOperationException("StrUtil-6"); + if(!v1[5].Equals("c")) throw new InvalidOperationException("StrUtil-7"); + if(!v1[6].Equals("de")) throw new InvalidOperationException("StrUtil-8"); + if(!v1[7].Equals("ax")) throw new InvalidOperationException("StrUtil-9"); + if(v1[8].Length > 0) throw new InvalidOperationException("StrUtil-10"); + + const string str2 = "12ab56"; + List v2 = StrUtil.SplitWithSep(str2, new string[]{ "AB" }, false); + if(v2.Count != 3) throw new InvalidOperationException("StrUtil-11"); + if(!v2[0].Equals("12")) throw new InvalidOperationException("StrUtil-12"); + if(!v2[1].Equals("AB")) throw new InvalidOperationException("StrUtil-13"); + if(!v2[2].Equals("56")) throw new InvalidOperationException("StrUtil-14"); + + List v3 = StrUtil.SplitWithSep("pqrs", vSeps, false); + if(v3.Count != 1) throw new InvalidOperationException("StrUtil-15"); + if(!v3[0].Equals("pqrs")) throw new InvalidOperationException("StrUtil-16"); + + if(StrUtil.VersionToString(0x000F000E000D000CUL) != "15.14.13.12") + throw new InvalidOperationException("StrUtil-V1"); + if(StrUtil.VersionToString(0x00FF000E00010000UL) != "255.14.1") + throw new InvalidOperationException("StrUtil-V2"); + if(StrUtil.VersionToString(0x000F00FF00000000UL) != "15.255") + throw new InvalidOperationException("StrUtil-V3"); + if(StrUtil.VersionToString(0x00FF000000000000UL) != "255") + throw new InvalidOperationException("StrUtil-V4"); + if(StrUtil.VersionToString(0x00FF000000000000UL, 2) != "255.0") + throw new InvalidOperationException("StrUtil-V5"); + if(StrUtil.VersionToString(0x0000000000070000UL) != "0.0.7") + throw new InvalidOperationException("StrUtil-V6"); + if(StrUtil.VersionToString(0x0000000000000000UL) != "0") + throw new InvalidOperationException("StrUtil-V7"); + if(StrUtil.VersionToString(0x00000000FFFF0000UL, 4) != "0.0.65535.0") + throw new InvalidOperationException("StrUtil-V8"); + if(StrUtil.VersionToString(0x0000000000000000UL, 4) != "0.0.0.0") + throw new InvalidOperationException("StrUtil-V9"); + + if(StrUtil.RtfEncodeChar('\u0000') != "\\u0?") + throw new InvalidOperationException("StrUtil-Rtf1"); + if(StrUtil.RtfEncodeChar('\u7FFF') != "\\u32767?") + throw new InvalidOperationException("StrUtil-Rtf2"); + if(StrUtil.RtfEncodeChar('\u8000') != "\\u-32768?") + throw new InvalidOperationException("StrUtil-Rtf3"); + if(StrUtil.RtfEncodeChar('\uFFFF') != "\\u-1?") + throw new InvalidOperationException("StrUtil-Rtf4"); + + if(!StrUtil.StringToBool(Boolean.TrueString)) + throw new InvalidOperationException("StrUtil-Bool1"); + if(StrUtil.StringToBool(Boolean.FalseString)) + throw new InvalidOperationException("StrUtil-Bool2"); + + if(StrUtil.Count("Abracadabra", "a") != 4) + throw new InvalidOperationException("StrUtil-Count1"); + if(StrUtil.Count("Bla", "U") != 0) + throw new InvalidOperationException("StrUtil-Count2"); + if(StrUtil.Count("AAAAA", "AA") != 4) + throw new InvalidOperationException("StrUtil-Count3"); + + const string sU = "data:mytype;base64,"; + if(!StrUtil.IsDataUri(sU)) + throw new InvalidOperationException("StrUtil-DataUri1"); + if(!StrUtil.IsDataUri(sU, "mytype")) + throw new InvalidOperationException("StrUtil-DataUri2"); + if(StrUtil.IsDataUri(sU, "notmytype")) + throw new InvalidOperationException("StrUtil-DataUri3"); + + uint u = 0x7FFFFFFFU; + if(u.ToString(NumberFormatInfo.InvariantInfo) != "2147483647") + throw new InvalidOperationException("StrUtil-Inv1"); + if(uint.MaxValue.ToString(NumberFormatInfo.InvariantInfo) != + "4294967295") + throw new InvalidOperationException("StrUtil-Inv2"); + if(long.MinValue.ToString(NumberFormatInfo.InvariantInfo) != + "-9223372036854775808") + throw new InvalidOperationException("StrUtil-Inv3"); + if(short.MinValue.ToString(NumberFormatInfo.InvariantInfo) != + "-32768") + throw new InvalidOperationException("StrUtil-Inv4"); + + if(!string.Equals("abcd", "aBcd", StrUtil.CaseIgnoreCmp)) + throw new InvalidOperationException("StrUtil-Case1"); + if(string.Equals(@"ab", StrUtil.CaseIgnoreCmp)) + throw new InvalidOperationException("StrUtil-Case2"); +#endif + } + + private static void TestUrlUtil() + { +#if DEBUG +#if !KeePassUAP + Debug.Assert(Uri.UriSchemeHttp.Equals("http", StrUtil.CaseIgnoreCmp)); + Debug.Assert(Uri.UriSchemeHttps.Equals("https", StrUtil.CaseIgnoreCmp)); +#endif + + if(UrlUtil.GetHost(@"scheme://domain:port/path?query_string#fragment_id") != + "domain") + throw new InvalidOperationException("UrlUtil-H1"); + if(UrlUtil.GetHost(@"http://example.org:80") != "example.org") + throw new InvalidOperationException("UrlUtil-H2"); + if(UrlUtil.GetHost(@"mailto:bob@example.com") != "example.com") + throw new InvalidOperationException("UrlUtil-H3"); + if(UrlUtil.GetHost(@"ftp://asmith@ftp.example.org") != "ftp.example.org") + throw new InvalidOperationException("UrlUtil-H4"); + if(UrlUtil.GetHost(@"scheme://username:password@domain:port/path?query_string#fragment_id") != + "domain") + throw new InvalidOperationException("UrlUtil-H5"); + if(UrlUtil.GetHost(@"bob@example.com") != "example.com") + throw new InvalidOperationException("UrlUtil-H6"); + if(UrlUtil.GetHost(@"s://u:p@d.tld:p/p?q#f") != "d.tld") + throw new InvalidOperationException("UrlUtil-H7"); + + if(NativeLib.IsUnix()) return; + + string strBase = "\\\\HOMESERVER\\Apps\\KeePass\\KeePass.exe"; + string strDoc = "\\\\HOMESERVER\\Documents\\KeePass\\NewDatabase.kdbx"; + string strRel = "..\\..\\Documents\\KeePass\\NewDatabase.kdbx"; + + string str = UrlUtil.MakeRelativePath(strBase, strDoc); + if(!str.Equals(strRel)) throw new InvalidOperationException("UrlUtil-R1"); + + str = UrlUtil.MakeAbsolutePath(strBase, strRel); + if(!str.Equals(strDoc)) throw new InvalidOperationException("UrlUtil-R2"); + + str = UrlUtil.GetQuotedAppPath(" \"Test\" \"%1\" "); + if(str != "Test") throw new InvalidOperationException("UrlUtil-Q1"); + str = UrlUtil.GetQuotedAppPath("C:\\Program Files\\Test.exe"); + if(str != "C:\\Program Files\\Test.exe") throw new InvalidOperationException("UrlUtil-Q2"); + str = UrlUtil.GetQuotedAppPath("Reg.exe \"Test\" \"Test 2\""); + if(str != "Reg.exe \"Test\" \"Test 2\"") throw new InvalidOperationException("UrlUtil-Q3"); +#endif + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Delegates/Handlers.cs b/src/KeePassLib2AndroidSdkStyle/Delegates/Handlers.cs new file mode 100644 index 00000000..5e8e3646 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Delegates/Handlers.cs @@ -0,0 +1,49 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; + +namespace KeePassLib.Delegates +{ + /// + /// Function definition of a method that performs an action on a group. + /// When traversing the internal tree, this function will be invoked + /// for all visited groups. + /// + /// Currently visited group. + /// You must return true if you want to continue the + /// traversal. If you want to immediately stop the whole traversal, + /// return false. + public delegate bool GroupHandler(PwGroup pg); + + /// + /// Function definition of a method that performs an action on an entry. + /// When traversing the internal tree, this function will be invoked + /// for all visited entries. + /// + /// Currently visited entry. + /// You must return true if you want to continue the + /// traversal. If you want to immediately stop the whole traversal, + /// return false. + public delegate bool EntryHandler(PwEntry pe); + + public delegate void VoidDelegate(); + + public delegate string StrPwEntryDelegate(string str, PwEntry pe); +} diff --git a/src/KeePassLib2AndroidSdkStyle/IDatabaseFormat.cs b/src/KeePassLib2AndroidSdkStyle/IDatabaseFormat.cs new file mode 100644 index 00000000..f9712a57 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/IDatabaseFormat.cs @@ -0,0 +1,28 @@ +using System.IO; +using KeePassLib.Interfaces; +using KeePassLib.Keys; + +namespace KeePassLib +{ + public interface IDatabaseFormat + { + void PopulateDatabaseFromStream(PwDatabase db, Stream s, IStatusLogger slLogger); + + byte[] HashOfLastStream { get; } + + bool CanWrite { get; } + string SuccessMessage { get; } + void Save(PwDatabase kpDatabase, Stream stream); + + bool CanHaveEntriesInRootGroup { get; } + bool CanHaveMultipleAttachments { get; } + bool CanHaveCustomFields { get; } + bool HasDefaultUsername { get; } + bool HasDatabaseName { get; } + bool SupportsAttachmentKeys { get; } + bool SupportsTags { get; } + bool SupportsOverrideUrl { get; } + bool CanRecycle { get; } + bool SupportsTemplates { get; } + } +} \ No newline at end of file diff --git a/src/KeePassLib2AndroidSdkStyle/Interfaces/IDeepCloneable.cs b/src/KeePassLib2AndroidSdkStyle/Interfaces/IDeepCloneable.cs new file mode 100644 index 00000000..55753e77 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Interfaces/IDeepCloneable.cs @@ -0,0 +1,37 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; + +namespace KeePassLib.Interfaces +{ + /// + /// Interface for objects that are deeply cloneable. + /// + /// Reference type. + public interface IDeepCloneable where T : class + { + /// + /// Deeply clone the object. + /// + /// Cloned object. + T CloneDeep(); + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Interfaces/IStatusLogger.cs b/src/KeePassLib2AndroidSdkStyle/Interfaces/IStatusLogger.cs new file mode 100644 index 00000000..ae7258f6 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Interfaces/IStatusLogger.cs @@ -0,0 +1,105 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; + +namespace KeePassLib.Interfaces +{ + /// + /// Status message types. + /// + public enum LogStatusType + { + /// + /// Default type: simple information type. + /// + Info = 0, + + /// + /// Warning message. + /// + Warning, + + /// + /// Error message. + /// + Error, + + /// + /// Additional information. Depends on lines above. + /// + AdditionalInfo + } + + /// + /// Status logging interface. + /// + public interface IStatusLogger + { + /// + /// Function which needs to be called when logging is started. + /// + /// This string should roughly describe + /// the operation, of which the status is logged. + /// Specifies whether the + /// operation is written to the log or not. + void StartLogging(string strOperation, bool bWriteOperationToLog); + + /// + /// Function which needs to be called when logging is ended + /// (i.e. when no more messages will be logged and when the + /// percent value won't change any more). + /// + void EndLogging(); + + /// + /// Set the current progress in percent. + /// + /// Percent of work finished. + /// Returns true if the caller should continue + /// the current work. + bool SetProgress(uint uPercent); + + /// + /// Set the current status text. + /// + /// Status text. + /// Type of the message. + /// Returns true if the caller should continue + /// the current work. + bool SetText(string strNewText, LogStatusType lsType); + + /// + /// Check if the user cancelled the current work. + /// + /// Returns true if the caller should continue + /// the current work. + bool ContinueWork(); + } + + public sealed class NullStatusLogger : IStatusLogger + { + public void StartLogging(string strOperation, bool bWriteOperationToLog) { } + public void EndLogging() { } + public bool SetProgress(uint uPercent) { return true; } + public bool SetText(string strNewText, LogStatusType lsType) { return true; } + public bool ContinueWork() { return true; } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Interfaces/IStructureItem.cs b/src/KeePassLib2AndroidSdkStyle/Interfaces/IStructureItem.cs new file mode 100644 index 00000000..60a8dd22 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Interfaces/IStructureItem.cs @@ -0,0 +1,42 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; + +namespace KeePassLib.Interfaces +{ + public interface IStructureItem : ITimeLogger // Provides LocationChanged + { + PwUuid Uuid + { + get; + set; + } + + PwGroup ParentGroup + { + get; + } + + PwUuid PreviousParentGroup + { + get; + } + } +} \ No newline at end of file diff --git a/src/KeePassLib2AndroidSdkStyle/Interfaces/ITimeLogger.cs b/src/KeePassLib2AndroidSdkStyle/Interfaces/ITimeLogger.cs new file mode 100644 index 00000000..ca4b7e18 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Interfaces/ITimeLogger.cs @@ -0,0 +1,105 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; + +namespace KeePassLib.Interfaces +{ + /// + /// Interface for objects that support various times (creation time, last + /// access time, last modification time and expiry time). Offers + /// several helper functions (for example a function to touch the current + /// object). + /// + public interface ITimeLogger + { + /// + /// The date/time when the object was created. + /// + DateTime CreationTime + { + get; + set; + } + + /// + /// The date/time when the object was last modified. + /// + DateTime LastModificationTime + { + get; + set; + } + + /// + /// The date/time when the object was last accessed. + /// + DateTime LastAccessTime + { + get; + set; + } + + /// + /// The date/time when the object expires. + /// + DateTime ExpiryTime + { + get; + set; + } + + /// + /// Flag that determines if the object does expire. + /// + bool Expires + { + get; + set; + } + + /// + /// Get or set the usage count of the object. To increase the usage + /// count by one, use the Touch function. + /// + ulong UsageCount + { + get; + set; + } + + /// + /// The date/time when the location of the object was last changed. + /// + DateTime LocationChanged + { + get; + set; + } + + /// + /// Touch the object. This function updates the internal last access + /// time. If the parameter is true, + /// the last modification time gets updated, too. Each time you call + /// Touch, the usage count of the object is increased by one. + /// + /// Update last modification time. + void Touch(bool bModified); + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Interfaces/IUIOperations.cs b/src/KeePassLib2AndroidSdkStyle/Interfaces/IUIOperations.cs new file mode 100644 index 00000000..18ec2cec --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Interfaces/IUIOperations.cs @@ -0,0 +1,37 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace KeePassLib.Interfaces +{ + public interface IUIOperations + { + /// + /// Let the user interface save the current database. + /// + /// If true, the UI will not ask for + /// whether to synchronize or overwrite, it'll simply overwrite the + /// file. + /// Returns true if the file has been saved. + bool UIFileSave(bool bForceSave); + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Interfaces/IXmlSerializerEx.cs b/src/KeePassLib2AndroidSdkStyle/Interfaces/IXmlSerializerEx.cs new file mode 100644 index 00000000..b95f2a5c --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Interfaces/IXmlSerializerEx.cs @@ -0,0 +1,33 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Xml; + +namespace KeePassLib.Interfaces +{ + public interface IXmlSerializerEx + { + void Serialize(XmlWriter xmlWriter, object o); + object Deserialize(Stream s); + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/KeePassLib2AndroidSdkStyle.csproj b/src/KeePassLib2AndroidSdkStyle/KeePassLib2AndroidSdkStyle.csproj new file mode 100644 index 00000000..2ccc629a --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/KeePassLib2AndroidSdkStyle.csproj @@ -0,0 +1,29 @@ + + + net8.0-android + 21 + enable + enable + + + + + + + + + + + + + + Designer + MSBuild:UpdateGeneratedFiles + + + + + Component + + + \ No newline at end of file diff --git a/src/KeePassLib2AndroidSdkStyle/Keys/CompositeKey.cs b/src/KeePassLib2AndroidSdkStyle/Keys/CompositeKey.cs new file mode 100644 index 00000000..7e935df0 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Keys/CompositeKey.cs @@ -0,0 +1,279 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using KeePassLib.Cryptography; +using KeePassLib.Cryptography.KeyDerivation; +using KeePassLib.Resources; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Keys +{ + /// + /// Represents a key. A key can be build up using several user key data sources + /// like a password, a key file, the currently logged on user credentials, + /// the current computer ID, etc. + /// + public sealed class CompositeKey + { + private List m_vUserKeys = new List(); + + /// + /// List of all user keys contained in the current composite key. + /// + public IEnumerable UserKeys + { + get { return m_vUserKeys; } + } + + public uint UserKeyCount + { + get { return (uint)m_vUserKeys.Count; } + } + + /// + /// Construct a new, empty key object. + /// + public CompositeKey() + { + } + + // /// + // /// Deconstructor, clears up the key. + // /// + // ~CompositeKey() + // { + // Clear(); + // } + + // /// + // /// Clears the key. This function also erases all previously stored + // /// user key data objects. + // /// + // public void Clear() + // { + // foreach(IUserKey pKey in m_vUserKeys) + // pKey.Clear(); + // m_vUserKeys.Clear(); + // } + + /// + /// Add a user key. + /// + /// User key to add. + public void AddUserKey(IUserKey pKey) + { + Debug.Assert(pKey != null); if(pKey == null) throw new ArgumentNullException("pKey"); + + m_vUserKeys.Add(pKey); + } + + /// + /// Remove a user key. + /// + /// User key to remove. + /// Returns true if the key was removed successfully. + public bool RemoveUserKey(IUserKey pKey) + { + Debug.Assert(pKey != null); if(pKey == null) throw new ArgumentNullException("pKey"); + + Debug.Assert(m_vUserKeys.IndexOf(pKey) >= 0); + return m_vUserKeys.Remove(pKey); + } + + /// + /// Test whether the composite key contains a specific type of + /// user keys (password, key file, ...). If at least one user + /// key of that type is present, the function returns true. + /// + /// User key type. + /// Returns true, if the composite key contains + /// a user key of the specified type. + public bool ContainsType(Type tUserKeyType) + { + Debug.Assert(tUserKeyType != null); + if(tUserKeyType == null) throw new ArgumentNullException("tUserKeyType"); + + foreach(IUserKey pKey in m_vUserKeys) + { + if(pKey == null) { Debug.Assert(false); continue; } + +#if KeePassUAP + if(pKey.GetType() == tUserKeyType) + return true; +#else + if(tUserKeyType.IsInstanceOfType(pKey)) + return true; +#endif + } + + return false; + } + + /// + /// Get the first user key of a specified type. + /// + /// Type of the user key to get. + /// Returns the first user key of the specified type + /// or null if no key of that type is found. + public IUserKey GetUserKey(Type tUserKeyType) + { + Debug.Assert(tUserKeyType != null); + if(tUserKeyType == null) throw new ArgumentNullException("tUserKeyType"); + + foreach(IUserKey pKey in m_vUserKeys) + { + if(pKey == null) { Debug.Assert(false); continue; } + +#if KeePassUAP + if(pKey.GetType() == tUserKeyType) + return pKey; +#else + if(tUserKeyType.IsInstanceOfType(pKey)) + return pKey; +#endif + } + + return null; + } + + public T GetUserKey() where T : IUserKey + { + return (T) GetUserKey(typeof (T)); + } + + /// + /// Creates the composite key from the supplied user key sources (password, + /// key file, user account, computer ID, etc.). + /// + private byte[] CreateRawCompositeKey32(byte[] mPbMasterSeed, byte[] mPbKdfSeed) + { + ValidateUserKeys(); + + // Concatenate user key data + List lData = new List(); + int cbData = 0; + foreach(IUserKey pKey in m_vUserKeys) + { + if (pKey is ISeedBasedUserKey) + ((ISeedBasedUserKey)pKey).SetParams(mPbMasterSeed, mPbKdfSeed); + ProtectedBinary b = pKey.KeyData; + if(b != null) + { + byte[] pbKeyData = b.ReadData(); + lData.Add(pbKeyData); + cbData += pbKeyData.Length; + } + } + + byte[] pbAllData = new byte[cbData]; + int p = 0; + foreach(byte[] pbData in lData) + { + Array.Copy(pbData, 0, pbAllData, p, pbData.Length); + p += pbData.Length; + MemUtil.ZeroByteArray(pbData); + } + Debug.Assert(p == cbData); + + byte[] pbHash = CryptoUtil.HashSha256(pbAllData); + MemUtil.ZeroByteArray(pbAllData); + return pbHash; + } + + + /// + /// Generate a 32-byte (256-bit) key from the composite key. + /// + public ProtectedBinary GenerateKey32(KdfParameters p, byte[] mPbMasterSeed) + { + if(p == null) { Debug.Assert(false); throw new ArgumentNullException("p"); } + + + 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[] pbRaw32 = CreateRawCompositeKey32(mPbMasterSeed, kdf.GetSeed(p)); + if((pbRaw32 == null) || (pbRaw32.Length != 32)) + { Debug.Assert(false); return null; } + + + byte[] pbTrf32 = kdf.Transform(pbRaw32, p); + if(pbTrf32 == null) { Debug.Assert(false); return null; } + + if(pbTrf32.Length != 32) + { + Debug.Assert(false); + pbTrf32 = CryptoUtil.HashSha256(pbTrf32); + } + + ProtectedBinary pbRet = new ProtectedBinary(true, pbTrf32); + MemUtil.ZeroByteArray(pbTrf32); + MemUtil.ZeroByteArray(pbRaw32); + return pbRet; + } + + private void ValidateUserKeys() + { + int nAccounts = 0; + + foreach(IUserKey uKey in m_vUserKeys) + { + if(uKey is KcpUserAccount) + ++nAccounts; + } + + if(nAccounts >= 2) + { + Debug.Assert(false); + throw new InvalidOperationException(); + } + } + } + + public interface ISeedBasedUserKey + { + void SetParams(byte[] masterSeed, byte[] mPbKdfSeed); + } + + public sealed class InvalidCompositeKeyException : Exception + { + public override string Message + { + get + { + return KLRes.InvalidCompositeKey + MessageService.NewParagraph + + KLRes.InvalidCompositeKeyHint; + } + } + + /// + /// Construct a new invalid composite key exception. + /// + public InvalidCompositeKeyException() + { + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Keys/IUserKey.cs b/src/KeePassLib2AndroidSdkStyle/Keys/IUserKey.cs new file mode 100644 index 00000000..3f7af817 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Keys/IUserKey.cs @@ -0,0 +1,48 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; + +using KeePassLib.Security; + +namespace KeePassLib.Keys +{ + /// + /// Interface to a user key, like a password, key file data, etc. + /// + public interface IUserKey + { + /// + /// Get key data. Querying this property is fast (it returns a + /// reference to a cached ProtectedBinary object). + /// If no key data is available, null is returned. + /// + ProtectedBinary KeyData + { + get; + } + + // /// + // /// Clear the key and securely erase all security-critical information. + // /// + // void Clear(); + + uint GetMinKdbxVersion(); + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Keys/KcpCustomKey.cs b/src/KeePassLib2AndroidSdkStyle/Keys/KcpCustomKey.cs new file mode 100644 index 00000000..fc26e24a --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Keys/KcpCustomKey.cs @@ -0,0 +1,73 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Cryptography; +using KeePassLib.Security; + +namespace KeePassLib.Keys +{ + public sealed class KcpCustomKey : IUserKey + { + private readonly string m_strName; + private ProtectedBinary m_pbKey; + + /// + /// Name of the provider that generated the custom key. + /// + public string Name + { + get { return m_strName; } + } + + public ProtectedBinary KeyData + { + get { return m_pbKey; } + } + + public uint GetMinKdbxVersion() + { + return 0; + } + + public KcpCustomKey(string strName, byte[] pbKeyData, bool bPerformHash) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + Debug.Assert(pbKeyData != null); if(pbKeyData == null) throw new ArgumentNullException("pbKeyData"); + + m_strName = strName; + + if(bPerformHash) + { + byte[] pbRaw = CryptoUtil.HashSha256(pbKeyData); + m_pbKey = new ProtectedBinary(true, pbRaw); + } + else m_pbKey = new ProtectedBinary(true, pbKeyData); + } + + // public void Clear() + // { + // m_pbKey = null; + // } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Keys/KcpKeyFile.Xml.cs b/src/KeePassLib2AndroidSdkStyle/Keys/KcpKeyFile.Xml.cs new file mode 100644 index 00000000..1cc60545 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Keys/KcpKeyFile.Xml.cs @@ -0,0 +1,281 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Xml.Serialization; + +using KeePassLib.Cryptography; +using KeePassLib.Resources; +using KeePassLib.Serialization; +using KeePassLib.Utility; + +namespace KeePassLib.Keys +{ + [XmlType("KeyFile")] + public sealed class KfxFile + { + private const ulong KfxVersionCriticalMask = 0xFFFF000000000000UL; + private const int KfxDataHashLength = 4; + + private KfxMeta m_meta = new KfxMeta(); + public KfxMeta Meta + { + get { return m_meta; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_meta = value; + } + } + + private KfxKey m_key = new KfxKey(); + public KfxKey Key + { + get { return m_key; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_key = value; + } + } + + public static KfxFile Create(ulong uVersion, byte[] pbKey, byte[] pbHash) + { + if(pbKey == null) throw new ArgumentNullException("pbKey"); + if(pbKey.Length == 0) throw new ArgumentOutOfRangeException("pbKey"); + + if(uVersion == 0) uVersion = 0x0002000000000000; + + // Null hash: generate one, empty hash: store no hash + if(pbHash == null) pbHash = HashData(pbKey); + VerifyHash(pbKey, pbHash); + + KfxFile kf = new KfxFile(); + + if(uVersion == 0x0001000000000000) + kf.Meta.Version = "1.00"; // KeePass <= 2.46 used two zeros + else kf.Meta.Version = StrUtil.VersionToString(uVersion, 2); + + if(uVersion == 0x0001000000000000) + kf.Key.Data.Value = Convert.ToBase64String(pbKey); + else if(uVersion == 0x0002000000000000) + { + kf.Key.Data.Value = FormatKeyHex(pbKey, 3); + + if(pbHash.Length != 0) + kf.Key.Data.Hash = MemUtil.ByteArrayToHexString(pbHash); + } + else throw new NotSupportedException(KLRes.FileVersionUnsupported); + + return kf; + } + + internal static KfxFile Create(ulong uVersion, string strKey, string strHash) + { + byte[] pbKey = ParseKey(uVersion, strKey); + byte[] pbHash = ((strHash != null) ? ParseHash(strHash) : null); + + return Create(uVersion, pbKey, pbHash); + } + + internal static bool CanLoad(string strFilePath) + { + if(string.IsNullOrEmpty(strFilePath)) { Debug.Assert(false); return false; } + + try + { + IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFilePath); + using(Stream s = IOConnection.OpenRead(ioc)) + { + return (Load(s) != null); + } + } + catch(Exception) { } + + return false; + } + + public static KfxFile Load(Stream s) + { + return XmlUtilEx.Deserialize(s); + } + + public void Save(Stream s) + { + XmlUtilEx.Serialize(s, this, true); + } + + private static string FormatKeyHex(byte[] pb, int cTabs) + { + StringBuilder sb = new StringBuilder(); + string str = MemUtil.ByteArrayToHexString(pb); + + for(int i = 0; i < str.Length; ++i) + { + if((i & 0x1F) == 0) + { + sb.AppendLine(); + sb.Append('\t', cTabs); + } + else if((i & 0x07) == 0) sb.Append(' '); + + sb.Append(str[i]); + } + + sb.AppendLine(); + if(cTabs > 0) sb.Append('\t', cTabs - 1); + return sb.ToString(); + } + + private ulong GetVersion() + { + string str = m_meta.Version; + if(string.IsNullOrEmpty(str)) return 0; + + return StrUtil.ParseVersion(str); + } + + public byte[] GetKey() + { + ulong uVersion = GetVersion(); + + byte[] pbKey = ParseKey(uVersion, m_key.Data.Value); + if((pbKey == null) || (pbKey.Length == 0)) + throw new FormatException(KLRes.FileCorrupted); + + byte[] pbHash = ParseHash(m_key.Data.Hash); + VerifyHash(pbKey, pbHash); + + return pbKey; + } + + private static byte[] HashData(byte[] pb) + { + return MemUtil.Mid(CryptoUtil.HashSha256(pb), 0, KfxDataHashLength); + } + + private static void VerifyHash(byte[] pbKey, byte[] pbHash) + { + // The hash is optional; empty hash means success + if((pbHash == null) || (pbHash.Length == 0)) return; + + byte[] pbHashCmp = HashData(pbKey); + if(!MemUtil.ArraysEqual(pbHash, pbHashCmp)) + throw new Exception("Keyfile hash mismatch!"); + } + + private static byte[] ParseKey(ulong uVersion, string strKey) + { + if(strKey == null) throw new ArgumentNullException("strKey"); + + strKey = StrUtil.RemoveWhiteSpace(strKey); + if(string.IsNullOrEmpty(strKey)) return MemUtil.EmptyByteArray; + + uVersion &= KfxVersionCriticalMask; + + byte[] pbKey; + if(uVersion == 0x0001000000000000) + pbKey = Convert.FromBase64String(strKey); + else if(uVersion == 0x0002000000000000) + pbKey = ParseHex(strKey); + else throw new NotSupportedException(KLRes.FileVersionUnsupported); + + return pbKey; + } + + private static byte[] ParseHash(string strHash) + { + return ParseHex(strHash); + } + + private static byte[] ParseHex(string str) + { + if(str == null) throw new ArgumentNullException("str"); + if(str.Length == 0) return MemUtil.EmptyByteArray; + + if(((str.Length & 1) != 0) || !StrUtil.IsHexString(str, true)) + throw new FormatException(); + + return MemUtil.HexStringToByteArray(str); + } + } + + public sealed class KfxMeta + { + private string m_strVersion = string.Empty; + [DefaultValue("")] + public string Version + { + get { return m_strVersion; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strVersion = value; + } + } + } + + public sealed class KfxKey + { + private KfxData m_data = new KfxData(); + public KfxData Data + { + get { return m_data; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_data = value; + } + } + } + + public sealed class KfxData + { + private string m_strHash = string.Empty; + [DefaultValue("")] + [XmlAttribute("Hash")] + public string Hash + { + get { return m_strHash; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strHash = value; + } + } + + private string m_strValue = string.Empty; + [DefaultValue("")] + [XmlText] + public string Value + { + get { return m_strValue; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strValue = value; + } + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Keys/KcpKeyFile.cs b/src/KeePassLib2AndroidSdkStyle/Keys/KcpKeyFile.cs new file mode 100644 index 00000000..e69fe2a1 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Keys/KcpKeyFile.cs @@ -0,0 +1,358 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Diagnostics; +using System.IO; +using System.Security; +using System.Text; +using System.Xml; + +#if !KeePassUAP +using System.Security.Cryptography; +#endif + +using KeePassLib.Cryptography; +using KeePassLib.Resources; +using KeePassLib.Security; +using KeePassLib.Serialization; +using KeePassLib.Utility; +using InvalidDataException = KeePassLib.Serialization.InvalidDataException; + +namespace KeePassLib.Keys +{ + /// + /// Key files as provided by the user. + /// + public sealed class KcpKeyFile : IUserKey + { + private IOConnectionInfo m_ioc; + private ProtectedBinary m_pbKeyData; + private ProtectedBinary m_pbFileData; + + /// + /// Path to the key file. + /// + public string Path + { + get { return m_ioc.Path; } + } + + /// + /// Get key data. Querying this property is fast (it returns a + /// reference to a cached ProtectedBinary object). + /// If no key data is available, null is returned. + /// + public ProtectedBinary KeyData + { + get { return m_pbKeyData; } + } + + public uint GetMinKdbxVersion() + { + return 0; + } + + public IOConnectionInfo Ioc + { + get { return m_ioc; } + } + + public ProtectedBinary RawFileData + { + get { return m_pbFileData; } + } + + public KcpKeyFile(string strKeyFile) + { + Construct(IOConnectionInfo.FromPath(strKeyFile), false); + } + + public KcpKeyFile(string strKeyFile, bool bThrowIfDbFile) + { + Construct(IOConnectionInfo.FromPath(strKeyFile), bThrowIfDbFile); + } + + public KcpKeyFile(IOConnectionInfo iocKeyFile) + { + Construct(iocKeyFile, false); + } + + public KcpKeyFile(IOConnectionInfo iocKeyFile, bool bThrowIfDbFile) + { + Construct(iocKeyFile, bThrowIfDbFile); + } + + public KcpKeyFile(byte[] keyFileContents, IOConnectionInfo iocKeyFile, bool bThrowIfDbFile) + { + Construct(keyFileContents, iocKeyFile, bThrowIfDbFile); + } + + private void Construct(byte[] pbFileData, IOConnectionInfo iocKeyFile, bool bThrowIfDbFile) + { + if (pbFileData == null) throw new Java.IO.FileNotFoundException(); + m_pbFileData = new ProtectedBinary(true, pbFileData); + + if(bThrowIfDbFile && (pbFileData.Length >= 8)) + { + uint uSig1 = MemUtil.BytesToUInt32(MemUtil.Mid(pbFileData, 0, 4)); + uint uSig2 = MemUtil.BytesToUInt32(MemUtil.Mid(pbFileData, 4, 4)); + + if(((uSig1 == KdbxFile.FileSignature1) && + (uSig2 == KdbxFile.FileSignature2)) || + ((uSig1 == KdbxFile.FileSignaturePreRelease1) && + (uSig2 == KdbxFile.FileSignaturePreRelease2)) || + ((uSig1 == KdbxFile.FileSignatureOld1) && + (uSig2 == KdbxFile.FileSignatureOld2))) +#if KeePassLibSD + throw new Exception(KLRes.KeyFileDbSel); +#else + throw new InvalidDataException(KLRes.KeyFileDbSel); +#endif + } + + byte[] pbKey = LoadKeyFile(pbFileData); + + if (pbKey == null) throw new InvalidOperationException(); + + m_ioc = iocKeyFile; + m_pbKeyData = new ProtectedBinary(true, pbKey); + + MemUtil.ZeroByteArray(pbKey); + } + + private void Construct(IOConnectionInfo iocFile, bool bThrowIfDbFile) + { + byte[] pbFileData = IOConnection.ReadFile(iocFile); + Construct(pbFileData, iocFile, bThrowIfDbFile); + } + // public void Clear() + // { + // m_strPath = string.Empty; + // m_pbKeyData = null; + // } + + + private static byte[] LoadKeyFile(byte[] pbFileData) + { + if (pbFileData == null) throw new ArgumentNullException("pbFileData"); + + byte[] pbKey = LoadKeyFileXml(pbFileData); + if (pbKey != null) return pbKey; + + int cb = pbFileData.Length; + if (cb == 32) return pbFileData; + + if (cb == 64) + { + pbKey = LoadKeyFileHex(pbFileData); + if (pbKey != null) return pbKey; + } + + return CryptoUtil.HashSha256(pbFileData); + } + + private static byte[] LoadKeyFileXml(byte[] pbFileData) + { + KfxFile kf; + try + { + using (MemoryStream ms = new MemoryStream(pbFileData, false)) + { + kf = KfxFile.Load(ms); + } + } + catch (Exception) { return null; } + + // We have a syntactically valid XML key file; + // failing to verify the key should throw an exception + return ((kf != null) ? kf.GetKey() : null); + } + + private static byte[] LoadKeyFileHex(byte[] pbFileData) + { + if (pbFileData == null) { Debug.Assert(false); return null; } + + try + { + int cc = pbFileData.Length; + if ((cc & 1) != 0) { Debug.Assert(false); return null; } + + if (!StrUtil.IsHexString(pbFileData, true)) return null; + + string strHex = StrUtil.Utf8.GetString(pbFileData); + return MemUtil.HexStringToByteArray(strHex); + } + catch (Exception) { Debug.Assert(false); } + + 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. + public static void Create(string strFilePath, byte[] pbAdditionalEntropy) + { + byte[] pbKey32 = CryptoRandom.Instance.GetRandomBytes(32); + if(pbKey32 == null) throw new SecurityException(); + + byte[] pbFinalKey32; + if((pbAdditionalEntropy == null) || (pbAdditionalEntropy.Length == 0)) + pbFinalKey32 = pbKey32; + else + { + using(MemoryStream ms = new MemoryStream()) + { + MemUtil.Write(ms, pbAdditionalEntropy); + MemUtil.Write(ms, pbKey32); + + pbFinalKey32 = CryptoUtil.HashSha256(ms.ToArray()); + } + } + + CreateXmlKeyFile(strFilePath, pbFinalKey32); + } + + // ================================================================ + // XML Key Files + // ================================================================ + + // Sample XML file: + // + // + // + // 1.00 + // + // + // ySFoKuCcJblw8ie6RkMBdVCnAf4EedSch7ItujK6bmI= + // + // + + private const string RootElementName = "KeyFile"; + private const string MetaElementName = "Meta"; + private const string VersionElementName = "Version"; + private const string KeyElementName = "Key"; + private const string KeyDataElementName = "Data"; + + private static byte[] LoadXmlKeyFile(byte[] pbFileData) + { + if(pbFileData == null) { Debug.Assert(false); return null; } + + MemoryStream ms = new MemoryStream(pbFileData, false); + byte[] pbKeyData = null; + + try + { + XmlDocument doc = new XmlDocument() { XmlResolver = null }; + doc.Load(ms); + + XmlElement el = doc.DocumentElement; + if((el == null) || !el.Name.Equals(RootElementName)) return null; + if(el.ChildNodes.Count < 2) return null; + + foreach(XmlNode xmlChild in el.ChildNodes) + { + if(xmlChild.Name.Equals(MetaElementName)) { } // Ignore Meta + else if(xmlChild.Name == KeyElementName) + { + foreach(XmlNode xmlKeyChild in xmlChild.ChildNodes) + { + if(xmlKeyChild.Name == KeyDataElementName) + { + if(pbKeyData == null) + pbKeyData = Convert.FromBase64String(xmlKeyChild.InnerText); + } + } + } + } + } + catch(Exception) { pbKeyData = null; } + finally { ms.Close(); } + + return pbKeyData; + } + + private static void CreateXmlKeyFile(string strFile, byte[] pbKeyData) + { + Debug.Assert(strFile != null); + if(strFile == null) throw new ArgumentNullException("strFile"); + Debug.Assert(pbKeyData != null); + if(pbKeyData == null) throw new ArgumentNullException("pbKeyData"); + + IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFile); + Stream sOut = IOConnection.OpenWrite(ioc); + +#if KeePassUAP + XmlWriterSettings xws = new XmlWriterSettings(); + xws.Encoding = StrUtil.Utf8; + xws.Indent = false; + + XmlWriter xtw = XmlWriter.Create(sOut, xws); +#else + XmlTextWriter xtw = new XmlTextWriter(sOut, StrUtil.Utf8); +#endif + + xtw.WriteStartDocument(); + xtw.WriteWhitespace("\r\n"); + xtw.WriteStartElement(RootElementName); // KeyFile + xtw.WriteWhitespace("\r\n\t"); + + xtw.WriteStartElement(MetaElementName); // Meta + xtw.WriteWhitespace("\r\n\t\t"); + xtw.WriteStartElement(VersionElementName); // Version + xtw.WriteString("1.00"); + xtw.WriteEndElement(); // End Version + xtw.WriteWhitespace("\r\n\t"); + xtw.WriteEndElement(); // End Meta + xtw.WriteWhitespace("\r\n\t"); + + xtw.WriteStartElement(KeyElementName); // Key + xtw.WriteWhitespace("\r\n\t\t"); + + xtw.WriteStartElement(KeyDataElementName); // Data + xtw.WriteString(Convert.ToBase64String(pbKeyData)); + xtw.WriteEndElement(); // End Data + xtw.WriteWhitespace("\r\n\t"); + + xtw.WriteEndElement(); // End Key + xtw.WriteWhitespace("\r\n"); + + xtw.WriteEndElement(); // RootElementName + xtw.WriteWhitespace("\r\n"); + xtw.WriteEndDocument(); // End KeyFile + xtw.Close(); + + sOut.Close(); + } + + /// + /// Allows to change the ioc value (without reloading the data, assuming it's the same content) + /// + /// + public void ResetIoc(IOConnectionInfo newIoc) + { + m_ioc = newIoc; + } +} +} diff --git a/src/KeePassLib2AndroidSdkStyle/Keys/KcpPassword.cs b/src/KeePassLib2AndroidSdkStyle/Keys/KcpPassword.cs new file mode 100644 index 00000000..ac0be666 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Keys/KcpPassword.cs @@ -0,0 +1,106 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Cryptography; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Keys +{ + /// + /// Master password / passphrase as provided by the user. + /// + public sealed class KcpPassword : IUserKey + { + private ProtectedString m_psPassword; + private ProtectedBinary m_pbKeyData; + + /// + /// Get the password as protected string. + /// + public ProtectedString Password + { + get { return m_psPassword; } + } + + /// + /// Get key data. Querying this property is fast (it returns a + /// reference to a cached ProtectedBinary object). + /// If no key data is available, null is returned. + /// + public ProtectedBinary KeyData + { + get { return m_pbKeyData; } + } + + public uint GetMinKdbxVersion() + { + return 0; + } + + public KcpPassword(byte[] pbPasswordUtf8) + { + SetKey(pbPasswordUtf8); + } + + public KcpPassword(string strPassword) + { + SetKey(StrUtil.Utf8.GetBytes(strPassword)); + } + + private void SetKey(byte[] pbPasswordUtf8) + { + Debug.Assert(pbPasswordUtf8 != null); + if(pbPasswordUtf8 == null) throw new ArgumentNullException("pbPasswordUtf8"); + +#if (DEBUG && !KeePassLibSD) + Debug.Assert(ValidatePassword(pbPasswordUtf8)); +#endif + + byte[] pbRaw = CryptoUtil.HashSha256(pbPasswordUtf8); + + m_psPassword = new ProtectedString(true, pbPasswordUtf8); + m_pbKeyData = new ProtectedBinary(true, pbRaw); + } + + // public void Clear() + // { + // m_psPassword = null; + // m_pbKeyData = null; + // } + +#if (DEBUG && !KeePassLibSD) + private static bool ValidatePassword(byte[] pb) + { + try + { + string str = StrUtil.Utf8.GetString(pb); + return str.IsNormalized(NormalizationForm.FormC); + } + catch(Exception) { Debug.Assert(false); } + + return false; +} +#endif + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Keys/KcpUserAccount.cs b/src/KeePassLib2AndroidSdkStyle/Keys/KcpUserAccount.cs new file mode 100644 index 00000000..67bc9dc1 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Keys/KcpUserAccount.cs @@ -0,0 +1,115 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Diagnostics; +using System.Security; + +#if !KeePassUAP +using System.Security.Cryptography; +#endif + +using KeePassLib.Cryptography; +using KeePassLib.Resources; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Keys +{ + /// + /// A user key depending on the currently logged on Windows user account. + /// + public sealed class KcpUserAccount : IUserKey + { + private ProtectedBinary m_pbKeyData = null; + + // Constant initialization vector (unique for KeePass) + private static readonly byte[] m_pbEntropy = new byte[]{ + 0xDE, 0x13, 0x5B, 0x5F, 0x18, 0xA3, 0x46, 0x70, + 0xB2, 0x57, 0x24, 0x29, 0x69, 0x88, 0x98, 0xE6 + }; + + private const string UserKeyFileName = "ProtectedUserKey.bin"; + + /// + /// Get key data. Querying this property is fast (it returns a + /// reference to a cached ProtectedBinary object). + /// If no key data is available, null is returned. + /// + public ProtectedBinary KeyData + { + get { return m_pbKeyData; } + } + + public uint GetMinKdbxVersion() + { + return 0; + } + + /// + /// Construct a user account key. + /// + public KcpUserAccount() + { + throw new NotSupportedException("DataProtection not supported on MonoForAndroid!"); + } + + // public void Clear() + // { + // m_pbKeyData = null; + // } + + private static byte[] LoadUserKey(bool bShowWarning) + { + byte[] pbKey = null; + +#if !KeePassLibSD + try + { + throw new NotSupportedException("DataProtection not supported on MonoForAndroid!"); + } + catch (Exception exLoad) + { + if (bShowWarning) MessageService.ShowWarning(exLoad); + + pbKey = null; + } +#endif + + return pbKey; + } + + private static byte[] CreateUserKey() + { + byte[] pbKey = null; + +#if !KeePassLibSD + try + { + throw new NotSupportedException("DataProtection not supported on MonoForAndroid!"); + } + catch (Exception) { pbKey = null; } +#endif + + return pbKey; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Keys/KeyProvider.cs b/src/KeePassLib2AndroidSdkStyle/Keys/KeyProvider.cs new file mode 100644 index 00000000..c331041f --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Keys/KeyProvider.cs @@ -0,0 +1,152 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +using KeePassLib.Serialization; + +namespace KeePassLib.Keys +{ + public sealed class KeyProviderQueryContext + { + private IOConnectionInfo m_ioInfo; + public IOConnectionInfo DatabaseIOInfo + { + get { return m_ioInfo; } + } + + public string DatabasePath + { + get { return m_ioInfo.Path; } + } + + private bool m_bCreatingNewKey; + public bool CreatingNewKey + { + get { return m_bCreatingNewKey; } + } + + private bool m_bSecDesktop; + public bool IsOnSecureDesktop + { + get { return m_bSecDesktop; } + } + + public KeyProviderQueryContext(IOConnectionInfo ioInfo, bool bCreatingNewKey, + bool bOnSecDesktop) + { + if(ioInfo == null) throw new ArgumentNullException("ioInfo"); + + m_ioInfo = ioInfo.CloneDeep(); + m_bCreatingNewKey = bCreatingNewKey; + m_bSecDesktop = bOnSecDesktop; + } + } + + public abstract class KeyProvider + { + /// + /// Name of your key provider (should be unique). + /// + public abstract string Name + { + get; + } + + /// + /// Property indicating whether the provider is exclusive. + /// If the provider is exclusive, KeePass doesn't allow other + /// key sources (master password, Windows user account, ...) + /// to be combined with the provider. + /// Key providers typically should return false + /// (to allow non-exclusive use), i.e. don't override this + /// property. + /// + public virtual bool Exclusive + { + get { return false; } + } + + /// + /// Property that specifies whether the returned key data + /// gets hashed by KeePass first or is written directly to + /// the user key data stream. + /// Standard key provider plugins should return false + /// (i.e. don't overwrite this property). Returning true + /// may cause severe security problems and is highly + /// discouraged. + /// + public virtual bool DirectKey + { + get { return false; } + } + + // public virtual PwIcon ImageIndex + // { + // get { return PwIcon.UserKey; } + // } + + /// + /// This property specifies whether the GetKey method might + /// show a form or dialog. If there is any chance that the method shows + /// one, this property must return true. Only if it's guaranteed + /// that the GetKey method doesn't show any form or dialog, this + /// property should return false. + /// + public virtual bool GetKeyMightShowGui + { + get { return true; } + } + + /// + /// This property specifies whether the key provider is compatible + /// with the secure desktop mode. This almost never is the case, + /// so you usually won't override this property. + /// + public virtual bool SecureDesktopCompatible + { + get { return false; } + } + + public abstract byte[] GetKey(KeyProviderQueryContext ctx); + } + +#if DEBUG + public sealed class SampleKeyProvider : KeyProvider + { + public override string Name + { + get { return "Built-In Sample Key Provider"; } + } + + // Do not just copy this to your own key provider class! See above. + public override bool GetKeyMightShowGui + { + get { return false; } + } + + public override byte[] GetKey(KeyProviderQueryContext ctx) + { + return new byte[]{ 2, 3, 5, 7, 11, 13 }; + } + } +#endif +} diff --git a/src/KeePassLib2AndroidSdkStyle/Keys/KeyProviderPool.cs b/src/KeePassLib2AndroidSdkStyle/Keys/KeyProviderPool.cs new file mode 100644 index 00000000..ba2e1bb2 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Keys/KeyProviderPool.cs @@ -0,0 +1,105 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace KeePassLib.Keys +{ + public sealed class KeyProviderPool : IEnumerable + { + private List m_vProviders = new List(); + + public int Count + { + get { return m_vProviders.Count; } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_vProviders.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return m_vProviders.GetEnumerator(); + } + + public void Add(KeyProvider prov) + { + Debug.Assert(prov != null); if(prov == null) throw new ArgumentNullException("prov"); + + m_vProviders.Add(prov); + } + + public bool Remove(KeyProvider prov) + { + Debug.Assert(prov != null); if(prov == null) throw new ArgumentNullException("prov"); + + return m_vProviders.Remove(prov); + } + + public KeyProvider Get(string strProviderName) + { + if(strProviderName == null) throw new ArgumentNullException("strProviderName"); + + foreach(KeyProvider prov in m_vProviders) + { + if(prov.Name == strProviderName) return prov; + } + + return null; + } + + public bool IsKeyProvider(string strName) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + foreach(KeyProvider prov in m_vProviders) + { + if(prov.Name == strName) return true; + } + + return false; + } + + internal byte[] GetKey(string strProviderName, KeyProviderQueryContext ctx, + out bool bPerformHash) + { + Debug.Assert(strProviderName != null); if(strProviderName == null) throw new ArgumentNullException("strProviderName"); + + bPerformHash = true; + + foreach(KeyProvider prov in m_vProviders) + { + if(prov.Name == strProviderName) + { + bPerformHash = !prov.DirectKey; + return prov.GetKey(ctx); + } + } + + Debug.Assert(false); + return null; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Keys/KeyValidator.cs b/src/KeePassLib2AndroidSdkStyle/Keys/KeyValidator.cs new file mode 100644 index 00000000..37a58942 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Keys/KeyValidator.cs @@ -0,0 +1,51 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace KeePassLib.Keys +{ + public enum KeyValidationType + { + MasterPassword = 0 + } + + public abstract class KeyValidator + { + /// + /// Name of your key validator (should be unique). + /// + public abstract string Name + { + get; + } + + /// + /// Validate a key. + /// + /// Key to validate. + /// Type of the validation to perform. + /// Returns null, if the validation is successful. + /// If there's a problem with the key, the returned string describes + /// the problem. + public abstract string Validate(string strKey, KeyValidationType t); + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Keys/KeyValidatorPool.cs b/src/KeePassLib2AndroidSdkStyle/Keys/KeyValidatorPool.cs new file mode 100644 index 00000000..0ce27cb4 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Keys/KeyValidatorPool.cs @@ -0,0 +1,86 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +using KeePassLib.Utility; + +namespace KeePassLib.Keys +{ + public sealed class KeyValidatorPool : IEnumerable + { + private List m_vValidators = new List(); + + public int Count + { + get { return m_vValidators.Count; } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_vValidators.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return m_vValidators.GetEnumerator(); + } + + public void Add(KeyValidator v) + { + Debug.Assert(v != null); if(v == null) throw new ArgumentNullException("v"); + + m_vValidators.Add(v); + } + + public bool Remove(KeyValidator v) + { + Debug.Assert(v != null); if(v == null) throw new ArgumentNullException("v"); + + return m_vValidators.Remove(v); + } + + public string Validate(string strKey, KeyValidationType t) + { + Debug.Assert(strKey != null); if(strKey == null) throw new ArgumentNullException("strKey"); + + foreach(KeyValidator v in m_vValidators) + { + string strResult = v.Validate(strKey, t); + if(strResult != null) return strResult; + } + + return null; + } + + public string Validate(byte[] pbKeyUtf8, KeyValidationType t) + { + Debug.Assert(pbKeyUtf8 != null); if(pbKeyUtf8 == null) throw new ArgumentNullException("pbKeyUtf8"); + + if(m_vValidators.Count == 0) return null; + + string strKey = StrUtil.Utf8.GetString(pbKeyUtf8, 0, pbKeyUtf8.Length); + return Validate(strKey, t); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Keys/UserKeyType.cs b/src/KeePassLib2AndroidSdkStyle/Keys/UserKeyType.cs new file mode 100644 index 00000000..6a58d4d2 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Keys/UserKeyType.cs @@ -0,0 +1,33 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; + +namespace KeePassLib.Keys +{ + [Flags] + public enum UserKeyType + { + None = 0, + Other = 1, + Password = 2, + KeyFile = 4, + UserAccount = 8 + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Kp2aLog.cs b/src/KeePassLib2AndroidSdkStyle/Kp2aLog.cs new file mode 100644 index 00000000..2226ee87 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Kp2aLog.cs @@ -0,0 +1,127 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.IO; +using Android; +using Android.App; +using Android.Content; +using Android.Preferences; +using KeePassLib.Serialization; + +namespace keepass2android +{ + public static class Kp2aLog + { + private static bool? _logToFile; + + private static object _fileLocker = new object(); + + public static void Log(string message) + { + if (message != null) + Android.Util.Log.Debug("KP2A", message); + if (LogToFile) + { + lock (_fileLocker) + { + try + { + using (var streamWriter = File.AppendText(LogFilename)) + { + string stringToLog = DateTime.Now + ":" + DateTime.Now.Millisecond + " -- " + message; + streamWriter.WriteLine(stringToLog); + } + } + catch (Exception e) + { + Android.Util.Log.Debug("KP2A", "Couldn't write to log file. " + e); + } + } + + } + + } + + public static string LogFilename + { + get { return Application.Context.FilesDir.CanonicalPath +"/keepass2android.log"; } + } + + public static bool LogToFile + { + get + { + if (_logToFile == null) + _logToFile = File.Exists(LogFilename); + return (bool) _logToFile; + } + } + public static event EventHandler OnUnexpectedError; + + public static void LogUnexpectedError(Exception exception) + { + Log(exception.ToString()); + if (OnUnexpectedError != null) + OnUnexpectedError(null, exception); + } + + public static void CreateLogFile() + { + if (!File.Exists(LogFilename)) + { + File.Create(LogFilename).Dispose(); + _logToFile = true; + } + + + } + + public static void FinishLogFile() + { + if (File.Exists(LogFilename)) + { + _logToFile = false; + int count = 0; + while (File.Exists(LogFilename + "." + count)) + count++; + File.Move(LogFilename, LogFilename + "." + count); + + } + + } + + public static void SendLog(Context ctx) + { + if (!File.Exists(LogFilename)) + return; + Intent sendIntent = new Intent(); + sendIntent.SetAction(Intent.ActionSend); + sendIntent.PutExtra(Intent.ExtraText, File.ReadAllText(LogFilename)); + sendIntent.PutExtra(Intent.ExtraEmail, "crocoapps@gmail.com"); + sendIntent.PutExtra(Intent.ExtraSubject, "Keepass2Android log"); + sendIntent.SetType("text/plain"); + ctx.StartActivity(Intent.CreateChooser(sendIntent, "Send log to...")); + } + + public static void LogTask(object task, string activityName) + { + Log($"Task in activity {activityName} changed to {task?.GetType()?.Name ?? "null"}"); + } + } +} \ No newline at end of file diff --git a/src/KeePassLib2AndroidSdkStyle/Native/NativeLib.cs b/src/KeePassLib2AndroidSdkStyle/Native/NativeLib.cs new file mode 100644 index 00000000..d1f9d3ed --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Native/NativeLib.cs @@ -0,0 +1,373 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.RegularExpressions; +using Java.Text; +using keepass2android; +#if !KeePassUAP +using System.IO; +using System.Threading; +#endif + +using KeePassLib.Utility; + +namespace KeePassLib.Native +{ + /// + /// Interface to native library (library containing fast versions of + /// several cryptographic functions). + /// + public static class NativeLib + { + private static bool m_bAllowNative = true; + + /// + /// If this property is set to true, the native library is used. + /// If it is false, all calls to functions in this class will fail. + /// + public static bool AllowNative + { + get { return m_bAllowNative; } + set { m_bAllowNative = value; } + } + + private static int? g_oiPointerSize = null; + /// + /// Size of a native pointer (in bytes). + /// + public static int PointerSize + { + get + { + if(!g_oiPointerSize.HasValue) +#if KeePassUAP + g_oiPointerSize = Marshal.SizeOf(); +#else + g_oiPointerSize = Marshal.SizeOf(typeof(IntPtr)); +#endif + return g_oiPointerSize.Value; + } + } + + private static ulong? m_ouMonoVersion = null; + public static ulong MonoVersion + { + get + { + if(m_ouMonoVersion.HasValue) return m_ouMonoVersion.Value; + + ulong uVersion = 0; + try + { + Type t = Type.GetType("Mono.Runtime"); + if(t != null) + { + MethodInfo mi = t.GetMethod("GetDisplayName", + BindingFlags.NonPublic | BindingFlags.Static); + if(mi != null) + { + string strName = (mi.Invoke(null, null) as string); + if(!string.IsNullOrEmpty(strName)) + { + Match m = Regex.Match(strName, "\\d+(\\.\\d+)+"); + if(m.Success) + uVersion = StrUtil.ParseVersion(m.Value); + else { Debug.Assert(false); } + } + else { Debug.Assert(false); } + } + else { Debug.Assert(false); } + } + } + catch(Exception) { Debug.Assert(false); } + + m_ouMonoVersion = uVersion; + return uVersion; + } + } + + /// + /// Determine if the native library is installed. + /// + /// Returns true, if the native library is installed. + public static bool IsLibraryInstalled() + { + byte[] pDummy0 = new byte[32]; + byte[] pDummy1 = new byte[32]; + + // Save the native state + bool bCachedNativeState = m_bAllowNative; + + // Temporarily allow native functions and try to load the library + m_bAllowNative = true; + bool bResult = TransformKey256(pDummy0, pDummy1, 16); + + // Pop native state and return result + m_bAllowNative = bCachedNativeState; + return bResult; + } + + private static bool? m_bIsUnix = null; + public static bool IsUnix() + { + if(m_bIsUnix.HasValue) return m_bIsUnix.Value; + + PlatformID p = GetPlatformID(); + + // Mono defines Unix as 128 in early .NET versions +#if !KeePassLibSD + m_bIsUnix = ((p == PlatformID.Unix) || (p == PlatformID.MacOSX) || + ((int)p == 128)); +#else + m_bIsUnix = (((int)p == 4) || ((int)p == 6) || ((int)p == 128)); +#endif + return m_bIsUnix.Value; + } + + private static PlatformID? m_platID = null; + public static PlatformID GetPlatformID() + { + if(m_platID.HasValue) return m_platID.Value; + +#if KeePassUAP + m_platID = EnvironmentExt.OSVersion.Platform; +#else + m_platID = Environment.OSVersion.Platform; +#endif + +#if (!KeePassLibSD && !KeePassUAP) + /*// Mono returns PlatformID.Unix on Mac OS X, workaround this + //not supported on Mono + if(m_platID.Value == PlatformID.Unix) + { + if((RunConsoleApp("uname", null) ?? string.Empty).Trim().Equals( + "Darwin", StrUtil.CaseIgnoreCmp)) + m_platID = PlatformID.MacOSX; + }*/ +#endif + + return m_platID.Value; + } + + private static DesktopType? m_tDesktop = null; + public static DesktopType GetDesktopType() + { + if(!m_tDesktop.HasValue) + { + DesktopType t = DesktopType.None; + if(!IsUnix()) t = DesktopType.Windows; + else + { + try + { + string strXdg = (Environment.GetEnvironmentVariable( + "XDG_CURRENT_DESKTOP") ?? string.Empty).Trim(); + string strGdm = (Environment.GetEnvironmentVariable( + "GDMSESSION") ?? string.Empty).Trim(); + StringComparison sc = StrUtil.CaseIgnoreCmp; + + if(strXdg.Equals("Unity", sc)) + t = DesktopType.Unity; + else if(strXdg.Equals("LXDE", sc)) + t = DesktopType.Lxde; + else if(strXdg.Equals("XFCE", sc)) + t = DesktopType.Xfce; + else if(strXdg.Equals("MATE", sc)) + t = DesktopType.Mate; + else if(strXdg.Equals("X-Cinnamon", sc)) + t = DesktopType.Cinnamon; + else if(strXdg.Equals("Pantheon", sc)) // Elementary OS + t = DesktopType.Pantheon; + else if(strXdg.Equals("KDE", sc) || // Mint 16 + strGdm.Equals("kde-plasma", sc)) // Ubuntu 12.04 + t = DesktopType.Kde; + else if(strXdg.Equals("GNOME", sc)) + { + if(strGdm.Equals("cinnamon", sc)) // Mint 13 + t = DesktopType.Cinnamon; + else t = DesktopType.Gnome; + } + } + catch(Exception) { Debug.Assert(false); } + } + + m_tDesktop = t; + } + + return m_tDesktop.Value; + } + +#if (!KeePassLibSD && !KeePassUAP) + /* Not supported on Android + public static string RunConsoleApp(string strAppPath, string strParams) + { + return RunConsoleApp(strAppPath, strParams, null); + } + public static string RunConsoleApp(string strAppPath, string strParams, + string strStdInput) + { + return RunConsoleApp(strAppPath, strParams, strStdInput, + (AppRunFlags.GetStdOutput | AppRunFlags.WaitForExit)); + } + */ + private delegate string RunProcessDelegate(); + + private static void EnsureNoBom(StreamWriter sw) + { + if(sw == null) { Debug.Assert(false); return; } + if(!MonoWorkarounds.IsRequired(1219)) return; + + try + { + Encoding enc = sw.Encoding; + if(enc == null) { Debug.Assert(false); return; } + byte[] pbBom = enc.GetPreamble(); + if((pbBom == null) || (pbBom.Length == 0)) return; + + // For Mono >= 4.0 (using Microsoft's reference source) + try + { + FieldInfo fi = typeof(StreamWriter).GetField("haveWrittenPreamble", + BindingFlags.Instance | BindingFlags.NonPublic); + if(fi != null) + { + fi.SetValue(sw, true); + return; + } + } + catch(Exception) { Debug.Assert(false); } + + // For Mono < 4.0 + FieldInfo fiPD = typeof(StreamWriter).GetField("preamble_done", + BindingFlags.Instance | BindingFlags.NonPublic); + if(fiPD != null) fiPD.SetValue(sw, true); + else { Debug.Assert(false); } + } + catch(Exception) { Debug.Assert(false); } + } +#endif + + /// + /// Transform a key. + /// + /// Source and destination buffer. + /// Key to use in the transformation. + /// Number of transformation rounds. + /// Returns true, if the key was transformed successfully. + /// + /// Transform a key. + /// + /// Source and destination buffer. + /// Key to use in the transformation. + /// Number of transformation rounds. + /// Returns true, if the key was transformed successfully. + public static bool TransformKey256(byte[] pBuf256, byte[] pKey256, + ulong uRounds) + { + if (m_bAllowNative == false) return false; + + try + { +#if !EXCLUDE_KEYTRANSFORM + Com.Keepassdroid.Crypto.Finalkey.NativeFinalKey key = new Com.Keepassdroid.Crypto.Finalkey.NativeFinalKey(); + + byte[] newKey = key.TransformMasterKey(pKey256, pBuf256, (int)uRounds); + Array.Copy(newKey, pBuf256, newKey.Length); +#else + Log.Warn("KP2A", "Transforming key managed!"); + return false; +#endif + } + catch (Exception e) + { + Kp2aLog.Log(e.ToString()); + return false; + } + + return true; + } + + /// + /// Benchmark key transformation. + /// + /// Number of milliseconds to perform the benchmark. + /// Number of transformations done. + /// Returns true, if the benchmark was successful. + public static bool TransformKeyBenchmark256(uint uTimeMs, out ulong puRounds) + { + puRounds = 0; + +#if KeePassUAP + return false; +#else + if(!m_bAllowNative) return false; + + try { puRounds = NativeMethods.TransformKeyBenchmark(uTimeMs); } + catch(Exception) { return false; } + + return true; +#endif + } + + private static KeyValuePair PrepareArrays256(byte[] pBuf256, + byte[] pKey256) + { + Debug.Assert((pBuf256 != null) && (pBuf256.Length == 32)); + if(pBuf256 == null) throw new ArgumentNullException("pBuf256"); + if(pBuf256.Length != 32) throw new ArgumentException(); + + Debug.Assert((pKey256 != null) && (pKey256.Length == 32)); + if(pKey256 == null) throw new ArgumentNullException("pKey256"); + if(pKey256.Length != 32) throw new ArgumentException(); + + IntPtr hBuf = Marshal.AllocHGlobal(pBuf256.Length); + Marshal.Copy(pBuf256, 0, hBuf, pBuf256.Length); + + IntPtr hKey = Marshal.AllocHGlobal(pKey256.Length); + Marshal.Copy(pKey256, 0, hKey, pKey256.Length); + + return new KeyValuePair(hBuf, hKey); + } + + private static void GetBuffers256(KeyValuePair kvpSource, + byte[] pbDestBuf, byte[] pbDestKey) + { + if(kvpSource.Key != IntPtr.Zero) + Marshal.Copy(kvpSource.Key, pbDestBuf, 0, pbDestBuf.Length); + + if(kvpSource.Value != IntPtr.Zero) + Marshal.Copy(kvpSource.Value, pbDestKey, 0, pbDestKey.Length); + } + + private static void FreeArrays(KeyValuePair kvpPointers) + { + if(kvpPointers.Key != IntPtr.Zero) + Marshal.FreeHGlobal(kvpPointers.Key); + + if(kvpPointers.Value != IntPtr.Zero) + Marshal.FreeHGlobal(kvpPointers.Value); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Native/NativeMethods.cs b/src/KeePassLib2AndroidSdkStyle/Native/NativeMethods.cs new file mode 100644 index 00000000..67aaa06d --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Native/NativeMethods.cs @@ -0,0 +1,202 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +using KeePassLib.Utility; + +namespace KeePassLib.Native +{ + internal static partial class NativeMethods + { + internal const int MAX_PATH = 260; + + // internal const uint TF_SFT_SHOWNORMAL = 0x00000001; + // internal const uint TF_SFT_HIDDEN = 0x00000008; + + /* [DllImport("KeePassNtv32.dll", EntryPoint = "TransformKey")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TransformKey32(IntPtr pBuf256, + IntPtr pKey256, UInt64 uRounds); + + [DllImport("KeePassNtv64.dll", EntryPoint = "TransformKey")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TransformKey64(IntPtr pBuf256, + IntPtr pKey256, UInt64 uRounds); + + internal static bool TransformKey(IntPtr pBuf256, IntPtr pKey256, + UInt64 uRounds) + { + if(Marshal.SizeOf(typeof(IntPtr)) == 8) + return TransformKey64(pBuf256, pKey256, uRounds); + else + return TransformKey32(pBuf256, pKey256, uRounds); + } + + [DllImport("KeePassNtv32.dll", EntryPoint = "TransformKeyTimed")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TransformKeyTimed32(IntPtr pBuf256, + IntPtr pKey256, ref UInt64 puRounds, UInt32 uSeconds); + + [DllImport("KeePassNtv64.dll", EntryPoint = "TransformKeyTimed")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TransformKeyTimed64(IntPtr pBuf256, + IntPtr pKey256, ref UInt64 puRounds, UInt32 uSeconds); + + internal static bool TransformKeyTimed(IntPtr pBuf256, IntPtr pKey256, + ref UInt64 puRounds, UInt32 uSeconds) + { + if(Marshal.SizeOf(typeof(IntPtr)) == 8) + return TransformKeyTimed64(pBuf256, pKey256, ref puRounds, uSeconds); + else + return TransformKeyTimed32(pBuf256, pKey256, ref puRounds, uSeconds); + } */ + +#if !KeePassUAP + [DllImport("KeePassLibC32.dll", EntryPoint = "TransformKey256")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TransformKey32(IntPtr pBuf256, + IntPtr pKey256, UInt64 uRounds); + + [DllImport("KeePassLibC64.dll", EntryPoint = "TransformKey256")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TransformKey64(IntPtr pBuf256, + IntPtr pKey256, UInt64 uRounds); + + internal static bool TransformKey(IntPtr pBuf256, IntPtr pKey256, + UInt64 uRounds) + { + if(NativeLib.PointerSize == 8) + return TransformKey64(pBuf256, pKey256, uRounds); + else + return TransformKey32(pBuf256, pKey256, uRounds); + } + + [DllImport("KeePassLibC32.dll", EntryPoint = "TransformKeyBenchmark256")] + private static extern UInt64 TransformKeyBenchmark32(UInt32 uTimeMs); + + [DllImport("KeePassLibC64.dll", EntryPoint = "TransformKeyBenchmark256")] + private static extern UInt64 TransformKeyBenchmark64(UInt32 uTimeMs); + + internal static UInt64 TransformKeyBenchmark(UInt32 uTimeMs) + { + if(NativeLib.PointerSize == 8) + return TransformKeyBenchmark64(uTimeMs); + return TransformKeyBenchmark32(uTimeMs); + } +#endif + + /* [DllImport("KeePassLibC32.dll", EntryPoint = "TF_ShowLangBar")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TF_ShowLangBar32(UInt32 dwFlags); + + [DllImport("KeePassLibC64.dll", EntryPoint = "TF_ShowLangBar")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TF_ShowLangBar64(UInt32 dwFlags); + + internal static bool TfShowLangBar(uint dwFlags) + { + if(Marshal.SizeOf(typeof(IntPtr)) == 8) + return TF_ShowLangBar64(dwFlags); + return TF_ShowLangBar32(dwFlags); + } */ + +#if (!KeePassLibSD && !KeePassUAP) + [DllImport("ShlWApi.dll", CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool PathRelativePathTo([Out] StringBuilder pszPath, + [In] string pszFrom, uint dwAttrFrom, [In] string pszTo, uint dwAttrTo); + + [DllImport("ShlWApi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] + private static extern int StrCmpLogicalW(string x, string y); + + private static bool? m_obSupportsLogicalCmp = null; + + private static void TestNaturalComparisonsSupport() + { + try + { + StrCmpLogicalW("0", "0"); // Throws exception if unsupported + m_obSupportsLogicalCmp = true; + } + catch(Exception) { m_obSupportsLogicalCmp = false; } + } +#endif + + internal static bool SupportsStrCmpNaturally + { + get + { +#if (!KeePassLibSD && !KeePassUAP) + if(!m_obSupportsLogicalCmp.HasValue) + TestNaturalComparisonsSupport(); + + return m_obSupportsLogicalCmp.Value; +#else + return false; +#endif + } + } + + internal static int StrCmpNaturally(string x, string y) + { +#if (!KeePassLibSD && !KeePassUAP) + if(!NativeMethods.SupportsStrCmpNaturally) + { + Debug.Assert(false); + return string.Compare(x, y, true); + } + + return StrCmpLogicalW(x, y); +#else + Debug.Assert(false); + return string.Compare(x, y, true); +#endif + } + + internal static string GetUserRuntimeDir() + { +#if KeePassLibSD + return Path.GetTempPath(); +#else +#if KeePassUAP + string strRtDir = EnvironmentExt.AppDataLocalFolderPath; +#else + string strRtDir = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); + if(string.IsNullOrEmpty(strRtDir)) + strRtDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + if(string.IsNullOrEmpty(strRtDir)) + { + Debug.Assert(false); + return Path.GetTempPath(); // Not UrlUtil (otherwise cyclic) + } +#endif + + strRtDir = UrlUtil.EnsureTerminatingSeparator(strRtDir, false); + strRtDir += PwDefs.ShortProductName; + + return strRtDir; +#endif + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/PwCustomIcon.cs b/src/KeePassLib2AndroidSdkStyle/PwCustomIcon.cs new file mode 100644 index 00000000..8dbf344e --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/PwCustomIcon.cs @@ -0,0 +1,123 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +#if !KeePassUAP +using System.Drawing; +#endif + +using KeePassLib.Utility; + +namespace KeePassLib +{ + public sealed class PwCustomIcon + { + // Recommended maximum sizes, not obligatory + internal const int MaxWidth = 128; + internal const int MaxHeight = 128; + + private readonly PwUuid m_uuid; + private readonly byte[] m_pbImageDataPng; + + private string m_strName = string.Empty; + private DateTime? m_odtLastMod = null; + + private Dictionary m_dImageCache = new Dictionary(); + + public PwUuid Uuid + { + get { return m_uuid; } + } + + public byte[] ImageDataPng + { + get { return m_pbImageDataPng; } + // When allowing 'set', do not copy the cache in 'Clone' + } + + public string Name + { + get { return m_strName; } + set + { + if (value == null) throw new ArgumentNullException("value"); + m_strName = value; + } + } + + public DateTime? LastModificationTime + { + get { return m_odtLastMod; } + set { m_odtLastMod = value; } + } + + [Obsolete("Use GetImage instead.")] + public Android.Graphics.Bitmap Image + { + get { return GetImage(); } // Backward compatibility + } + + public PwCustomIcon(PwUuid pu, byte[] pbImageDataPng) + { + if (pu == null) { Debug.Assert(false); throw new ArgumentNullException("pu"); } + if (pu.Equals(PwUuid.Zero)) { Debug.Assert(false); throw new ArgumentOutOfRangeException("pu"); } + if (pbImageDataPng == null) { Debug.Assert(false); throw new ArgumentNullException("pbImageDataPng"); } + + m_uuid = pu; + m_pbImageDataPng = pbImageDataPng; + } + + private static long GetKey(int w, int h) + { + return (((long)w << 32) ^ (long)h); + } + + /// + /// Get the icon as an Image (original size). + /// + public Android.Graphics.Bitmap GetImage() + { + const long lKey = -1; + + Android.Graphics.Bitmap img; + if (m_dImageCache.TryGetValue(lKey, out img)) return img; + + try { img = GfxUtil.LoadImage(m_pbImageDataPng); } + catch (Exception) { Debug.Assert(false); } + + m_dImageCache[lKey] = img; + return img; + } + + internal PwCustomIcon Clone() + { + PwCustomIcon ico = new PwCustomIcon(m_uuid, m_pbImageDataPng); + + ico.m_strName = m_strName; + ico.m_odtLastMod = m_odtLastMod; + + ico.m_dImageCache = m_dImageCache; // Same image data + + return ico; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/PwDatabase.cs b/src/KeePassLib2AndroidSdkStyle/PwDatabase.cs new file mode 100644 index 00000000..787a88e5 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/PwDatabase.cs @@ -0,0 +1,2140 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +#if !KeePassUAP +using System.Drawing; +#endif + +using KeePassLib.Collections; +using KeePassLib.Cryptography; +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.KeyDerivation; +using KeePassLib.Delegates; +using KeePassLib.Interfaces; +using KeePassLib.Keys; +using KeePassLib.Resources; +using KeePassLib.Security; +using KeePassLib.Serialization; +using KeePassLib.Utility; + +namespace KeePassLib +{ + /// + /// The core password manager class. It contains a number of groups, which + /// contain the actual entries. + /// + public sealed class PwDatabase + { + internal const int DefaultHistoryMaxItems = 10; // -1 = unlimited + internal const long DefaultHistoryMaxSize = 6 * 1024 * 1024; // -1 = unlimited + + private static bool m_bPrimaryCreated = false; + + // Initializations: see Clear() + private PwGroup m_pgRootGroup = null; + private PwObjectList m_vDeletedObjects = new PwObjectList(); + + private PwUuid m_uuidDataCipher = StandardAesEngine.AesUuid; + private PwCompressionAlgorithm m_caCompression = PwCompressionAlgorithm.GZip; + // private ulong m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; + private KdfParameters m_kdfParams = KdfPool.GetDefaultParameters(); + + private CompositeKey m_pwUserKey = null; + private MemoryProtectionConfig m_memProtConfig = new MemoryProtectionConfig(); + + private List m_vCustomIcons = new List(); + private bool m_bUINeedsIconUpdate = true; + + private DateTime m_dtSettingsChanged = PwDefs.DtDefaultNow; + private string m_strName = string.Empty; + private DateTime m_dtNameChanged = PwDefs.DtDefaultNow; + private string m_strDesc = string.Empty; + private DateTime m_dtDescChanged = PwDefs.DtDefaultNow; + private string m_strDefaultUserName = string.Empty; + private DateTime m_dtDefaultUserChanged = PwDefs.DtDefaultNow; + private uint m_uMntncHistoryDays = 365; + private Color m_clr = Color.Empty; + + private DateTime m_dtKeyLastChanged = PwDefs.DtDefaultNow; + private long m_lKeyChangeRecDays = -1; + private long m_lKeyChangeForceDays = -1; + private bool m_bKeyChangeForceOnce = false; + + private IOConnectionInfo m_ioSource = new IOConnectionInfo(); + private bool m_bDatabaseOpened = false; + private bool m_bModified = false; + + private PwUuid m_pwLastSelectedGroup = PwUuid.Zero; + private PwUuid m_pwLastTopVisibleGroup = PwUuid.Zero; + + private bool m_bUseRecycleBin = true; + private PwUuid m_pwRecycleBin = PwUuid.Zero; + private DateTime m_dtRecycleBinChanged = PwDefs.DtDefaultNow; + private PwUuid m_pwEntryTemplatesGroup = PwUuid.Zero; + private DateTime m_dtEntryTemplatesChanged = PwDefs.DtDefaultNow; + + private int m_nHistoryMaxItems = DefaultHistoryMaxItems; + private long m_lHistoryMaxSize = DefaultHistoryMaxSize; // In bytes + + private StringDictionaryEx m_dCustomData = new StringDictionaryEx(true); + private VariantDictionary m_dPublicCustomData = new VariantDictionary(); + + private byte[] m_pbHashOfFileOnDisk = null; + private byte[] m_pbHashOfLastIO = null; + + private bool m_bUseFileTransactions = false; + private bool m_bUseFileLocks = false; + + private IStatusLogger m_slStatus = null; + + private static string m_strLocalizedAppName = string.Empty; + + // private const string StrBackupExtension = ".bak"; + + /// + /// Get the root group that contains all groups and entries stored in the + /// database. + /// + /// Root group. The return value is null, if no database + /// has been opened. + public PwGroup RootGroup + { + get { return m_pgRootGroup; } + set + { + Debug.Assert(value != null); + if(value == null) throw new ArgumentNullException("value"); + + m_pgRootGroup = value; + } + } + + /// + /// IOConnection of the currently opened database file. + /// Is never null. + /// + public IOConnectionInfo IOConnectionInfo + { + get { return m_ioSource; } + } + + /// + /// If this is true, a database is currently open. + /// + public bool IsOpen + { + get { return m_bDatabaseOpened; } + } + + /// + /// Modification flag. If true, the class has been modified and the + /// user interface should prompt the user to save the changes before + /// closing the database for example. + /// + public bool Modified + { + get { return m_bModified; } + set { m_bModified = value; } + } + + /// + /// The user key used for database encryption. This key must be created + /// and set before using any of the database load/save functions. + /// + public CompositeKey MasterKey + { + get { return m_pwUserKey; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + + m_pwUserKey = value; + } + } + + public DateTime SettingsChanged + { + get { return m_dtSettingsChanged; } + set { m_dtSettingsChanged = value; } + } + + /// + /// Name of the database. + /// + public string Name + { + get { return m_strName; } + set + { + Debug.Assert(value != null); + if(value != null) m_strName = value; + } + } + + public DateTime NameChanged + { + get { return m_dtNameChanged; } + set { m_dtNameChanged = value; } + } + + /// + /// Database description. + /// + public string Description + { + get { return m_strDesc; } + set + { + Debug.Assert(value != null); + if(value != null) m_strDesc = value; + } + } + + public DateTime DescriptionChanged + { + get { return m_dtDescChanged; } + set { m_dtDescChanged = value; } + } + + /// + /// Default user name used for new entries. + /// + public string DefaultUserName + { + get { return m_strDefaultUserName; } + set + { + Debug.Assert(value != null); + if(value != null) m_strDefaultUserName = value; + } + } + + public DateTime DefaultUserNameChanged + { + get { return m_dtDefaultUserChanged; } + set { m_dtDefaultUserChanged = value; } + } + + /// + /// Number of days until history entries are being deleted + /// in a database maintenance operation. + /// + public uint MaintenanceHistoryDays + { + get { return m_uMntncHistoryDays; } + set { m_uMntncHistoryDays = value; } + } + + public Color Color + { + get { return m_clr; } + set { m_clr = value; } + } + + public DateTime MasterKeyChanged + { + get { return m_dtKeyLastChanged; } + set { m_dtKeyLastChanged = value; } + } + + public long MasterKeyChangeRec + { + get { return m_lKeyChangeRecDays; } + set { m_lKeyChangeRecDays = value; } + } + + public long MasterKeyChangeForce + { + get { return m_lKeyChangeForceDays; } + set { m_lKeyChangeForceDays = value; } + } + + public bool MasterKeyChangeForceOnce + { + get { return m_bKeyChangeForceOnce; } + set { m_bKeyChangeForceOnce = value; } + } + + /// + /// The encryption algorithm used to encrypt the data part of the database. + /// + public PwUuid DataCipherUuid + { + get { return m_uuidDataCipher; } + set + { + Debug.Assert(value != null); + if(value != null) m_uuidDataCipher = value; + } + } + + /// + /// Compression algorithm used to encrypt the data part of the database. + /// + public PwCompressionAlgorithm Compression + { + get { return m_caCompression; } + set { m_caCompression = value; } + } + + // /// + // /// Number of key transformation rounds (KDF parameter). + // /// + // public ulong KeyEncryptionRounds + // { + // get { return m_uKeyEncryptionRounds; } + // set { m_uKeyEncryptionRounds = value; } + // } + + public KdfParameters KdfParameters + { + get { return m_kdfParams; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_kdfParams = value; + } + } + + /// + /// Memory protection configuration (for default fields). + /// + public MemoryProtectionConfig MemoryProtection + { + get { return m_memProtConfig; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + + m_memProtConfig = value; + } + } + + /// + /// Get a list of all deleted objects. + /// + public PwObjectList DeletedObjects + { + get { return m_vDeletedObjects; } + } + + /// + /// Get all custom icons stored in this database. + /// + public List CustomIcons + { + get { return m_vCustomIcons; } + } + + /// + /// This is a dirty-flag for the UI. It is used to indicate when an + /// icon list update is required. + /// + public bool UINeedsIconUpdate + { + get { return m_bUINeedsIconUpdate; } + set { m_bUINeedsIconUpdate = value; } + } + + public PwUuid LastSelectedGroup + { + get { return m_pwLastSelectedGroup; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_pwLastSelectedGroup = value; + } + } + + public PwUuid LastTopVisibleGroup + { + get { return m_pwLastTopVisibleGroup; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_pwLastTopVisibleGroup = value; + } + } + + public bool RecycleBinEnabled + { + get { return m_bUseRecycleBin; } + set { m_bUseRecycleBin = value; } + } + + public PwUuid RecycleBinUuid + { + get { return m_pwRecycleBin; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_pwRecycleBin = value; + } + } + + public DateTime RecycleBinChanged + { + get { return m_dtRecycleBinChanged; } + set { m_dtRecycleBinChanged = value; } + } + + /// + /// UUID of the group containing template entries. May be + /// PwUuid.Zero, if no entry templates group has been specified. + /// + public PwUuid EntryTemplatesGroup + { + get { return m_pwEntryTemplatesGroup; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_pwEntryTemplatesGroup = value; + } + } + + public DateTime EntryTemplatesGroupChanged + { + get { return m_dtEntryTemplatesChanged; } + set { m_dtEntryTemplatesChanged = value; } + } + + public int HistoryMaxItems + { + get { return m_nHistoryMaxItems; } + set { m_nHistoryMaxItems = value; } + } + + public long HistoryMaxSize + { + get { return m_lHistoryMaxSize; } + set { m_lHistoryMaxSize = value; } + } + + /// + /// Custom data container that can be used by plugins to store + /// own data in KeePass databases. + /// The data is stored in the encrypted part of encrypted + /// database files. + /// Use unique names for your items, e.g. "PluginName_ItemName". + /// + public StringDictionaryEx CustomData + { + get { return m_dCustomData; } + internal set + { + if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dCustomData = value; + } + } + + /// + /// Custom data container that can be used by plugins to store + /// own data in KeePass databases. + /// The data is stored in the *unencrypted* part of database files, + /// and it is not supported by all file formats (e.g. supported by KDBX, + /// unsupported by XML). + /// It is highly recommended to use CustomData instead, + /// if possible. + /// Use unique names for your items, e.g. "PluginName_ItemName". + /// + public VariantDictionary PublicCustomData + { + get { return m_dPublicCustomData; } + internal set + { + if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dPublicCustomData = value; + } + } + + /// + /// Hash value of the primary file on disk (last read or last write). + /// A call to SaveAs without making the saved file primary will + /// not change this hash. May be null. + /// + public byte[] HashOfFileOnDisk + { + get { return m_pbHashOfFileOnDisk; } + set { m_pbHashOfFileOnDisk = value; } + } + + public byte[] HashOfLastIO + { + get { return m_pbHashOfLastIO; } + set { m_pbHashOfLastIO = value; } + } + + public bool UseFileLocks + { + get { return m_bUseFileLocks; } + set { m_bUseFileLocks = value; } + } + + private string m_strDetachBins = null; + /// + /// Detach binaries when opening a file. If this isn't null, + /// all binaries are saved to the specified path and are removed + /// from the database. + /// + public string DetachBinaries + { + get { return m_strDetachBins; } + set { m_strDetachBins = value; } + } + + /// + /// Localized application name. + /// + public static string LocalizedAppName + { + get { return m_strLocalizedAppName; } + set { Debug.Assert(value != null); m_strLocalizedAppName = value; } + } + + /// + /// Constructs an empty password manager object. + /// + public PwDatabase() + { + if(m_bPrimaryCreated == false) m_bPrimaryCreated = true; + + Clear(); + } + + private void Clear() + { + m_pgRootGroup = null; + m_vDeletedObjects = new PwObjectList(); + + m_uuidDataCipher = StandardAesEngine.AesUuid; + m_caCompression = PwCompressionAlgorithm.GZip; + // m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; + m_kdfParams = KdfPool.GetDefaultParameters(); + + m_pwUserKey = null; + m_memProtConfig = new MemoryProtectionConfig(); + + m_vCustomIcons = new List(); + m_bUINeedsIconUpdate = true; + + DateTime dtNow = DateTime.UtcNow; + + m_dtSettingsChanged = dtNow; + m_strName = string.Empty; + m_dtNameChanged = dtNow; + m_strDesc = string.Empty; + m_dtDescChanged = dtNow; + m_strDefaultUserName = string.Empty; + m_dtDefaultUserChanged = dtNow; + m_uMntncHistoryDays = 365; + m_clr = Color.Empty; + + m_dtKeyLastChanged = dtNow; + m_lKeyChangeRecDays = -1; + m_lKeyChangeForceDays = -1; + m_bKeyChangeForceOnce = false; + + m_ioSource = new IOConnectionInfo(); + m_bDatabaseOpened = false; + m_bModified = false; + + m_pwLastSelectedGroup = PwUuid.Zero; + m_pwLastTopVisibleGroup = PwUuid.Zero; + + m_bUseRecycleBin = true; + m_pwRecycleBin = PwUuid.Zero; + m_dtRecycleBinChanged = dtNow; + m_pwEntryTemplatesGroup = PwUuid.Zero; + m_dtEntryTemplatesChanged = dtNow; + + m_nHistoryMaxItems = DefaultHistoryMaxItems; + m_lHistoryMaxSize = DefaultHistoryMaxSize; + + m_dCustomData = new StringDictionaryEx(); + m_dPublicCustomData = new VariantDictionary(); + + m_pbHashOfFileOnDisk = null; + m_pbHashOfLastIO = null; + + m_bUseFileTransactions = false; + m_bUseFileLocks = false; + } + + /// + /// Initialize the class for managing a new database. Previously loaded + /// data is deleted. + /// + /// IO connection of the new database. + /// Key to open the database. + public void New(IOConnectionInfo ioConnection, CompositeKey pwKey, string filenameWithoutPathAndExt) + { + Debug.Assert(ioConnection != null); + if(ioConnection == null) throw new ArgumentNullException("ioConnection"); + Debug.Assert(pwKey != null); + if(pwKey == null) throw new ArgumentNullException("pwKey"); + + Close(); + + m_ioSource = ioConnection; + m_pwUserKey = pwKey; + + m_bDatabaseOpened = true; + m_bModified = true; + + m_pgRootGroup = new PwGroup(true, true, filenameWithoutPathAndExt, + PwIcon.FolderOpen); + m_pgRootGroup.IsExpanded = true; + } + + + /// + /// Open a database. The URL may point to any supported data source. + /// + /// IO connection to load the database from. + /// Key used to open the specified database. + /// Logger, which gets all status messages. + public void Open(Stream s, string fileNameWithoutPathAndExt, IOConnectionInfo ioSource, CompositeKey pwKey, + IStatusLogger slLogger, IDatabaseFormat format) + { + Debug.Assert(s != null); + if (s == null) throw new ArgumentNullException("s"); + Debug.Assert(fileNameWithoutPathAndExt != null); + if (fileNameWithoutPathAndExt == null) throw new ArgumentException("fileNameWithoutPathAndExt"); + Debug.Assert(pwKey != null); + Debug.Assert(ioSource != null); + if(ioSource == null) throw new ArgumentNullException("ioSource"); + Debug.Assert(pwKey != null); + if(pwKey == null) throw new ArgumentNullException("pwKey"); + + Close(); + + try + { + m_pgRootGroup = new PwGroup(true, true, fileNameWithoutPathAndExt, PwIcon.FolderOpen); + m_pgRootGroup.IsExpanded = true; + + m_pwUserKey = pwKey; + + m_bModified = false; + + format.PopulateDatabaseFromStream(this, s, slLogger); + + m_pbHashOfLastIO = format.HashOfLastStream; + m_pbHashOfFileOnDisk = format.HashOfLastStream; + + Debug.Assert(m_pbHashOfFileOnDisk != null); + + m_bDatabaseOpened = true; + m_ioSource = ioSource; + } + catch(Exception) + { + Clear(); + throw; + } + } + + + /// + /// Save the currently opened database. The file is written to the given stream which is expected to be the original location. + /// + /// This allows to save to cloud locations etc. + public void Save(Stream streamOfOriginalLocation, IStatusLogger slLogger) + { + Stream s = streamOfOriginalLocation; + KdbxFile kdb = new KdbxFile(this); + kdb.Save(s, null, KdbxFormat.Default, slLogger); + + m_pbHashOfLastIO = kdb.HashOfFileOnDisk; + m_pbHashOfFileOnDisk = kdb.HashOfFileOnDisk; + Debug.Assert(m_pbHashOfFileOnDisk != null); + m_bModified = false; + } + + + /// + /// Closes the currently opened database. No confirmation message is shown + /// before closing. Unsaved changes will be lost. + /// + public void Close() + { + Clear(); + } + + public void MergeIn(PwDatabase pdSource, PwMergeMethod mm) + { + MergeIn(pdSource, mm, null); + } + + public void MergeIn(PwDatabase pdSource, PwMergeMethod mm, + IStatusLogger slStatus) + { + if (pdSource == null) throw new ArgumentNullException("pdSource"); + + if (mm == PwMergeMethod.CreateNewUuids) + { + pdSource.RootGroup.Uuid = new PwUuid(true); + pdSource.RootGroup.CreateNewItemUuids(true, true, true); + } + + // PwGroup pgOrgStructure = m_pgRootGroup.CloneStructure(); + // PwGroup pgSrcStructure = pdSource.RootGroup.CloneStructure(); + // Later in case 'if(mm == PwMergeMethod.Synchronize)': + // PwObjectPoolEx ppOrg = PwObjectPoolEx.FromGroup(pgOrgStructure); + // PwObjectPoolEx ppSrc = PwObjectPoolEx.FromGroup(pgSrcStructure); + + PwObjectPoolEx ppOrg = PwObjectPoolEx.FromGroup(m_pgRootGroup); + PwObjectPoolEx ppSrc = PwObjectPoolEx.FromGroup(pdSource.RootGroup); + + GroupHandler ghSrc = delegate (PwGroup pg) + { + // if(pg == pdSource.m_pgRootGroup) return true; + + // Do not use ppOrg for finding the group, because new groups + // might have been added (which are not in the pool, and the + // pool should not be modified) + PwGroup pgLocal = m_pgRootGroup.FindGroup(pg.Uuid, true); + + if (pgLocal == null) + { + PwGroup pgSourceParent = pg.ParentGroup; + PwGroup pgLocalContainer; + if (pgSourceParent == null) + { + // pg is the root group of pdSource, and no corresponding + // local group was found; create the group within the + // local root group + Debug.Assert(pg == pdSource.m_pgRootGroup); + pgLocalContainer = m_pgRootGroup; + } + else if (pgSourceParent == pdSource.m_pgRootGroup) + pgLocalContainer = m_pgRootGroup; + else + pgLocalContainer = m_pgRootGroup.FindGroup(pgSourceParent.Uuid, true); + Debug.Assert(pgLocalContainer != null); + if (pgLocalContainer == null) pgLocalContainer = m_pgRootGroup; + + PwGroup pgNew = new PwGroup(false, false); + pgNew.Uuid = pg.Uuid; + pgNew.AssignProperties(pg, false, true); + + if (!pgLocalContainer.CanAddGroup(pgNew)) + { + Debug.Assert(false); + pgLocalContainer = m_pgRootGroup; + pgLocalContainer.CheckCanAddGroup(pgNew); + } + // pgLocalContainer.AddGroup(pgNew, true); + InsertObjectAtBestPos(pgLocalContainer.Groups, pgNew, ppSrc); + pgNew.ParentGroup = pgLocalContainer; + } + else // pgLocal != null + { + Debug.Assert(mm != PwMergeMethod.CreateNewUuids); + + if (mm == PwMergeMethod.OverwriteExisting) + pgLocal.AssignProperties(pg, false, false); + else if ((mm == PwMergeMethod.OverwriteIfNewer) || + (mm == PwMergeMethod.Synchronize)) + { + pgLocal.AssignProperties(pg, true, false); + } + // else if(mm == PwMergeMethod.KeepExisting) ... + } + + return ((slStatus != null) ? slStatus.ContinueWork() : true); + }; + + EntryHandler ehSrc = delegate (PwEntry pe) + { + // PwEntry peLocal = m_pgRootGroup.FindEntry(pe.Uuid, true); + PwEntry peLocal = (ppOrg.GetItemByUuid(pe.Uuid) as PwEntry); + Debug.Assert(object.ReferenceEquals(peLocal, + m_pgRootGroup.FindEntry(pe.Uuid, true))); + + if (peLocal == null) + { + PwGroup pgSourceParent = pe.ParentGroup; + PwGroup pgLocalContainer; + if (pgSourceParent == pdSource.m_pgRootGroup) + pgLocalContainer = m_pgRootGroup; + else + pgLocalContainer = m_pgRootGroup.FindGroup(pgSourceParent.Uuid, true); + Debug.Assert(pgLocalContainer != null); + if (pgLocalContainer == null) pgLocalContainer = m_pgRootGroup; + + PwEntry peNew = new PwEntry(false, false); + peNew.Uuid = pe.Uuid; + peNew.AssignProperties(pe, false, true, true); + + // pgLocalContainer.AddEntry(peNew, true); + InsertObjectAtBestPos(pgLocalContainer.Entries, peNew, ppSrc); + peNew.ParentGroup = pgLocalContainer; + } + else // peLocal != null + { + Debug.Assert(mm != PwMergeMethod.CreateNewUuids); + + const PwCompareOptions cmpOpt = (PwCompareOptions.IgnoreParentGroup | + PwCompareOptions.IgnoreLastAccess | PwCompareOptions.IgnoreHistory | + PwCompareOptions.NullEmptyEquivStd); + bool bEquals = peLocal.EqualsEntry(pe, cmpOpt, MemProtCmpMode.None); + + bool bOrgBackup = !bEquals; + if (mm != PwMergeMethod.OverwriteExisting) + bOrgBackup &= (TimeUtil.CompareLastMod(pe, peLocal, true) > 0); + bOrgBackup &= !pe.HasBackupOfData(peLocal, false, true); + if (bOrgBackup) peLocal.CreateBackup(null); // Maintain at end + + bool bSrcBackup = !bEquals && (mm != PwMergeMethod.OverwriteExisting); + bSrcBackup &= (TimeUtil.CompareLastMod(peLocal, pe, true) > 0); + bSrcBackup &= !peLocal.HasBackupOfData(pe, false, true); + if (bSrcBackup) pe.CreateBackup(null); // Maintain at end + + if (mm == PwMergeMethod.OverwriteExisting) + peLocal.AssignProperties(pe, false, false, false); + else if ((mm == PwMergeMethod.OverwriteIfNewer) || + (mm == PwMergeMethod.Synchronize)) + { + peLocal.AssignProperties(pe, true, false, false); + } + // else if(mm == PwMergeMethod.KeepExisting) ... + + MergeEntryHistory(peLocal, pe, mm); + } + + return ((slStatus != null) ? slStatus.ContinueWork() : true); + }; + + ghSrc(pdSource.RootGroup); + if (!pdSource.RootGroup.TraverseTree(TraversalMethod.PreOrder, ghSrc, ehSrc)) + throw new InvalidOperationException(); + + IStatusLogger slPrevStatus = m_slStatus; + m_slStatus = slStatus; + + if (mm == PwMergeMethod.Synchronize) + { + RelocateGroups(ppOrg, ppSrc); + RelocateEntries(ppOrg, ppSrc); + ReorderObjects(m_pgRootGroup, ppOrg, ppSrc); + + // After all relocations and reorderings + MergeInLocationChanged(m_pgRootGroup, ppOrg, ppSrc); + ppOrg = null; // Pools are now invalid, because the location + ppSrc = null; // changed times have been merged in + } + + // Delete *after* relocating, because relocating might empty + // some groups that are marked for deletion (and objects + // that weren't relocated yet might prevent the deletion) + Dictionary dDel = CreateDeletedObjectsPool(); + if (mm == PwMergeMethod.Synchronize) + MergeInDeletionInfo(pdSource.m_vDeletedObjects, dDel); + ApplyDeletions(m_pgRootGroup, dDel); + // The list and the dictionary should be kept in sync + Debug.Assert(m_vDeletedObjects.UCount == (uint)dDel.Count); + + // Must be called *after* merging groups, because group UUIDs + // are required for recycle bin and entry template UUIDs + MergeInDbProperties(pdSource, mm); + + MergeInCustomIcons(pdSource, dDel); + Debug.Assert(m_vDeletedObjects.UCount == (uint)dDel.Count); + + MaintainBackups(); + + Debug.Assert(!HasDuplicateUuids()); + m_slStatus = slPrevStatus; + } + + + private void MergeInCustomIcons(PwDatabase pdSource, + Dictionary dDel) + { + bool bIconsMod = false; + + Dictionary d = new Dictionary(); + for (int i = m_vCustomIcons.Count - 1; i >= 0; --i) + d[m_vCustomIcons[i].Uuid] = i; + Debug.Assert(d.Count == m_vCustomIcons.Count); // UUIDs unique + + foreach (PwCustomIcon ciS in pdSource.m_vCustomIcons) + { + int iT; + if (d.TryGetValue(ciS.Uuid, out iT)) + { + PwCustomIcon ciT = m_vCustomIcons[iT]; + + DateTime? odtT = ciT.LastModificationTime; + DateTime? odtS = ciS.LastModificationTime; + + if (odtT.HasValue && odtS.HasValue) + { + if (odtT.Value >= odtS.Value) continue; + } + else if (odtT.HasValue) continue; + else if (!odtS.HasValue) continue; // Both no time + + m_vCustomIcons[iT] = ciS.Clone(); + } + else + { + d[ciS.Uuid] = m_vCustomIcons.Count; + m_vCustomIcons.Add(ciS.Clone()); + } + + bIconsMod = true; + } + + List lObsoleteDel = new List(); + foreach (KeyValuePair kvpDel in dDel) + { + int iT; + if (d.TryGetValue(kvpDel.Key, out iT)) + { + PwCustomIcon ci = m_vCustomIcons[iT]; + if (ci == null) { Debug.Assert(false); continue; } // Dup. del. obj.? + + DateTime? odt = ci.LastModificationTime; + + if (odt.HasValue && (odt.Value > kvpDel.Value.DeletionTime)) + lObsoleteDel.Add(kvpDel.Value); + else + { + m_vCustomIcons[iT] = null; // Preserve indices, removed below + bIconsMod = true; + } + } + } + + Predicate f = delegate (PwCustomIcon ci) { return (ci == null); }; + m_vCustomIcons.RemoveAll(f); + + foreach (PwDeletedObject pdo in lObsoleteDel) + { + // Prevent future deletion attempts + if (!m_vDeletedObjects.Remove(pdo)) { Debug.Assert(false); } + if (!dDel.Remove(pdo.Uuid)) { Debug.Assert(false); } + } + + if (bIconsMod) m_bUINeedsIconUpdate = true; + + FixCustomIconRefs(); + } + + private Dictionary CreateDeletedObjectsPool() + { + Dictionary d = + new Dictionary(); + + int n = (int)m_vDeletedObjects.UCount; + for(int i = n - 1; i >= 0; --i) + { + PwDeletedObject pdo = m_vDeletedObjects.GetAt((uint)i); + + PwDeletedObject pdoEx; + if(d.TryGetValue(pdo.Uuid, out pdoEx)) + { + Debug.Assert(false); // Found duplicate, which should not happen + + if(pdo.DeletionTime > pdoEx.DeletionTime) + pdoEx.DeletionTime = pdo.DeletionTime; + + m_vDeletedObjects.RemoveAt((uint)i); + } + else d[pdo.Uuid] = pdo; + } + + return d; + } + + private void MergeInDeletionInfo(PwObjectList lSrc, + Dictionary dOrgDel) + { + foreach(PwDeletedObject pdoSrc in lSrc) + { + PwDeletedObject pdoOrg; + if(dOrgDel.TryGetValue(pdoSrc.Uuid, out pdoOrg)) // Update + { + Debug.Assert(pdoOrg.Uuid.Equals(pdoSrc.Uuid)); + + if(pdoSrc.DeletionTime > pdoOrg.DeletionTime) + pdoOrg.DeletionTime = pdoSrc.DeletionTime; + } + else // Add + { + m_vDeletedObjects.Add(pdoSrc); + dOrgDel[pdoSrc.Uuid] = pdoSrc; + } + } + } + + private void ApplyDeletions(PwObjectList l, Predicate fCanDelete, + Dictionary dOrgDel) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable + { + int n = (int)l.UCount; + for(int i = n - 1; i >= 0; --i) + { + if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; + + T t = l.GetAt((uint)i); + + PwDeletedObject pdo; + if(dOrgDel.TryGetValue(t.Uuid, out pdo)) + { + Debug.Assert(t.Uuid.Equals(pdo.Uuid)); + + bool bDel = (TimeUtil.Compare(t.LastModificationTime, + pdo.DeletionTime, true) < 0); + bDel &= fCanDelete(t); + + if(bDel) l.RemoveAt((uint)i); + else + { + // Prevent future deletion attempts; this also prevents + // delayed deletions (emptying a group could cause a + // group to be deleted, if the deletion was prevented + // before due to the group not being empty) + if(!m_vDeletedObjects.Remove(pdo)) { Debug.Assert(false); } + if(!dOrgDel.Remove(pdo.Uuid)) { Debug.Assert(false); } + } + } + } + } + + private static bool SafeCanDeleteGroup(PwGroup pg) + { + if(pg == null) { Debug.Assert(false); return false; } + + if(pg.Groups.UCount > 0) return false; + if(pg.Entries.UCount > 0) return false; + return true; + } + + private static bool SafeCanDeleteEntry(PwEntry pe) + { + if(pe == null) { Debug.Assert(false); return false; } + + return true; + } + + // Apply deletions on all objects in the specified container + // (but not the container itself), using post-order traversal + // to avoid implicit deletions; + // https://sourceforge.net/p/keepass/bugs/1499/ + private void ApplyDeletions(PwGroup pgContainer, + Dictionary dOrgDel) + { + foreach(PwGroup pg in pgContainer.Groups) // Post-order traversal + { + ApplyDeletions(pg, dOrgDel); + } + + ApplyDeletions(pgContainer.Groups, PwDatabase.SafeCanDeleteGroup, dOrgDel); + ApplyDeletions(pgContainer.Entries, PwDatabase.SafeCanDeleteEntry, dOrgDel); + } + + private void RelocateGroups(PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) + { + PwObjectList vGroups = m_pgRootGroup.GetGroups(true); + + foreach(PwGroup pg in vGroups) + { + if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; + + // PwGroup pgOrg = pgOrgStructure.FindGroup(pg.Uuid, true); + IStructureItem ptOrg = ppOrg.GetItemByUuid(pg.Uuid); + if(ptOrg == null) continue; + // PwGroup pgSrc = pgSrcStructure.FindGroup(pg.Uuid, true); + IStructureItem ptSrc = ppSrc.GetItemByUuid(pg.Uuid); + if(ptSrc == null) continue; + + PwGroup pgOrgParent = ptOrg.ParentGroup; + // vGroups does not contain the root group, thus pgOrgParent + // should not be null + if(pgOrgParent == null) { Debug.Assert(false); continue; } + + PwGroup pgSrcParent = ptSrc.ParentGroup; + // pgSrcParent may be null (for the source root group) + if(pgSrcParent == null) continue; + + if(pgOrgParent.Uuid.Equals(pgSrcParent.Uuid)) + { + // pg.LocationChanged = ((ptSrc.LocationChanged > ptOrg.LocationChanged) ? + // ptSrc.LocationChanged : ptOrg.LocationChanged); + continue; + } + + if(ptSrc.LocationChanged > ptOrg.LocationChanged) + { + PwGroup pgLocal = m_pgRootGroup.FindGroup(pgSrcParent.Uuid, true); + if(pgLocal == null) { Debug.Assert(false); continue; } + + if(pgLocal.IsContainedIn(pg)) continue; + + pg.ParentGroup.Groups.Remove(pg); + + // pgLocal.AddGroup(pg, true); + InsertObjectAtBestPos(pgLocal.Groups, pg, ppSrc); + pg.ParentGroup = pgLocal; + + // pg.LocationChanged = ptSrc.LocationChanged; + } + else + { + Debug.Assert(pg.ParentGroup.Uuid.Equals(pgOrgParent.Uuid)); + Debug.Assert(pg.LocationChanged == ptOrg.LocationChanged); + } + } + + Debug.Assert(m_pgRootGroup.GetGroups(true).UCount == vGroups.UCount); + } + + private void RelocateEntries(PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) + { + PwObjectList vEntries = m_pgRootGroup.GetEntries(true); + + foreach(PwEntry pe in vEntries) + { + if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; + + // PwEntry peOrg = pgOrgStructure.FindEntry(pe.Uuid, true); + IStructureItem ptOrg = ppOrg.GetItemByUuid(pe.Uuid); + if(ptOrg == null) continue; + // PwEntry peSrc = pgSrcStructure.FindEntry(pe.Uuid, true); + IStructureItem ptSrc = ppSrc.GetItemByUuid(pe.Uuid); + if(ptSrc == null) continue; + + PwGroup pgOrg = ptOrg.ParentGroup; + PwGroup pgSrc = ptSrc.ParentGroup; + if(pgOrg.Uuid.Equals(pgSrc.Uuid)) + { + // pe.LocationChanged = ((ptSrc.LocationChanged > ptOrg.LocationChanged) ? + // ptSrc.LocationChanged : ptOrg.LocationChanged); + continue; + } + + if(ptSrc.LocationChanged > ptOrg.LocationChanged) + { + PwGroup pgLocal = m_pgRootGroup.FindGroup(pgSrc.Uuid, true); + if(pgLocal == null) { Debug.Assert(false); continue; } + + pe.ParentGroup.Entries.Remove(pe); + + // pgLocal.AddEntry(pe, true); + InsertObjectAtBestPos(pgLocal.Entries, pe, ppSrc); + pe.ParentGroup = pgLocal; + + // pe.LocationChanged = ptSrc.LocationChanged; + } + else + { + Debug.Assert(pe.ParentGroup.Uuid.Equals(pgOrg.Uuid)); + Debug.Assert(pe.LocationChanged == ptOrg.LocationChanged); + } + } + + Debug.Assert(m_pgRootGroup.GetEntries(true).UCount == vEntries.UCount); + } + + private void ReorderObjects(PwGroup pg, PwObjectPoolEx ppOrg, + PwObjectPoolEx ppSrc) + { + ReorderObjectList(pg.Groups, ppOrg, ppSrc); + ReorderObjectList(pg.Entries, ppOrg, ppSrc); + + foreach(PwGroup pgSub in pg.Groups) + { + ReorderObjects(pgSub, ppOrg, ppSrc); + } + } + + private void ReorderObjectList(PwObjectList lItems, + PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable + { + List> lBlocks = PartitionConsec(lItems, ppOrg, ppSrc); + if(lBlocks.Count <= 1) return; + +#if DEBUG + PwObjectList lOrgItems = lItems.CloneShallow(); +#endif + + Queue> qToDo = new Queue>(); + qToDo.Enqueue(new KeyValuePair(0, lBlocks.Count - 1)); + + while(qToDo.Count > 0) + { + if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; + + KeyValuePair kvp = qToDo.Dequeue(); + if(kvp.Key >= kvp.Value) { Debug.Assert(false); continue; } + + PwObjectPoolEx pPool; + int iPivot = FindLocationChangedPivot(lBlocks, kvp, out pPool); + PwObjectBlock bPivot = lBlocks[iPivot]; + + T tPivotPrimary = bPivot.PrimaryItem; + if(tPivotPrimary == null) { Debug.Assert(false); continue; } + ulong idPivot = pPool.GetIdByUuid(tPivotPrimary.Uuid); + if(idPivot == 0) { Debug.Assert(false); continue; } + + Queue> qBefore = new Queue>(); + Queue> qAfter = new Queue>(); + bool bBefore = true; + + for(int i = kvp.Key; i <= kvp.Value; ++i) + { + if(i == iPivot) { bBefore = false; continue; } + + PwObjectBlock b = lBlocks[i]; + Debug.Assert(b.LocationChanged <= bPivot.LocationChanged); + + T t = b.PrimaryItem; + if(t != null) + { + ulong idBPri = pPool.GetIdByUuid(t.Uuid); + if(idBPri > 0) + { + if(idBPri < idPivot) qBefore.Enqueue(b); + else qAfter.Enqueue(b); + + continue; + } + } + else { Debug.Assert(false); } + + if(bBefore) qBefore.Enqueue(b); + else qAfter.Enqueue(b); + } + + int j = kvp.Key; + while(qBefore.Count > 0) { lBlocks[j] = qBefore.Dequeue(); ++j; } + int iNewPivot = j; + lBlocks[j] = bPivot; + ++j; + while(qAfter.Count > 0) { lBlocks[j] = qAfter.Dequeue(); ++j; } + Debug.Assert(j == (kvp.Value + 1)); + + if((iNewPivot - 1) > kvp.Key) + qToDo.Enqueue(new KeyValuePair(kvp.Key, iNewPivot - 1)); + if((iNewPivot + 1) < kvp.Value) + qToDo.Enqueue(new KeyValuePair(iNewPivot + 1, kvp.Value)); + } + + uint u = 0; + foreach(PwObjectBlock b in lBlocks) + { + foreach(T t in b) + { + lItems.SetAt(u, t); + ++u; + } + } + Debug.Assert(u == lItems.UCount); + +#if DEBUG + Debug.Assert(u == lOrgItems.UCount); + foreach(T ptItem in lOrgItems) + { + Debug.Assert(lItems.IndexOf(ptItem) >= 0); + } +#endif + } + + private static List> PartitionConsec(PwObjectList lItems, + PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable + { + List> lBlocks = new List>(); + + Dictionary dItemUuids = new Dictionary(); + foreach(T t in lItems) { dItemUuids[t.Uuid] = true; } + + uint n = lItems.UCount; + for(uint u = 0; u < n; ++u) + { + T t = lItems.GetAt(u); + + PwObjectBlock b = new PwObjectBlock(); + + DateTime dtLoc; + PwUuid puPrevParent; + PwObjectPoolEx pPool = GetBestPool(t, ppOrg, ppSrc, out dtLoc, + out puPrevParent); + b.Add(t, dtLoc, pPool); + + lBlocks.Add(b); + + ulong idOrg = ppOrg.GetIdByUuid(t.Uuid); + ulong idSrc = ppSrc.GetIdByUuid(t.Uuid); + if((idOrg == 0) || (idSrc == 0)) continue; + + for(uint x = u + 1; x < n; ++x) + { + T tNext = lItems.GetAt(x); + + ulong idOrgNext = idOrg + 1; + while(true) + { + IStructureItem ptOrg = ppOrg.GetItemById(idOrgNext); + if(ptOrg == null) { idOrgNext = 0; break; } + if(ptOrg.Uuid.Equals(tNext.Uuid)) break; // Found it + if(dItemUuids.ContainsKey(ptOrg.Uuid)) { idOrgNext = 0; break; } + ++idOrgNext; + } + if(idOrgNext == 0) break; + + ulong idSrcNext = idSrc + 1; + while(true) + { + IStructureItem ptSrc = ppSrc.GetItemById(idSrcNext); + if(ptSrc == null) { idSrcNext = 0; break; } + if(ptSrc.Uuid.Equals(tNext.Uuid)) break; // Found it + if(dItemUuids.ContainsKey(ptSrc.Uuid)) { idSrcNext = 0; break; } + ++idSrcNext; + } + if(idSrcNext == 0) break; + + pPool = GetBestPool(tNext, ppOrg, ppSrc, out dtLoc, out puPrevParent); + b.Add(tNext, dtLoc, pPool); + + ++u; + idOrg = idOrgNext; + idSrc = idSrcNext; + } + } + + return lBlocks; + } + + private static PwObjectPoolEx GetBestPool(T t, PwObjectPoolEx ppOrg, + PwObjectPoolEx ppSrc, out DateTime dtLoc, out PwUuid puPrevParent) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable + { + PwObjectPoolEx p = null; + dtLoc = TimeUtil.SafeMinValueUtc; + puPrevParent = PwUuid.Zero; + + IStructureItem ptOrg = ppOrg.GetItemByUuid(t.Uuid); + if (ptOrg != null) + { + dtLoc = ptOrg.LocationChanged; + puPrevParent = ptOrg.PreviousParentGroup; + p = ppOrg; + } + + IStructureItem ptSrc = ppSrc.GetItemByUuid(t.Uuid); + if ((ptSrc != null) && (ptSrc.LocationChanged > dtLoc)) + { + dtLoc = ptSrc.LocationChanged; + puPrevParent = ptSrc.PreviousParentGroup; + p = ppSrc; + } + + Debug.Assert(p != null); + return p; + } + + private static int FindLocationChangedPivot(List> lBlocks, + KeyValuePair kvpRange, out PwObjectPoolEx pPool) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable + { + pPool = null; + + int iPosMax = kvpRange.Key; + DateTime dtMax = TimeUtil.SafeMinValueUtc; + + for(int i = kvpRange.Key; i <= kvpRange.Value; ++i) + { + PwObjectBlock b = lBlocks[i]; + if(b.LocationChanged > dtMax) + { + iPosMax = i; + dtMax = b.LocationChanged; + pPool = b.PoolAssoc; + } + } + + return iPosMax; + } + + private static void MergeInLocationChanged(PwGroup pg, + PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) + { + GroupHandler gh = delegate (PwGroup pgSub) + { + DateTime dt; + PwUuid puPrevParent; + if (GetBestPool(pgSub, ppOrg, ppSrc, out dt, + out puPrevParent) != null) + { + pgSub.LocationChanged = dt; + pgSub.PreviousParentGroup = puPrevParent; + } + else { Debug.Assert(false); } + return true; + }; + + EntryHandler eh = delegate (PwEntry pe) + { + DateTime dt; + PwUuid puPrevParent; + if (GetBestPool(pe, ppOrg, ppSrc, out dt, + out puPrevParent) != null) + { + pe.LocationChanged = dt; + pe.PreviousParentGroup = puPrevParent; + } + else { Debug.Assert(false); } + return true; + }; + + gh(pg); + pg.TraverseTree(TraversalMethod.PreOrder, gh, eh); + } + + private static void InsertObjectAtBestPos(PwObjectList lItems, + T tNew, PwObjectPoolEx ppSrc) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable + { + if(tNew == null) { Debug.Assert(false); return; } + + ulong idSrc = ppSrc.GetIdByUuid(tNew.Uuid); + if(idSrc == 0) { Debug.Assert(false); lItems.Add(tNew); return; } + + const uint uIdOffset = 2; + Dictionary dOrg = new Dictionary(); + for(uint u = 0; u < lItems.UCount; ++u) + dOrg[lItems.GetAt(u).Uuid] = uIdOffset + u; + + ulong idSrcNext = idSrc + 1; + uint idOrgNext = 0; + while(true) + { + IStructureItem pNext = ppSrc.GetItemById(idSrcNext); + if(pNext == null) break; + if(dOrg.TryGetValue(pNext.Uuid, out idOrgNext)) break; + ++idSrcNext; + } + + if(idOrgNext != 0) + { + lItems.Insert(idOrgNext - uIdOffset, tNew); + return; + } + + ulong idSrcPrev = idSrc - 1; + uint idOrgPrev = 0; + while(true) + { + IStructureItem pPrev = ppSrc.GetItemById(idSrcPrev); + if(pPrev == null) break; + if(dOrg.TryGetValue(pPrev.Uuid, out idOrgPrev)) break; + --idSrcPrev; + } + + if(idOrgPrev != 0) + { + lItems.Insert(idOrgPrev + 1 - uIdOffset, tNew); + return; + } + + lItems.Add(tNew); + } + + private void MergeInDbProperties(PwDatabase pdSource, PwMergeMethod mm) + { + if(pdSource == null) { Debug.Assert(false); return; } + if((mm == PwMergeMethod.KeepExisting) || (mm == PwMergeMethod.None)) + return; + + bool bForce = (mm == PwMergeMethod.OverwriteExisting); + bool bSourceNewer = (pdSource.m_dtSettingsChanged > m_dtSettingsChanged); + + if(bForce || bSourceNewer) + { + m_dtSettingsChanged = pdSource.m_dtSettingsChanged; + + m_clr = pdSource.m_clr; + } + + if(bForce || (pdSource.m_dtNameChanged > m_dtNameChanged)) + { + m_strName = pdSource.m_strName; + m_dtNameChanged = pdSource.m_dtNameChanged; + } + + if(bForce || (pdSource.m_dtDescChanged > m_dtDescChanged)) + { + m_strDesc = pdSource.m_strDesc; + m_dtDescChanged = pdSource.m_dtDescChanged; + } + + if(bForce || (pdSource.m_dtDefaultUserChanged > m_dtDefaultUserChanged)) + { + m_strDefaultUserName = pdSource.m_strDefaultUserName; + m_dtDefaultUserChanged = pdSource.m_dtDefaultUserChanged; + } + + PwUuid pwPrefBin = m_pwRecycleBin, pwAltBin = pdSource.m_pwRecycleBin; + if(bForce || (pdSource.m_dtRecycleBinChanged > m_dtRecycleBinChanged)) + { + pwPrefBin = pdSource.m_pwRecycleBin; + pwAltBin = m_pwRecycleBin; + m_bUseRecycleBin = pdSource.m_bUseRecycleBin; + m_dtRecycleBinChanged = pdSource.m_dtRecycleBinChanged; + } + if(m_pgRootGroup.FindGroup(pwPrefBin, true) != null) + m_pwRecycleBin = pwPrefBin; + else if(m_pgRootGroup.FindGroup(pwAltBin, true) != null) + m_pwRecycleBin = pwAltBin; + else m_pwRecycleBin = PwUuid.Zero; // Debug.Assert(false); + + PwUuid pwPrefTmp = m_pwEntryTemplatesGroup, pwAltTmp = pdSource.m_pwEntryTemplatesGroup; + if(bForce || (pdSource.m_dtEntryTemplatesChanged > m_dtEntryTemplatesChanged)) + { + pwPrefTmp = pdSource.m_pwEntryTemplatesGroup; + pwAltTmp = m_pwEntryTemplatesGroup; + m_dtEntryTemplatesChanged = pdSource.m_dtEntryTemplatesChanged; + } + if(m_pgRootGroup.FindGroup(pwPrefTmp, true) != null) + m_pwEntryTemplatesGroup = pwPrefTmp; + else if(m_pgRootGroup.FindGroup(pwAltTmp, true) != null) + m_pwEntryTemplatesGroup = pwAltTmp; + else m_pwEntryTemplatesGroup = PwUuid.Zero; // Debug.Assert(false); + + foreach(KeyValuePair kvp in pdSource.m_dCustomData) + { + if(bSourceNewer || !m_dCustomData.Exists(kvp.Key)) + m_dCustomData.Set(kvp.Key, kvp.Value, null); + } + + // 'Clone' duplicates deep values (e.g. byte arrays) + VariantDictionary vdS = (VariantDictionary)pdSource.m_dPublicCustomData.Clone(); + if (bForce || bSourceNewer) + vdS.CopyTo(m_dPublicCustomData); + else + { + m_dPublicCustomData.CopyTo(vdS); + m_dPublicCustomData = vdS; + } + } + + private void MergeEntryHistory(PwEntry pe, PwEntry peSource, + PwMergeMethod mm) + { + if(!pe.Uuid.Equals(peSource.Uuid)) { Debug.Assert(false); return; } + + if(pe.History.UCount == peSource.History.UCount) + { + bool bEqual = true; + for(uint uEnum = 0; uEnum < pe.History.UCount; ++uEnum) + { + if(pe.History.GetAt(uEnum).LastModificationTime != + peSource.History.GetAt(uEnum).LastModificationTime) + { + bEqual = false; + break; + } + } + + if(bEqual) return; + } + + if((m_slStatus != null) && !m_slStatus.ContinueWork()) return; + + IDictionary dict = +#if KeePassLibSD + new SortedList(); +#else + new SortedDictionary(); +#endif + foreach(PwEntry peOrg in pe.History) + { + dict[peOrg.LastModificationTime] = peOrg; + } + + foreach(PwEntry peSrc in peSource.History) + { + DateTime dt = peSrc.LastModificationTime; + if(dict.ContainsKey(dt)) + { + if(mm == PwMergeMethod.OverwriteExisting) + dict[dt] = peSrc.CloneDeep(); + } + else dict[dt] = peSrc.CloneDeep(); + } + + pe.History.Clear(); + foreach(KeyValuePair kvpCur in dict) + { + Debug.Assert(kvpCur.Value.Uuid.Equals(pe.Uuid)); + Debug.Assert(kvpCur.Value.History.UCount == 0); + pe.History.Add(kvpCur.Value); + } + } + + public bool MaintainBackups() + { + if(m_pgRootGroup == null) { Debug.Assert(false); return false; } + + bool bDeleted = false; + EntryHandler eh = delegate(PwEntry pe) + { + if(pe.MaintainBackups(this)) bDeleted = true; + return true; + }; + + m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, null, eh); + return bDeleted; + } + + /* /// + /// Synchronize current database with another one. + /// + /// Source file. + public void Synchronize(string strFile) + { + PwDatabase pdSource = new PwDatabase(); + + IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFile); + pdSource.Open(ioc, m_pwUserKey, null); + + MergeIn(pdSource, PwMergeMethod.Synchronize); + } */ + + /// + /// Get the index of a custom icon. + /// + /// ID of the icon. + /// Index of the icon. + public int GetCustomIconIndex(PwUuid pwIconId) + { + for (int i = 0; i < m_vCustomIcons.Count; ++i) + { + PwCustomIcon pwci = m_vCustomIcons[i]; + if (pwci.Uuid.Equals(pwIconId)) + return i; + } + + // Debug.Assert(false); // Do not assert + return -1; + } + + public int GetCustomIconIndex(byte[] pbPngData) + { + if(pbPngData == null) { Debug.Assert(false); return -1; } + + for (int i = 0; i < m_vCustomIcons.Count; ++i) + { + PwCustomIcon pwci = m_vCustomIcons[i]; + byte[] pbEx = pwci.ImageDataPng; + if (pbEx == null) { Debug.Assert(false); continue; } + + if (MemUtil.ArraysEqual(pbEx, pbPngData)) + return i; + } + + return -1; + } + + /// + /// Get a custom icon. This function can return null, if + /// no cached image of the icon is available. + /// + /// ID of the icon. + /// Image data. + public Android.Graphics.Bitmap GetCustomIcon(PwUuid pwIconId) + { + int nIndex = GetCustomIconIndex(pwIconId); + + if(nIndex >= 0) return m_vCustomIcons[nIndex].Image; + else { Debug.Assert(false); return null; } + } + + public bool DeleteCustomIcons(List lUuids) + { + if (lUuids == null) { Debug.Assert(false); throw new ArgumentNullException("lUuids"); } + if (lUuids.Count == 0) return false; + + Dictionary dToDel = new Dictionary(); + foreach (PwUuid pu in lUuids) { dToDel[pu] = true; } + + DateTime dt = DateTime.UtcNow; + for (int i = m_vCustomIcons.Count - 1; i >= 0; --i) + { + PwUuid pu = m_vCustomIcons[i].Uuid; + if (dToDel.ContainsKey(pu)) + { + m_vCustomIcons[i] = null; // Removed below + m_vDeletedObjects.Add(new PwDeletedObject(pu, dt)); + } + } + + Predicate f = delegate (PwCustomIcon ci) { return (ci == null); }; + m_vCustomIcons.RemoveAll(f); + + FixCustomIconRefs(); + return true; + } + + private void FixCustomIconRefs() + { + Dictionary d = new Dictionary(); + foreach (PwCustomIcon ci in m_vCustomIcons) { d[ci.Uuid] = true; } + + GroupHandler gh = delegate (PwGroup pg) + { + PwUuid pu = pg.CustomIconUuid; + if (pu.Equals(PwUuid.Zero)) return true; + if (!d.ContainsKey(pu)) pg.CustomIconUuid = PwUuid.Zero; + return true; + }; + + EntryHandler eh = delegate (PwEntry pe) + { + FixCustomIconRefs(pe, d); + return true; + }; + + gh(m_pgRootGroup); + m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); + } + + private void FixCustomIconRefs(PwEntry pe, Dictionary d) + { + PwUuid pu = pe.CustomIconUuid; + if (pu.Equals(PwUuid.Zero)) return; + if (!d.ContainsKey(pu)) pe.CustomIconUuid = PwUuid.Zero; + + foreach (PwEntry peH in pe.History) FixCustomIconRefs(peH, d); + } + + + private int GetTotalObjectUuidCount() + { + uint uGroups, uEntries; + m_pgRootGroup.GetCounts(true, out uGroups, out uEntries); + + uint uTotal = uGroups + uEntries + 1; // 1 for root group + if(uTotal > 0x7FFFFFFFU) { Debug.Assert(false); return 0x7FFFFFFF; } + return (int)uTotal; + } + + internal bool HasDuplicateUuids() + { + int nTotal = GetTotalObjectUuidCount(); + Dictionary d = new Dictionary(nTotal); + bool bDupFound = false; + + GroupHandler gh = delegate(PwGroup pg) + { + PwUuid pu = pg.Uuid; + if(d.ContainsKey(pu)) + { + bDupFound = true; + return false; + } + + d.Add(pu, null); + Debug.Assert(d.ContainsKey(pu)); + return true; + }; + + EntryHandler eh = delegate(PwEntry pe) + { + PwUuid pu = pe.Uuid; + if(d.ContainsKey(pu)) + { + bDupFound = true; + return false; + } + + d.Add(pu, null); + Debug.Assert(d.ContainsKey(pu)); + return true; + }; + + gh(m_pgRootGroup); + m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); + + Debug.Assert(bDupFound || (d.Count == nTotal)); + return bDupFound; + } + + internal void FixDuplicateUuids() + { + int nTotal = GetTotalObjectUuidCount(); + Dictionary d = new Dictionary(nTotal); + + GroupHandler gh = delegate(PwGroup pg) + { + PwUuid pu = pg.Uuid; + if(d.ContainsKey(pu)) + { + pu = new PwUuid(true); + while(d.ContainsKey(pu)) { Debug.Assert(false); pu = new PwUuid(true); } + + pg.Uuid = pu; + } + + d.Add(pu, null); + return true; + }; + + EntryHandler eh = delegate(PwEntry pe) + { + PwUuid pu = pe.Uuid; + if(d.ContainsKey(pu)) + { + pu = new PwUuid(true); + while(d.ContainsKey(pu)) { Debug.Assert(false); pu = new PwUuid(true); } + + pe.SetUuid(pu, true); + } + + d.Add(pu, null); + return true; + }; + + gh(m_pgRootGroup); + m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); + + Debug.Assert(d.Count == nTotal); + Debug.Assert(!HasDuplicateUuids()); + } + + /* public void CreateBackupFile(IStatusLogger sl) + { + if(sl != null) sl.SetText(KLRes.CreatingBackupFile, LogStatusType.Info); + + IOConnectionInfo iocBk = m_ioSource.CloneDeep(); + iocBk.Path += StrBackupExtension; + + bool bMadeUnhidden = UrlUtil.UnhideFile(iocBk.Path); + + bool bFastCopySuccess = false; + if(m_ioSource.IsLocalFile() && (m_ioSource.UserName.Length == 0) && + (m_ioSource.Password.Length == 0)) + { + try + { + string strFile = m_ioSource.Path + StrBackupExtension; + File.Copy(m_ioSource.Path, strFile, true); + bFastCopySuccess = true; + } + catch(Exception) { Debug.Assert(false); } + } + + if(bFastCopySuccess == false) + { + using(Stream sIn = IOConnection.OpenRead(m_ioSource)) + { + using(Stream sOut = IOConnection.OpenWrite(iocBk)) + { + MemUtil.CopyStream(sIn, sOut); + sOut.Close(); + } + + sIn.Close(); + } + } + + if(bMadeUnhidden) UrlUtil.HideFile(iocBk.Path, true); // Hide again + } */ + + /* private static void RemoveData(PwGroup pg) + { + EntryHandler eh = delegate(PwEntry pe) + { + pe.AutoType.Clear(); + pe.Binaries.Clear(); + pe.History.Clear(); + pe.Strings.Clear(); + return true; + }; + + pg.TraverseTree(TraversalMethod.PreOrder, null, eh); + } */ + + public uint DeleteDuplicateEntries(IStatusLogger sl) + { + uint uDeleted = 0; + + PwGroup pgRecycleBin = null; + if(m_bUseRecycleBin) + pgRecycleBin = m_pgRootGroup.FindGroup(m_pwRecycleBin, true); + + DateTime dtNow = DateTime.UtcNow; + PwObjectList l = m_pgRootGroup.GetEntries(true); + int i = 0; + while(true) + { + if(i >= ((int)l.UCount - 1)) break; + + if(sl != null) + { + long lCnt = (long)l.UCount, li = (long)i; + long nArTotal = (lCnt * lCnt) / 2L; + long nArCur = li * lCnt - ((li * li) / 2L); + long nArPct = (nArCur * 100L) / nArTotal; + if(nArPct < 0) nArPct = 0; + if(nArPct > 100) nArPct = 100; + if(!sl.SetProgress((uint)nArPct)) break; + } + + PwEntry peA = l.GetAt((uint)i); + + for(uint j = (uint)i + 1; j < l.UCount; ++j) + { + PwEntry peB = l.GetAt(j); + if(!DupEntriesEqual(peA, peB)) continue; + + bool bDeleteA = (TimeUtil.CompareLastMod(peA, peB, true) <= 0); + if(pgRecycleBin != null) + { + bool bAInBin = peA.IsContainedIn(pgRecycleBin); + bool bBInBin = peB.IsContainedIn(pgRecycleBin); + + if(bAInBin && !bBInBin) bDeleteA = true; + else if(bBInBin && !bAInBin) bDeleteA = false; + } + + if(bDeleteA) + { + peA.ParentGroup.Entries.Remove(peA); + m_vDeletedObjects.Add(new PwDeletedObject(peA.Uuid, dtNow)); + + l.RemoveAt((uint)i); + --i; + } + else + { + peB.ParentGroup.Entries.Remove(peB); + m_vDeletedObjects.Add(new PwDeletedObject(peB.Uuid, dtNow)); + + l.RemoveAt(j); + } + + ++uDeleted; + break; + } + + ++i; + } + + return uDeleted; + } + + private static List m_lStdFields = null; + private static bool DupEntriesEqual(PwEntry a, PwEntry b) + { + if(m_lStdFields == null) m_lStdFields = PwDefs.GetStandardFields(); + + foreach(string strStdKey in m_lStdFields) + { + string strA = a.Strings.ReadSafe(strStdKey); + string strB = b.Strings.ReadSafe(strStdKey); + if(!strA.Equals(strB)) return false; + } + + foreach(KeyValuePair kvpA in a.Strings) + { + if(PwDefs.IsStandardField(kvpA.Key)) continue; + + ProtectedString psB = b.Strings.Get(kvpA.Key); + if(psB == null) return false; + + // Ignore protection setting, compare values only + if(!kvpA.Value.ReadString().Equals(psB.ReadString())) return false; + } + + foreach(KeyValuePair kvpB in b.Strings) + { + if(PwDefs.IsStandardField(kvpB.Key)) continue; + + ProtectedString psA = a.Strings.Get(kvpB.Key); + if(psA == null) return false; + + // Must be equal by logic + Debug.Assert(kvpB.Value.ReadString().Equals(psA.ReadString())); + } + + if(a.Binaries.UCount != b.Binaries.UCount) return false; + foreach(KeyValuePair kvpBin in a.Binaries) + { + ProtectedBinary pbB = b.Binaries.Get(kvpBin.Key); + if(pbB == null) return false; + + // Ignore protection setting, compare values only + byte[] pbDataA = kvpBin.Value.ReadData(); + byte[] pbDataB = pbB.ReadData(); + bool bBinEq = MemUtil.ArraysEqual(pbDataA, pbDataB); + MemUtil.ZeroByteArray(pbDataA); + MemUtil.ZeroByteArray(pbDataB); + if(!bBinEq) return false; + } + + return true; + } + + public uint DeleteEmptyGroups() + { + uint uDeleted = 0; + + PwObjectList l = m_pgRootGroup.GetGroups(true); + int iStart = (int)l.UCount - 1; + for(int i = iStart; i >= 0; --i) + { + PwGroup pg = l.GetAt((uint)i); + if((pg.Groups.UCount > 0) || (pg.Entries.UCount > 0)) continue; + + pg.ParentGroup.Groups.Remove(pg); + m_vDeletedObjects.Add(new PwDeletedObject(pg.Uuid, DateTime.UtcNow)); + + ++uDeleted; + } + + return uDeleted; + } + + + public uint DeleteUnusedCustomIcons() + { + Dictionary dToDel = new Dictionary(); + foreach (PwCustomIcon ci in m_vCustomIcons) { dToDel[ci.Uuid] = true; } + + GroupHandler gh = delegate (PwGroup pg) + { + PwUuid pu = pg.CustomIconUuid; + if (!pu.Equals(PwUuid.Zero)) dToDel.Remove(pu); + return true; + }; + + EntryHandler eh = delegate (PwEntry pe) + { + RemoveCustomIconsFromDict(dToDel, pe); + return true; + }; + + gh(m_pgRootGroup); + m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); + + uint cDel = (uint)dToDel.Count; + if (cDel != 0) + { + DeleteCustomIcons(new List(dToDel.Keys)); + m_bUINeedsIconUpdate = true; + } + + return cDel; + } + + private static void RemoveCustomIconsFromDict(Dictionary d, + PwEntry pe) + { + PwUuid pu = pe.CustomIconUuid; + if (!pu.Equals(PwUuid.Zero)) d.Remove(pu); + + foreach (PwEntry peH in pe.History) RemoveCustomIconsFromDict(d, peH); + } + + internal static void CopyCustomIcons(PwDatabase pdFrom, PwDatabase pdTo, + PwGroup pgSelect, bool bResetIfUnknown) + { + if (pgSelect == null) { Debug.Assert(false); return; } + + Dictionary dFrom = new Dictionary(); + if (pdFrom != null) + { + foreach (PwCustomIcon ci in pdFrom.m_vCustomIcons) + dFrom[ci.Uuid] = ci; + } + + Dictionary dTo = new Dictionary(); + if (pdTo != null) + { + for (int i = pdTo.m_vCustomIcons.Count - 1; i >= 0; --i) + dTo[pdTo.m_vCustomIcons[i].Uuid] = i; + } + + Func fEnsureIcon = delegate (PwUuid puIcon) + { + if (puIcon.Equals(PwUuid.Zero)) return true; + if (pdTo == null) { Debug.Assert(false); return false; } + + PwCustomIcon ciFrom; + if (!dFrom.TryGetValue(puIcon, out ciFrom)) { Debug.Assert(false); return false; } + + int iTo; + if (dTo.TryGetValue(puIcon, out iTo)) + { + PwCustomIcon ciTo = pdTo.m_vCustomIcons[iTo]; + + DateTime? odtFrom = ciFrom.LastModificationTime; + DateTime? odtTo = ciTo.LastModificationTime; + + if (odtFrom.HasValue && odtTo.HasValue) + { + if (odtFrom.Value <= odtTo.Value) return true; + } + else if (odtTo.HasValue) return true; + else if (!odtFrom.HasValue) return true; // Both no time + + pdTo.m_vCustomIcons[iTo] = ciFrom.Clone(); + } + else + { + dTo[puIcon] = pdTo.m_vCustomIcons.Count; + pdTo.m_vCustomIcons.Add(ciFrom.Clone()); + } + + pdTo.Modified = true; + pdTo.UINeedsIconUpdate = true; + return true; + }; + + GroupHandler gh = delegate (PwGroup pgCur) + { + bool bTo = fEnsureIcon(pgCur.CustomIconUuid); + if (!bTo && bResetIfUnknown) pgCur.CustomIconUuid = PwUuid.Zero; + return true; + }; + + EntryHandler eh = delegate (PwEntry peCur) + { + bool bTo = fEnsureIcon(peCur.CustomIconUuid); + if (!bTo && bResetIfUnknown) peCur.CustomIconUuid = PwUuid.Zero; + return true; + }; + + gh(pgSelect); + pgSelect.TraverseTree(TraversalMethod.PreOrder, gh, eh); + } + } +} + diff --git a/src/KeePassLib2AndroidSdkStyle/PwDefs.cs b/src/KeePassLib2AndroidSdkStyle/PwDefs.cs new file mode 100644 index 00000000..6c855b9f --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/PwDefs.cs @@ -0,0 +1,536 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Xml.Serialization; + +using KeePassLib.Delegates; +using KeePassLib.Interfaces; +using KeePassLib.Serialization; + +namespace KeePassLib +{ + /// + /// Contains KeePassLib-global definitions and enums. + /// + public static class PwDefs + { + /// + /// The product name. + /// + public const string ProductName = "KeePass Password Safe"; + + /// + /// A short, simple string representing the product name. The string + /// should contain no spaces, directory separator characters, etc. + /// + public const string ShortProductName = "KeePass"; + + internal const string UnixName = "keepass2"; + internal const string ResClass = "KeePass2"; // With initial capital + + /// + /// Version, encoded as 32-bit unsigned integer. + /// 2.00 = 0x02000000, 2.01 = 0x02000100, ..., 2.18 = 0x02010800. + /// As of 2.19, the version is encoded component-wise per byte, + /// e.g. 2.19 = 0x02130000. + /// It is highly recommended to use FileVersion64 instead. + /// + public const uint Version32 = 0x02230000; + + /// + /// Version, encoded as 64-bit unsigned integer + /// (component-wise, 16 bits per component). + /// + public const ulong FileVersion64 = 0x0002002300000000UL; + + /// + /// Version, encoded as string. + /// + public const string VersionString = "2.35"; + + public const string Copyright = @"Copyright © 2003-2017 Dominik Reichl"; + + /// + /// Product website URL. Terminated by a forward slash. + /// + public const string HomepageUrl = "http://keepass.info/"; + + /// + /// Product donations URL. + /// + public const string DonationsUrl = "http://keepass.info/donate.html"; + + /// + /// URL to the online plugins page. + /// + public const string PluginsUrl = "http://keepass.info/plugins.html"; + + /// + /// URL to the online translations page. + /// + public const string TranslationsUrl = "http://keepass.info/translations.html"; + + /// + /// URL to a TXT file (eventually compressed) that contains information + /// about the latest KeePass version available on the website. + /// + public const string VersionUrl = "https://sslsites.de/keepass.info/update/version2x.txt.gz"; + // public const string VersionUrl = "http://keepass.info/update/version2x.txt.gz"; + + /// + /// URL to the root path of the online KeePass help. Terminated by + /// a forward slash. + /// + public const string HelpUrl = "http://keepass.info/help/"; + + /// + /// A DateTime object that represents the time when the assembly + /// was loaded. + /// + public static readonly DateTime DtDefaultNow = DateTime.UtcNow; + + /// + /// Default number of master key encryption/transformation rounds + /// (making dictionary attacks harder). + /// + public const ulong DefaultKeyEncryptionRounds = 500000; + + /// + /// Default identifier string for the title field. Should not contain + /// spaces, tabs or other whitespace. + /// + public const string TitleField = "Title"; + + /// + /// Default identifier string for the user name field. Should not contain + /// spaces, tabs or other whitespace. + /// + public const string UserNameField = "UserName"; + + /// + /// Default identifier string for the password field. Should not contain + /// spaces, tabs or other whitespace. + /// + public const string PasswordField = "Password"; + + /// + /// Default identifier string for the URL field. Should not contain + /// spaces, tabs or other whitespace. + /// + public const string UrlField = "URL"; + + /// + /// Default identifier string for the notes field. Should not contain + /// spaces, tabs or other whitespace. + /// + public const string NotesField = "Notes"; + + /// + /// Default identifier string for the field which will contain TAN indices. + /// + public const string TanIndexField = UserNameField; + + /// + /// Default title of an entry that is really a TAN entry. + /// + public const string TanTitle = @""; + + /// + /// Prefix of a custom auto-type string field. + /// + public const string AutoTypeStringPrefix = "S:"; + + /// + /// Default string representing a hidden password. + /// + public const string HiddenPassword = "********"; + + /// + /// Default auto-type keystroke sequence. If no custom sequence is + /// specified, this sequence is used. + /// + public const string DefaultAutoTypeSequence = @"{USERNAME}{TAB}{PASSWORD}{ENTER}"; + + /// + /// Default auto-type keystroke sequence for TAN entries. If no custom + /// sequence is specified, this sequence is used. + /// + public const string DefaultAutoTypeSequenceTan = @"{PASSWORD}"; + + /// + /// Check if a name is a standard field name. + /// + /// Input field name. + /// Returns true, if the field name is a standard + /// field name (title, user name, password, ...), otherwise false. + public static bool IsStandardField(string strFieldName) + { + Debug.Assert(strFieldName != null); if(strFieldName == null) return false; + + if(strFieldName.Equals(TitleField)) return true; + if(strFieldName.Equals(UserNameField)) return true; + if(strFieldName.Equals(PasswordField)) return true; + if(strFieldName.Equals(UrlField)) return true; + if(strFieldName.Equals(NotesField)) return true; + + return false; + } + + public static List GetStandardFields() + { + List l = new List(); + + l.Add(TitleField); + l.Add(UserNameField); + l.Add(PasswordField); + l.Add(UrlField); + l.Add(NotesField); + + return l; + } + + /// + /// Check if an entry is a TAN. + /// + /// Password entry. + /// Returns true if the entry is a TAN. + public static bool IsTanEntry(PwEntry pe) + { + Debug.Assert(pe != null); if(pe == null) return false; + + return (pe.Strings.ReadSafe(PwDefs.TitleField) == TanTitle); + } + } + + // #pragma warning disable 1591 // Missing XML comments warning + /// + /// Search parameters for group and entry searches. + /// + public sealed class SearchParameters + { + private string m_strName = string.Empty; + [DefaultValue("")] + public string Name + { + get { return m_strName; } + set + { + if (value == null) throw new ArgumentNullException("value"); + m_strName = value; + } + } + + private string m_strText = string.Empty; + [DefaultValue("")] + public string SearchString + { + get { return m_strText; } + set + { + if (value == null) throw new ArgumentNullException("value"); + m_strText = value; + } + } + + private PwSearchMode m_sm = PwSearchMode.Simple; + public PwSearchMode SearchMode + { + get { return m_sm; } + set { m_sm = value; } + } + + [DefaultValue(false)] + [Obsolete] + [XmlIgnore] + public bool RegularExpression + { + get { return (m_sm == PwSearchMode.Regular); } + set { m_sm = (value ? PwSearchMode.Regular : PwSearchMode.Simple); } + } + + private bool m_bSearchInTitles = true; + [DefaultValue(true)] + public bool SearchInTitles + { + get { return m_bSearchInTitles; } + set { m_bSearchInTitles = value; } + } + + private bool m_bSearchInUserNames = true; + [DefaultValue(true)] + public bool SearchInUserNames + { + get { return m_bSearchInUserNames; } + set { m_bSearchInUserNames = value; } + } + + private bool m_bSearchInPasswords = false; + [DefaultValue(false)] + public bool SearchInPasswords + { + get { return m_bSearchInPasswords; } + set { m_bSearchInPasswords = value; } + } + + private bool m_bSearchInUrls = true; + [DefaultValue(true)] + public bool SearchInUrls + { + get { return m_bSearchInUrls; } + set { m_bSearchInUrls = value; } + } + + private bool m_bSearchInNotes = true; + [DefaultValue(true)] + public bool SearchInNotes + { + get { return m_bSearchInNotes; } + set { m_bSearchInNotes = value; } + } + + private bool m_bSearchInOther = true; + [DefaultValue(true)] + public bool SearchInOther + { + get { return m_bSearchInOther; } + set { m_bSearchInOther = value; } + } + + private bool m_bSearchInStringNames = false; + [DefaultValue(false)] + public bool SearchInStringNames + { + get { return m_bSearchInStringNames; } + set { m_bSearchInStringNames = value; } + } + + private bool m_bSearchInTags = true; + [DefaultValue(true)] + public bool SearchInTags + { + get { return m_bSearchInTags; } + set { m_bSearchInTags = value; } + } + + private bool m_bSearchInUuids = false; + [DefaultValue(false)] + public bool SearchInUuids + { + get { return m_bSearchInUuids; } + set { m_bSearchInUuids = value; } + } + + private bool m_bSearchInGroupPaths = false; + [DefaultValue(false)] + public bool SearchInGroupPaths + { + get { return m_bSearchInGroupPaths; } + set { m_bSearchInGroupPaths = value; } + } + + private bool m_bSearchInGroupNames = false; + [DefaultValue(false)] + public bool SearchInGroupNames + { + get { return m_bSearchInGroupNames; } + set { m_bSearchInGroupNames = value; } + } + + private bool m_bSearchInHistory = false; + [DefaultValue(false)] + public bool SearchInHistory + { + get { return m_bSearchInHistory; } + set { m_bSearchInHistory = value; } + } + +#if KeePassUAP + private StringComparison m_scType = StringComparison.OrdinalIgnoreCase; +#else + private StringComparison m_scType = StringComparison.InvariantCultureIgnoreCase; +#endif + /// + /// String comparison type. Specifies the condition when the specified + /// text matches a group/entry string. + /// + public StringComparison ComparisonMode + { + get { return m_scType; } + set { m_scType = value; } + } + + private bool m_bExcludeExpired = false; + [DefaultValue(false)] + public bool ExcludeExpired + { + get { return m_bExcludeExpired; } + set { m_bExcludeExpired = value; } + } + + private bool m_bRespectEntrySearchingDisabled = true; + [DefaultValue(true)] + public bool RespectEntrySearchingDisabled + { + get { return m_bRespectEntrySearchingDisabled; } + set { m_bRespectEntrySearchingDisabled = value; } + } + + private StrPwEntryDelegate m_fnDataTrf = null; + [XmlIgnore] + public StrPwEntryDelegate DataTransformationFn + { + get { return m_fnDataTrf; } + set { m_fnDataTrf = value; } + } + + private string m_strDataTrf = string.Empty; + /// + /// Only for serialization. + /// + [DefaultValue("")] + public string DataTransformation + { + get { return m_strDataTrf; } + set + { + if (value == null) throw new ArgumentNullException("value"); + m_strDataTrf = value; + } + } + + [XmlIgnore] + public static SearchParameters None + { + get + { + SearchParameters sp = new SearchParameters(); + + Debug.Assert(sp.m_strName.Length == 0); + Debug.Assert(sp.m_strText.Length == 0); + Debug.Assert(sp.m_sm == PwSearchMode.Simple); + sp.m_bSearchInTitles = false; + sp.m_bSearchInUserNames = false; + Debug.Assert(!sp.m_bSearchInPasswords); + sp.m_bSearchInUrls = false; + sp.m_bSearchInNotes = false; + sp.m_bSearchInOther = false; + Debug.Assert(!sp.m_bSearchInStringNames); + sp.m_bSearchInTags = false; + Debug.Assert(!sp.m_bSearchInUuids); + Debug.Assert(!sp.m_bSearchInGroupPaths); + Debug.Assert(!sp.m_bSearchInGroupNames); + Debug.Assert(!sp.m_bSearchInHistory); + // Debug.Assert(sp.m_scType == StringComparison.InvariantCultureIgnoreCase); + Debug.Assert(!sp.m_bExcludeExpired); + Debug.Assert(sp.m_bRespectEntrySearchingDisabled); + + return sp; + } + } + + /// + /// Construct a new search parameters object. + /// + public SearchParameters() + { + } + + public SearchParameters Clone() + { + return (SearchParameters)this.MemberwiseClone(); + } + } + // #pragma warning restore 1591 // Missing XML comments warning + + // #pragma warning disable 1591 // Missing XML comments warning + /// + /// Memory protection configuration structure (for default fields). + /// + public sealed class MemoryProtectionConfig : IDeepCloneable + { + public bool ProtectTitle = false; + public bool ProtectUserName = false; + public bool ProtectPassword = true; + public bool ProtectUrl = false; + public bool ProtectNotes = false; + + // public bool AutoEnableVisualHiding = false; + + public MemoryProtectionConfig CloneDeep() + { + return (MemoryProtectionConfig)this.MemberwiseClone(); + } + + public bool GetProtection(string strField) + { + if(strField == PwDefs.TitleField) return this.ProtectTitle; + if(strField == PwDefs.UserNameField) return this.ProtectUserName; + if(strField == PwDefs.PasswordField) return this.ProtectPassword; + if(strField == PwDefs.UrlField) return this.ProtectUrl; + if(strField == PwDefs.NotesField) return this.ProtectNotes; + + return false; + } + } + // #pragma warning restore 1591 // Missing XML comments warning + + public sealed class ObjectTouchedEventArgs : EventArgs + { + private object m_o; + public object Object { get { return m_o; } } + + private bool m_bModified; + public bool Modified { get { return m_bModified; } } + + private bool m_bParentsTouched; + public bool ParentsTouched { get { return m_bParentsTouched; } } + + public ObjectTouchedEventArgs(object o, bool bModified, + bool bParentsTouched) + { + m_o = o; + m_bModified = bModified; + m_bParentsTouched = bParentsTouched; + } + } + + public sealed class IOAccessEventArgs : EventArgs + { + private IOConnectionInfo m_ioc; + public IOConnectionInfo IOConnectionInfo { get { return m_ioc; } } + + private IOConnectionInfo m_ioc2; + public IOConnectionInfo IOConnectionInfo2 { get { return m_ioc2; } } + + private IOAccessType m_t; + public IOAccessType Type { get { return m_t; } } + + public IOAccessEventArgs(IOConnectionInfo ioc, IOConnectionInfo ioc2, + IOAccessType t) + { + m_ioc = ioc; + m_ioc2 = ioc2; + m_t = t; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/PwDeletedObject.cs b/src/KeePassLib2AndroidSdkStyle/PwDeletedObject.cs new file mode 100644 index 00000000..9a648879 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/PwDeletedObject.cs @@ -0,0 +1,86 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +using KeePassLib.Interfaces; + +namespace KeePassLib +{ + /// + /// Represents an object that has been deleted. + /// + public sealed class PwDeletedObject : IDeepCloneable + { + private PwUuid m_uuid = PwUuid.Zero; + /// + /// UUID of the entry that has been deleted. + /// + public PwUuid Uuid + { + get { return m_uuid; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_uuid = value; + } + } + + private DateTime m_dtDeletionTime = PwDefs.DtDefaultNow; + /// + /// The date/time when the entry has been deleted. + /// + public DateTime DeletionTime + { + get { return m_dtDeletionTime; } + set { m_dtDeletionTime = value; } + } + + /// + /// Construct a new PwDeletedObject object. + /// + public PwDeletedObject() + { + } + + public PwDeletedObject(PwUuid uuid, DateTime dtDeletionTime) + { + if(uuid == null) throw new ArgumentNullException("uuid"); + + m_uuid = uuid; + m_dtDeletionTime = dtDeletionTime; + } + + /// + /// Clone the object. + /// + /// Value copy of the current object. + public PwDeletedObject CloneDeep() + { + PwDeletedObject pdo = new PwDeletedObject(); + + pdo.m_uuid = m_uuid; // PwUuid objects are immutable + pdo.m_dtDeletionTime = m_dtDeletionTime; + + return pdo; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/PwEntry.cs b/src/KeePassLib2AndroidSdkStyle/PwEntry.cs new file mode 100644 index 00000000..74949f10 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/PwEntry.cs @@ -0,0 +1,970 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +#if !KeePassUAP +using System.Drawing; +#endif + +using KeePassLib.Collections; +using KeePassLib.Interfaces; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib +{ + /// + /// A class representing a password entry. A password entry consists of several + /// fields like title, user name, password, etc. Each password entry has a + /// unique ID (UUID). + /// + public sealed class PwEntry : ITimeLogger, IStructureItem, IDeepCloneable + { + private PwUuid m_uuid = PwUuid.Zero; + private PwGroup m_pParentGroup = null; + private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow; + private PwUuid m_puPrevParentGroup = PwUuid.Zero; + + private ProtectedStringDictionary m_dStrings = new ProtectedStringDictionary(); + private ProtectedBinaryDictionary m_dBinaries = new ProtectedBinaryDictionary(); + private AutoTypeConfig m_cfgAutoType = new AutoTypeConfig(); + private PwObjectList m_lHistory = new PwObjectList(); + + private PwIcon m_pwIcon = PwIcon.Key; + private PwUuid m_puCustomIcon = PwUuid.Zero; + + private Color m_clrForeground = Color.Empty; + private Color m_clrBackground = Color.Empty; + + private DateTime m_tCreation = PwDefs.DtDefaultNow; + private DateTime m_tLastMod = PwDefs.DtDefaultNow; + private DateTime m_tLastAccess = PwDefs.DtDefaultNow; + private DateTime m_tExpire = PwDefs.DtDefaultNow; + private bool m_bExpires = false; + private ulong m_uUsageCount = 0; + + private string m_strOverrideUrl = string.Empty; + private bool m_bQualityCheck = true; + + private List m_lTags = new List(); + + private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); + + /// + /// UUID of this entry. + /// + public PwUuid Uuid + { + get { return m_uuid; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_uuid = value; + } + } + + /// + /// Reference to a group which contains the current entry. + /// + public PwGroup ParentGroup + { + get { return m_pParentGroup; } + + // Plugins: use PwGroup.AddEntry instead. + internal set { m_pParentGroup = value; } + } + + /// + /// The date/time when the location of the object was last changed. + /// + public DateTime LocationChanged + { + get { return m_tParentGroupLastMod; } + set { m_tParentGroupLastMod = value; } + } + + public PwUuid PreviousParentGroup + { + get { return m_puPrevParentGroup; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_puPrevParentGroup = value; + } + } + + /// + /// Get or set all entry strings. + /// + public ProtectedStringDictionary Strings + { + get { return m_dStrings; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dStrings = value; + } + } + + /// + /// Get or set all entry binaries. + /// + public ProtectedBinaryDictionary Binaries + { + get { return m_dBinaries; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dBinaries = value; + } + } + + /// + /// Get or set all auto-type window/keystroke sequence associations. + /// + public AutoTypeConfig AutoType + { + get { return m_cfgAutoType; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_cfgAutoType = value; + } + } + + /// + /// Get all previous versions of this entry (backups). + /// + public PwObjectList History + { + get { return m_lHistory; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_lHistory = value; + } + } + + /// + /// Image ID specifying the icon that will be used for this entry. + /// + public PwIcon IconId + { + get { return m_pwIcon; } + set { m_pwIcon = value; } + } + + /// + /// Get the custom icon ID. This value is 0, if no custom icon is + /// being used (i.e. the icon specified by the IconID property + /// should be displayed). + /// + public PwUuid CustomIconUuid + { + get { return m_puCustomIcon; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_puCustomIcon = value; + } + } + + /// + /// Get or set the foreground color of this entry. + /// + public Color ForegroundColor + { + get { return m_clrForeground; } + set { m_clrForeground = value; } + } + + /// + /// Get or set the background color of this entry. + /// + public Color BackgroundColor + { + get { return m_clrBackground; } + set { m_clrBackground = value; } + } + + /// + /// The date/time when this entry was created. + /// + public DateTime CreationTime + { + get { return m_tCreation; } + set { m_tCreation = value; } + } + + /// + /// The date/time when this entry was last modified. + /// + public DateTime LastModificationTime + { + get { return m_tLastMod; } + set { m_tLastMod = value; } + } + + /// + /// The date/time when this entry was last accessed (read). + /// + public DateTime LastAccessTime + { + get { return m_tLastAccess; } + set { m_tLastAccess = value; } + } + + /// + /// The date/time when this entry expires. Use the Expires property + /// to specify if the entry does actually expire or not. + /// + public DateTime ExpiryTime + { + get { return m_tExpire; } + set { m_tExpire = value; } + } + + /// + /// Specifies whether the entry expires or not. + /// + public bool Expires + { + get { return m_bExpires; } + set { m_bExpires = value; } + } + + /// + /// Get or set the usage count of the entry. To increase the usage + /// count by one, use the Touch function. + /// + public ulong UsageCount + { + get { return m_uUsageCount; } + set { m_uUsageCount = value; } + } + + /// + /// Entry-specific override URL. + /// + public string OverrideUrl + { + get { return m_strOverrideUrl; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_strOverrideUrl = value; + } + } + + public bool QualityCheck + { + get { return m_bQualityCheck; } + set { m_bQualityCheck = value; } + } + + /// + /// List of tags associated with this entry. + /// + public List Tags + { + get { StrUtil.NormalizeTags(m_lTags); return m_lTags; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_lTags = value; + } + } + + /// + /// Custom data container that can be used by plugins to store + /// own data in KeePass entries. + /// The data is stored in the encrypted part of encrypted + /// database files. + /// Use unique names for your items, e.g. "PluginName_ItemName". + /// + public StringDictionaryEx CustomData + { + get { return m_dCustomData; } + internal set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dCustomData = value; + } + } + + public static EventHandler EntryTouched; + public EventHandler Touched; + + /// + /// Construct a new, empty password entry. Member variables will be initialized + /// to their default values. + /// + /// If true, a new UUID will be created + /// for this entry. If false, the UUID is zero and you must set it + /// manually later. + /// If true, the creation, last modification + /// and last access times will be set to the current system time. + public PwEntry(bool bCreateNewUuid, bool bSetTimes) + { + if (bCreateNewUuid) m_uuid = new PwUuid(true); + + if (bSetTimes) + { + DateTime dtNow = DateTime.UtcNow; + m_tCreation = dtNow; + m_tLastMod = dtNow; + m_tLastAccess = dtNow; + m_tParentGroupLastMod = dtNow; + } + } + + /// + /// Construct a new, empty password entry. Member variables will be initialized + /// to their default values. + /// + /// Reference to the containing group, this + /// parameter may be null and set later manually. + /// If true, a new UUID will be created + /// for this entry. If false, the UUID is zero and you must set it + /// manually later. + /// If true, the creation, last modification + /// and last access times will be set to the current system time. + [Obsolete("Use a different constructor. To add an entry to a group, use AddEntry of PwGroup.")] + public PwEntry(PwGroup pwParentGroup, bool bCreateNewUuid, bool bSetTimes) + { + m_pParentGroup = pwParentGroup; + + if (bCreateNewUuid) m_uuid = new PwUuid(true); + + if (bSetTimes) + { + DateTime dtNow = DateTime.UtcNow; + m_tCreation = dtNow; + m_tLastMod = dtNow; + m_tLastAccess = dtNow; + m_tParentGroupLastMod = dtNow; + } + } + +#if DEBUG + // For display in debugger + public override string ToString() + { + return ("PwEntry '" + m_dStrings.ReadSafe(PwDefs.TitleField) + "'"); + } +#endif + + /// + /// Clone the current entry. The returned entry is an exact value copy + /// of the current entry (including UUID and parent group reference). + /// All mutable members are cloned. + /// + /// Exact value clone. All references to mutable values changed. + public PwEntry CloneDeep() + { + PwEntry peNew = new PwEntry(false, false); + + peNew.m_uuid = m_uuid; // PwUuid is immutable + peNew.m_pParentGroup = m_pParentGroup; + peNew.m_tParentGroupLastMod = m_tParentGroupLastMod; + peNew.m_puPrevParentGroup = m_puPrevParentGroup; + + peNew.m_dStrings = m_dStrings.CloneDeep(); + peNew.m_dBinaries = m_dBinaries.CloneDeep(); + peNew.m_cfgAutoType = m_cfgAutoType.CloneDeep(); + peNew.m_lHistory = m_lHistory.CloneDeep(); + + peNew.m_pwIcon = m_pwIcon; + peNew.m_puCustomIcon = m_puCustomIcon; + + peNew.m_clrForeground = m_clrForeground; + peNew.m_clrBackground = m_clrBackground; + + peNew.m_tCreation = m_tCreation; + peNew.m_tLastMod = m_tLastMod; + peNew.m_tLastAccess = m_tLastAccess; + peNew.m_tExpire = m_tExpire; + peNew.m_bExpires = m_bExpires; + peNew.m_uUsageCount = m_uUsageCount; + + peNew.m_strOverrideUrl = m_strOverrideUrl; + peNew.m_bQualityCheck = m_bQualityCheck; + + peNew.m_lTags.AddRange(m_lTags); + + peNew.m_dCustomData = m_dCustomData.CloneDeep(); + + return peNew; + } + + public PwEntry CloneStructure() + { + PwEntry peNew = new PwEntry(false, false); + + peNew.m_uuid = m_uuid; // PwUuid is immutable + peNew.m_tParentGroupLastMod = m_tParentGroupLastMod; + // Do not assign m_pParentGroup + + return peNew; + } + + private static PwCompareOptions BuildCmpOpt(bool bIgnoreParentGroup, + bool bIgnoreLastMod, bool bIgnoreLastAccess, bool bIgnoreHistory, + bool bIgnoreThisLastBackup) + { + PwCompareOptions pwOpt = PwCompareOptions.None; + if (bIgnoreParentGroup) pwOpt |= PwCompareOptions.IgnoreParentGroup; + if (bIgnoreLastMod) pwOpt |= PwCompareOptions.IgnoreLastMod; + if (bIgnoreLastAccess) pwOpt |= PwCompareOptions.IgnoreLastAccess; + if (bIgnoreHistory) pwOpt |= PwCompareOptions.IgnoreHistory; + if (bIgnoreThisLastBackup) pwOpt |= PwCompareOptions.IgnoreLastBackup; + return pwOpt; + } + + [Obsolete] + public bool EqualsEntry(PwEntry pe, bool bIgnoreParentGroup, bool bIgnoreLastMod, + bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup) + { + return EqualsEntry(pe, BuildCmpOpt(bIgnoreParentGroup, bIgnoreLastMod, + bIgnoreLastAccess, bIgnoreHistory, bIgnoreThisLastBackup), + MemProtCmpMode.None); + } + + [Obsolete] + public bool EqualsEntry(PwEntry pe, bool bIgnoreParentGroup, bool bIgnoreLastMod, + bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup, + MemProtCmpMode mpCmpStr) + { + return EqualsEntry(pe, BuildCmpOpt(bIgnoreParentGroup, bIgnoreLastMod, + bIgnoreLastAccess, bIgnoreHistory, bIgnoreThisLastBackup), mpCmpStr); + } + + public bool EqualsEntry(PwEntry pe, PwCompareOptions pwOpt, + MemProtCmpMode mpCmpStr) + { + if (pe == null) { Debug.Assert(false); return false; } + + bool bNeEqStd = ((pwOpt & PwCompareOptions.NullEmptyEquivStd) != + PwCompareOptions.None); + bool bIgnoreLastAccess = ((pwOpt & PwCompareOptions.IgnoreLastAccess) != + PwCompareOptions.None); + bool bIgnoreLastMod = ((pwOpt & PwCompareOptions.IgnoreLastMod) != + PwCompareOptions.None); + + if (!m_uuid.Equals(pe.m_uuid)) return false; + if ((pwOpt & PwCompareOptions.IgnoreParentGroup) == PwCompareOptions.None) + { + if (m_pParentGroup != pe.m_pParentGroup) return false; + if (!bIgnoreLastMod && (m_tParentGroupLastMod != pe.m_tParentGroupLastMod)) + return false; + if (!m_puPrevParentGroup.Equals(pe.m_puPrevParentGroup)) + return false; + } + + if (!m_dStrings.EqualsDictionary(pe.m_dStrings, pwOpt, mpCmpStr)) + return false; + if (!m_dBinaries.EqualsDictionary(pe.m_dBinaries)) return false; + + if (!m_cfgAutoType.Equals(pe.m_cfgAutoType)) return false; + + if ((pwOpt & PwCompareOptions.IgnoreHistory) == PwCompareOptions.None) + { + bool bIgnoreLastBackup = ((pwOpt & PwCompareOptions.IgnoreLastBackup) != + PwCompareOptions.None); + + if (!bIgnoreLastBackup && (m_lHistory.UCount != pe.m_lHistory.UCount)) + return false; + if (bIgnoreLastBackup && (m_lHistory.UCount == 0)) + { + Debug.Assert(false); + return false; + } + if (bIgnoreLastBackup && ((m_lHistory.UCount - 1) != pe.m_lHistory.UCount)) + return false; + + PwCompareOptions cmpSub = PwCompareOptions.IgnoreParentGroup; + if (bNeEqStd) cmpSub |= PwCompareOptions.NullEmptyEquivStd; + if (bIgnoreLastMod) cmpSub |= PwCompareOptions.IgnoreLastMod; + if (bIgnoreLastAccess) cmpSub |= PwCompareOptions.IgnoreLastAccess; + + for (uint uHist = 0; uHist < pe.m_lHistory.UCount; ++uHist) + { + if (!m_lHistory.GetAt(uHist).EqualsEntry(pe.m_lHistory.GetAt( + uHist), cmpSub, MemProtCmpMode.None)) + return false; + } + } + + if (m_pwIcon != pe.m_pwIcon) return false; + if (!m_puCustomIcon.Equals(pe.m_puCustomIcon)) return false; + + if (m_clrForeground != pe.m_clrForeground) return false; + if (m_clrBackground != pe.m_clrBackground) return false; + + if (m_tCreation != pe.m_tCreation) return false; + if (!bIgnoreLastMod && (m_tLastMod != pe.m_tLastMod)) return false; + if (!bIgnoreLastAccess && (m_tLastAccess != pe.m_tLastAccess)) return false; + if (m_tExpire != pe.m_tExpire) return false; + if (m_bExpires != pe.m_bExpires) return false; + if (!bIgnoreLastAccess && (m_uUsageCount != pe.m_uUsageCount)) return false; + + if (m_strOverrideUrl != pe.m_strOverrideUrl) return false; + if (m_bQualityCheck != pe.m_bQualityCheck) return false; + + // The Tags property normalizes + if (!MemUtil.ListsEqual(this.Tags, pe.Tags)) return false; + + if (!m_dCustomData.Equals(pe.m_dCustomData)) return false; + + return true; + } + + /// + /// Assign properties to the current entry based on a template entry. + /// + /// Template entry. Must not be null. + /// Only set the properties of the template entry + /// if it is newer than the current one. + /// If true, the history will be + /// copied, too. + /// If true, the + /// LocationChanged property is copied, otherwise not. + public void AssignProperties(PwEntry peTemplate, bool bOnlyIfNewer, + bool bIncludeHistory, bool bAssignLocationChanged) + { + if (peTemplate == null) { Debug.Assert(false); throw new ArgumentNullException("peTemplate"); } + + if (bOnlyIfNewer && (TimeUtil.Compare(peTemplate.m_tLastMod, + m_tLastMod, true) < 0)) + return; + + // Template UUID should be the same as the current one + Debug.Assert(m_uuid.Equals(peTemplate.m_uuid)); + m_uuid = peTemplate.m_uuid; + + if (bAssignLocationChanged) + { + m_tParentGroupLastMod = peTemplate.m_tParentGroupLastMod; + m_puPrevParentGroup = peTemplate.m_puPrevParentGroup; + } + + m_dStrings = peTemplate.m_dStrings.CloneDeep(); + m_dBinaries = peTemplate.m_dBinaries.CloneDeep(); + m_cfgAutoType = peTemplate.m_cfgAutoType.CloneDeep(); + if (bIncludeHistory) + m_lHistory = peTemplate.m_lHistory.CloneDeep(); + + m_pwIcon = peTemplate.m_pwIcon; + m_puCustomIcon = peTemplate.m_puCustomIcon; // Immutable + + m_clrForeground = peTemplate.m_clrForeground; + m_clrBackground = peTemplate.m_clrBackground; + + m_tCreation = peTemplate.m_tCreation; + m_tLastMod = peTemplate.m_tLastMod; + m_tLastAccess = peTemplate.m_tLastAccess; + m_tExpire = peTemplate.m_tExpire; + m_bExpires = peTemplate.m_bExpires; + m_uUsageCount = peTemplate.m_uUsageCount; + + m_strOverrideUrl = peTemplate.m_strOverrideUrl; + m_bQualityCheck = peTemplate.m_bQualityCheck; + + m_lTags = new List(peTemplate.m_lTags); + + m_dCustomData = peTemplate.m_dCustomData.CloneDeep(); + } + + /// + /// Touch the entry. This function updates the internal last access + /// time. If the parameter is true, + /// the last modification time gets updated, too. + /// + /// Modify last modification time. + public void Touch(bool bModified) + { + Touch(bModified, true); + } + + /// + /// Touch the entry. This function updates the internal last access + /// time. If the parameter is true, + /// the last modification time gets updated, too. + /// + /// Modify last modification time. + /// If true, all parent objects + /// get touched, too. + public void Touch(bool bModified, bool bTouchParents) + { + m_tLastAccess = DateTime.UtcNow; + ++m_uUsageCount; + + if (bModified) m_tLastMod = m_tLastAccess; + + if (this.Touched != null) + this.Touched(this, new ObjectTouchedEventArgs(this, + bModified, bTouchParents)); + if (PwEntry.EntryTouched != null) + PwEntry.EntryTouched(this, new ObjectTouchedEventArgs(this, + bModified, bTouchParents)); + + if (bTouchParents && (m_pParentGroup != null)) + m_pParentGroup.Touch(bModified, true); + } + + /// + /// Create a backup of this entry. The backup item doesn't contain any + /// history items. + /// + [Obsolete] + public void CreateBackup() + { + CreateBackup(null); + } + + /// + /// Create a backup of this entry. The backup item doesn't contain any + /// history items. + /// If this parameter isn't null, + /// the history list is maintained automatically (i.e. old backups are + /// deleted if there are too many or the history size is too large). + /// This parameter may be null (no maintenance then). + /// + public void CreateBackup(PwDatabase pwHistMntcSettings) + { + PwEntry peCopy = CloneDeep(); + peCopy.m_lHistory.Clear(); + + m_lHistory.Add(peCopy); // Must be added at end, see EqualsEntry + + if (pwHistMntcSettings != null) MaintainBackups(pwHistMntcSettings); + } + + /// + /// Restore an entry snapshot from backups. + /// + /// Index of the backup item, to which + /// should be reverted. + [Obsolete] + public void RestoreFromBackup(uint uBackupIndex) + { + RestoreFromBackup(uBackupIndex, null); + } + + /// + /// Restore an entry snapshot from backups. + /// + /// Index of the backup item, to which + /// should be reverted. + /// If this parameter isn't null, + /// the history list is maintained automatically (i.e. old backups are + /// deleted if there are too many or the history size is too large). + /// This parameter may be null (no maintenance then). + public void RestoreFromBackup(uint uBackupIndex, PwDatabase pwHistMntcSettings) + { + if (uBackupIndex >= m_lHistory.UCount) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("uBackupIndex"); + } + + PwEntry pe = m_lHistory.GetAt(uBackupIndex); + if (pe == null) { Debug.Assert(false); throw new InvalidOperationException(); } + + CreateBackup(pwHistMntcSettings); // Backup current data before restoring + AssignProperties(pe, false, false, false); + } + + public bool HasBackupOfData(PwEntry peData, bool bIgnoreLastMod, + bool bIgnoreLastAccess) + { + if (peData == null) { Debug.Assert(false); return false; } + + PwCompareOptions cmpOpt = (PwCompareOptions.IgnoreParentGroup | + PwCompareOptions.IgnoreHistory | PwCompareOptions.NullEmptyEquivStd); + if (bIgnoreLastMod) cmpOpt |= PwCompareOptions.IgnoreLastMod; + if (bIgnoreLastAccess) cmpOpt |= PwCompareOptions.IgnoreLastAccess; + + foreach (PwEntry pe in m_lHistory) + { + if (pe.EqualsEntry(peData, cmpOpt, MemProtCmpMode.None)) return true; + } + + return false; + } + + /// + /// Delete old history entries if there are too many or the + /// history size is too large. + /// If one or more history entries have been deleted, + /// true is returned. Otherwise false. + /// + public bool MaintainBackups(PwDatabase pwSettings) + { + if (pwSettings == null) { Debug.Assert(false); return false; } + + // Fix UUIDs of history entries; should not be necessary + PwUuid pu = m_uuid; + foreach (PwEntry pe in m_lHistory) + { + if (!pe.Uuid.Equals(pu)) { Debug.Assert(false); pe.Uuid = pu; } + } + + bool bDeleted = false; + + int nMaxItems = pwSettings.HistoryMaxItems; + if (nMaxItems >= 0) + { + while (m_lHistory.UCount > (uint)nMaxItems) + { + RemoveOldestBackup(); + bDeleted = true; + } + } + + long lMaxSize = pwSettings.HistoryMaxSize; + if (lMaxSize >= 0) + { + while (true) + { + ulong uHistSize = 0; + foreach (PwEntry pe in m_lHistory) { uHistSize += pe.GetSize(); } + + if (uHistSize > (ulong)lMaxSize) + { + RemoveOldestBackup(); + bDeleted = true; + } + else break; + } + } + + return bDeleted; + } + + private void RemoveOldestBackup() + { + DateTime dtMin = TimeUtil.SafeMaxValueUtc; + uint idxRemove = uint.MaxValue; + + for (uint u = 0; u < m_lHistory.UCount; ++u) + { + PwEntry pe = m_lHistory.GetAt(u); + if (TimeUtil.Compare(pe.LastModificationTime, dtMin, true) < 0) + { + idxRemove = u; + dtMin = pe.LastModificationTime; + } + } + + if (idxRemove != uint.MaxValue) m_lHistory.RemoveAt(idxRemove); + } + + public bool GetAutoTypeEnabled() + { + if (!m_cfgAutoType.Enabled) return false; + + if (m_pParentGroup != null) + return m_pParentGroup.GetAutoTypeEnabledInherited(); + + return PwGroup.DefaultAutoTypeEnabled; + } + + public string GetAutoTypeSequence() + { + string strSeq = m_cfgAutoType.DefaultSequence; + + PwGroup pg = m_pParentGroup; + while (pg != null) + { + if (strSeq.Length != 0) break; + + strSeq = pg.DefaultAutoTypeSequence; + pg = pg.ParentGroup; + } + + if (strSeq.Length != 0) return strSeq; + + if (PwDefs.IsTanEntry(this)) return PwDefs.DefaultAutoTypeSequenceTan; + return PwDefs.DefaultAutoTypeSequence; + } + + public bool GetSearchingEnabled() + { + if (m_pParentGroup != null) + return m_pParentGroup.GetSearchingEnabledInherited(); + + return PwGroup.DefaultSearchingEnabled; + } + + /// + /// Approximate the total size (in process memory) of this entry + /// in bytes (including strings, binaries and history entries). + /// + /// Size in bytes. + public ulong GetSize() + { + // This method assumes 64-bit pointers/references and Unicode + // strings (i.e. 2 bytes per character) + + ulong cb = 276; // Number of bytes; approx. fixed length data + ulong cc = 0; // Number of characters + + cb += (ulong)m_dStrings.UCount * 40; + foreach (KeyValuePair kvpStr in m_dStrings) + cc += (ulong)kvpStr.Key.Length + (ulong)kvpStr.Value.Length; + + cb += (ulong)m_dBinaries.UCount * 65; + foreach (KeyValuePair kvpBin in m_dBinaries) + { + cc += (ulong)kvpBin.Key.Length; + cb += (ulong)kvpBin.Value.Length; + } + + cc += (ulong)m_cfgAutoType.DefaultSequence.Length; + cb += (ulong)m_cfgAutoType.AssociationsCount * 24; + foreach (AutoTypeAssociation a in m_cfgAutoType.Associations) + cc += (ulong)a.WindowName.Length + (ulong)a.Sequence.Length; + + cb += (ulong)m_lHistory.UCount * 8; + foreach (PwEntry peHistory in m_lHistory) + cb += peHistory.GetSize(); + + cc += (ulong)m_strOverrideUrl.Length; + + cb += (ulong)m_lTags.Count * 8; + foreach (string strTag in m_lTags) + cc += (ulong)strTag.Length; + + cb += (ulong)m_dCustomData.Count * 16; + foreach (KeyValuePair kvp in m_dCustomData) + cc += (ulong)kvp.Key.Length + (ulong)kvp.Value.Length; + + return (cb + (cc << 1)); + } + + public bool HasTag(string strTag) + { + if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } + + // this.Tags normalizes + return this.Tags.Contains(StrUtil.NormalizeTag(strTag)); + } + + public bool AddTag(string strTag) + { + if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } + + strTag = StrUtil.NormalizeTag(strTag); + if (this.Tags.Contains(strTag)) return false; // this.Tags normalizes + + m_lTags.Add(strTag); + return true; + } + + public bool RemoveTag(string strTag) + { + if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } + + // this.Tags normalizes + return this.Tags.Remove(StrUtil.NormalizeTag(strTag)); + } + + internal List GetTagsInherited() + { + List l = ((m_pParentGroup != null) ? + m_pParentGroup.GetTagsInherited(false) : new List()); + l.AddRange(this.Tags); + StrUtil.NormalizeTags(l); + return l; + } + + public bool IsContainedIn(PwGroup pgContainer) + { + PwGroup pgCur = m_pParentGroup; + while (pgCur != null) + { + if (pgCur == pgContainer) return true; + + pgCur = pgCur.ParentGroup; + } + + return false; + } + + public void SetUuid(PwUuid pwNewUuid, bool bAlsoChangeHistoryUuids) + { + this.Uuid = pwNewUuid; + + if (bAlsoChangeHistoryUuids) + { + foreach (PwEntry peHist in m_lHistory) + peHist.Uuid = pwNewUuid; + } + } + + public void SetCreatedNow() + { + DateTime dt = DateTime.UtcNow; + + m_tCreation = dt; + m_tLastAccess = dt; + } + + public PwEntry Duplicate() + { + PwEntry pe = CloneDeep(); + + pe.SetUuid(new PwUuid(true), true); + pe.SetCreatedNow(); + + return pe; + } + } + + public sealed class PwEntryComparer : IComparer + { + private string m_strFieldName; + private bool m_bCaseInsensitive; + private bool m_bCompareNaturally; + + public PwEntryComparer(string strFieldName, bool bCaseInsensitive, + bool bCompareNaturally) + { + if (strFieldName == null) throw new ArgumentNullException("strFieldName"); + + m_strFieldName = strFieldName; + m_bCaseInsensitive = bCaseInsensitive; + m_bCompareNaturally = bCompareNaturally; + } + + public int Compare(PwEntry a, PwEntry b) + { + string strA = a.Strings.ReadSafe(m_strFieldName); + string strB = b.Strings.ReadSafe(m_strFieldName); + + if (m_bCompareNaturally) return StrUtil.CompareNaturally(strA, strB); + + return string.Compare(strA, strB, m_bCaseInsensitive); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/PwEnums.cs b/src/KeePassLib2AndroidSdkStyle/PwEnums.cs new file mode 100644 index 00000000..3d95dd9d --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/PwEnums.cs @@ -0,0 +1,328 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; + +namespace KeePassLib +{ + /// + /// Compression algorithm specifiers. + /// + public enum PwCompressionAlgorithm + { + /// + /// No compression. + /// + None = 0, + + /// + /// GZip compression. + /// + GZip = 1, + + /// + /// Virtual field: currently known number of algorithms. Should not be used + /// by plugins or libraries -- it's used internally only. + /// + Count = 2 + } + + /// + /// Tree traversal methods. + /// + public enum TraversalMethod + { + /// + /// Don't traverse the tree. + /// + None = 0, + + /// + /// Traverse the tree in pre-order mode, i.e. first visit all items + /// in the current node, then visit all subnodes. + /// + PreOrder = 1 + } + + /// + /// Methods for merging password databases/entries. + /// + public enum PwMergeMethod + { + // Do not change the explicitly assigned values, otherwise + // serialization (e.g. of Ecas triggers) breaks + None = 0, + OverwriteExisting = 1, + KeepExisting = 2, + OverwriteIfNewer = 3, + CreateNewUuids = 4, + Synchronize = 5 + } + + /// + /// Icon identifiers for groups and password entries. + /// + public enum PwIcon + { + Key = 0, + World, + Warning, + NetworkServer, + MarkedDirectory, + UserCommunication, + Parts, + Notepad, + WorldSocket, + Identity, + PaperReady, + Digicam, + IRCommunication, + MultiKeys, + Energy, + Scanner, + WorldStar, + CDRom, + Monitor, + EMail, + Configuration, + ClipboardReady, + PaperNew, + Screen, + EnergyCareful, + EMailBox, + Disk, + Drive, + PaperQ, + TerminalEncrypted, + Console, + Printer, + ProgramIcons, + Run, + Settings, + WorldComputer, + Archive, + Homebanking, + DriveWindows, + Clock, + EMailSearch, + PaperFlag, + Memory, + TrashBin, + Note, + Expired, + Info, + Package, + Folder, + FolderOpen, + FolderPackage, + LockOpen, + PaperLocked, + Checked, + Pen, + Thumbnail, + Book, + List, + UserKey, + Tool, + Home, + Star, + Tux, + Feather, + Apple, + Wiki, + Money, + Certificate, + BlackBerry, + + /// + /// Virtual identifier -- represents the number of icons. + /// + Count + } + + public enum ProxyServerType + { + None = 0, + System = 1, + Manual = 2 + } + + public enum ProxyAuthType + { + None = 0, + + /// + /// Use default user credentials (provided by the system). + /// + Default = 1, + + Manual = 2, + + /// + /// Default or Manual, depending on whether + /// manual credentials are available. + /// This type exists for supporting upgrading from KeePass + /// 2.28 to 2.29; the user cannot select this type. + /// + Auto = 3 + } + + /// + /// Comparison modes for in-memory protected objects. + /// + public enum MemProtCmpMode + { + /// + /// Ignore the in-memory protection states. + /// + None = 0, + + /// + /// Ignore the in-memory protection states of standard + /// objects; do compare in-memory protection states of + /// custom objects. + /// + CustomOnly, + + /// + /// Compare in-memory protection states. + /// + Full + } + + [Flags] + public enum PwCompareOptions + { + None = 0x0, + + /// + /// Empty standard string fields are considered to be the + /// same as non-existing standard string fields. + /// This doesn't affect custom string comparisons. + /// + NullEmptyEquivStd = 0x1, + + IgnoreParentGroup = 0x2, + IgnoreLastAccess = 0x4, + IgnoreLastMod = 0x8, + IgnoreHistory = 0x10, + IgnoreLastBackup = 0x20, + + // For groups: + PropertiesOnly = 0x40, + + IgnoreTimes = (IgnoreLastAccess | IgnoreLastMod) + } + + public enum IOAccessType + { + None = 0, + + /// + /// The IO connection is being opened for reading. + /// + Read = 1, + + /// + /// The IO connection is being opened for writing. + /// + Write = 2, + + /// + /// The IO connection is being opened for testing + /// whether a file/object exists. + /// + Exists = 3, + + /// + /// The IO connection is being opened for deleting a file/object. + /// + Delete = 4, + + /// + /// The IO connection is being opened for renaming/moving a file/object. + /// + Move = 5 + } + + // public enum PwLogicalOp + // { + // None = 0, + // Or = 1, + // And = 2, + // NOr = 3, + // NAnd = 4 + // } + + [Flags] + public enum AppRunFlags + { + None = 0, + GetStdOutput = 1, + WaitForExit = 2, + + // https://sourceforge.net/p/keepass/patches/84/ + /// + /// This flag prevents any handles being garbage-collected + /// before the started process has terminated, without + /// blocking the current thread. + /// + GCKeepAlive = 4, + + // https://sourceforge.net/p/keepass/patches/85/ + DoEvents = 8, + DisableForms = 16 + } + + [Flags] + public enum ScaleTransformFlags + { + None = 0, + + /// + /// UIIcon indicates that the returned image is going + /// to be displayed as icon in the UI and that it is not + /// subject to future changes in size. + /// + UIIcon = 1 + } + + public enum DesktopType + { + None = 0, + Windows, + Gnome, + Kde, + Unity, + Lxde, + Xfce, + Mate, + Cinnamon, + Pantheon + } + + + public enum PwSearchMode + { + None = 0, + Simple, + Regular, + XPath + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/PwGroup.Search.cs b/src/KeePassLib2AndroidSdkStyle/PwGroup.Search.cs new file mode 100644 index 00000000..57adf6da --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/PwGroup.Search.cs @@ -0,0 +1,372 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; +using System.Xml.XPath; + +using KeePassLib.Collections; +using KeePassLib.Delegates; +using KeePassLib.Interfaces; +using KeePassLib.Security; +using KeePassLib.Serialization; +using KeePassLib.Utility; + +namespace KeePassLib +{ + public sealed partial class PwGroup + { + private const int SearchContextStringMaxLength = 50; // Note, doesn't include elipsis, if added + public const string SearchContextUuid = "Uuid"; + public const string SearchContextParentGroup = "Parent Group"; + public const string SearchContextTags = "Tags"; + + /// + /// Search this group and all subgroups for entries. + /// + /// Specifies the search method. + /// Entry list in which the search results will + /// be stored. + public void SearchEntries(SearchParameters sp, PwObjectList listStorage) + { + SearchEntries(sp, listStorage, null); + } + + /// + /// Search this group and all subgroups for entries. + /// + /// Specifies the search method. + /// Entry list in which the search results will + /// be stored. + /// Optional status reporting object. + public void SearchEntries(SearchParameters sp, PwObjectList listStorage, + IStatusLogger slStatus) + { + SearchEntries(sp, listStorage, null, slStatus); + } + + /// + /// Search this group and all subgroups for entries. + /// + /// Specifies the search method. + /// Entry list in which the search results will + /// be stored. + /// Dictionary that will be populated with text fragments indicating the context of why each entry (keyed by Uuid) was returned + public void SearchEntries(SearchParameters sp, PwObjectList listStorage, + IDictionary> resultContexts, + IStatusLogger slStatus) + { + + if (sp == null) + { + Debug.Assert(false); + return; + } + + if (listStorage == null) + { + Debug.Assert(false); + return; + } + + ulong uCurEntries = 0, uTotalEntries = 0; + + List lTerms = StrUtil.SplitSearchTerms(sp.SearchString); + if ((lTerms.Count <= 1) || sp.RegularExpression) + { + if (slStatus != null) uTotalEntries = GetEntriesCount(true); + SearchEntriesSingle(sp, listStorage, resultContexts, slStatus, ref uCurEntries, + uTotalEntries); + return; + } + + // Search longer strings first (for improved performance) + lTerms.Sort(StrUtil.CompareLengthGt); + + string strFullSearch = sp.SearchString; // Backup + + PwGroup pg = this; + for (int iTerm = 0; iTerm < lTerms.Count; ++iTerm) + { + // Update counters for a better state guess + if (slStatus != null) + { + ulong uRemRounds = (ulong) (lTerms.Count - iTerm); + uTotalEntries = uCurEntries + (uRemRounds * + pg.GetEntriesCount(true)); + } + + PwGroup pgNew = new PwGroup(); + + sp.SearchString = lTerms[iTerm]; + + bool bNegate = false; + if (sp.SearchString.StartsWith("-")) + { + sp.SearchString = sp.SearchString.Substring(1); + bNegate = (sp.SearchString.Length > 0); + } + + if (!pg.SearchEntriesSingle(sp, pgNew.Entries, resultContexts, slStatus, + ref uCurEntries, uTotalEntries)) + { + pg = null; + break; + } + + if (bNegate) + { + PwObjectList lCand = pg.GetEntries(true); + + pg = new PwGroup(); + foreach (PwEntry peCand in lCand) + { + if (pgNew.Entries.IndexOf(peCand) < 0) pg.Entries.Add(peCand); + } + } + else pg = pgNew; + } + + if (pg != null) listStorage.Add(pg.Entries); + sp.SearchString = strFullSearch; // Restore + } + + private bool SearchEntriesSingle(SearchParameters spIn, + PwObjectList listStorage, IDictionary> resultContexts, + IStatusLogger slStatus, + ref ulong uCurEntries, ulong uTotalEntries) + { + SearchParameters sp = spIn.Clone(); + if (sp.SearchString == null) + { + Debug.Assert(false); + return true; + } + + sp.SearchString = sp.SearchString.Trim(); + + bool bTitle = sp.SearchInTitles; + bool bUserName = sp.SearchInUserNames; + bool bPassword = sp.SearchInPasswords; + bool bUrl = sp.SearchInUrls; + bool bNotes = sp.SearchInNotes; + bool bOther = sp.SearchInOther; + bool bUuids = sp.SearchInUuids; + bool bGroupName = sp.SearchInGroupNames; + bool bTags = sp.SearchInTags; + bool bExcludeExpired = sp.ExcludeExpired; + bool bRespectEntrySearchingDisabled = sp.RespectEntrySearchingDisabled; + + DateTime dtNow = DateTime.Now; + + Regex rx = null; + if (sp.RegularExpression) + { + RegexOptions ro = RegexOptions.None; // RegexOptions.Compiled + if ((sp.ComparisonMode == StringComparison.CurrentCultureIgnoreCase) || +#if !KeePassUAP + (sp.ComparisonMode == StringComparison.InvariantCultureIgnoreCase) || +#endif + (sp.ComparisonMode == StringComparison.OrdinalIgnoreCase)) + { + ro |= RegexOptions.IgnoreCase; + } + + rx = new Regex(sp.SearchString, ro); + } + + ulong uLocalCurEntries = uCurEntries; + + EntryHandler eh = null; + if (sp.SearchString.Length <= 0) // Report all + { + eh = delegate(PwEntry pe) + { + if (slStatus != null) + { + if (!slStatus.SetProgress((uint) ((uLocalCurEntries * + 100UL) / uTotalEntries))) return false; + ++uLocalCurEntries; + } + + if (bRespectEntrySearchingDisabled && !pe.GetSearchingEnabled()) + return true; // Skip + if (bExcludeExpired && pe.Expires && (dtNow > pe.ExpiryTime)) + return true; // Skip + + listStorage.Add(pe); + return true; + }; + } + else + { + eh = delegate(PwEntry pe) + { + if (slStatus != null) + { + if (!slStatus.SetProgress((uint) ((uLocalCurEntries * + 100UL) / uTotalEntries))) return false; + ++uLocalCurEntries; + } + + if (bRespectEntrySearchingDisabled && !pe.GetSearchingEnabled()) + return true; // Skip + if (bExcludeExpired && pe.Expires && (dtNow > pe.ExpiryTime)) + return true; // Skip + + uint uInitialResults = listStorage.UCount; + + foreach (KeyValuePair kvp in pe.Strings) + { + string strKey = kvp.Key; + + if (strKey == PwDefs.TitleField) + { + if (bTitle) + SearchEvalAdd(sp, kvp.Value.ReadString(), + rx, pe, listStorage, resultContexts, strKey); + } + else if (strKey == PwDefs.UserNameField) + { + if (bUserName) + SearchEvalAdd(sp, kvp.Value.ReadString(), + rx, pe, listStorage, resultContexts, strKey); + } + else if (strKey == PwDefs.PasswordField) + { + if (bPassword) + SearchEvalAdd(sp, kvp.Value.ReadString(), + rx, pe, listStorage, resultContexts, strKey); + } + else if (strKey == PwDefs.UrlField) + { + if (bUrl) + SearchEvalAdd(sp, kvp.Value.ReadString(), + rx, pe, listStorage, resultContexts, strKey); + } + else if (strKey == PwDefs.NotesField) + { + if (bNotes) + SearchEvalAdd(sp, kvp.Value.ReadString(), + rx, pe, listStorage, resultContexts, strKey); + } + else if (bOther) + SearchEvalAdd(sp, kvp.Value.ReadString(), + rx, pe, listStorage, resultContexts, strKey); + + // An entry can match only once => break if we have added it + if (listStorage.UCount > uInitialResults) break; + } + + if (bUuids && (listStorage.UCount == uInitialResults)) + SearchEvalAdd(sp, pe.Uuid.ToHexString(), rx, pe, listStorage, resultContexts, + SearchContextTags); + + if (bGroupName && (listStorage.UCount == uInitialResults) && + (pe.ParentGroup != null)) + SearchEvalAdd(sp, pe.ParentGroup.Name, rx, pe, listStorage, resultContexts, + SearchContextParentGroup); + + if (bTags) + { + foreach (string strTag in pe.Tags) + { + if (listStorage.UCount != uInitialResults) break; // Match + + SearchEvalAdd(sp, strTag, rx, pe, listStorage, resultContexts, SearchContextTags); + } + } + + return true; + }; + } + + if (!PreOrderTraverseTree(null, eh)) return false; + uCurEntries = uLocalCurEntries; + return true; + } + + private static void SearchEvalAdd(SearchParameters sp, string strDataField, + Regex rx, PwEntry pe, PwObjectList lResults, + IDictionary> resultContexts, string contextFieldName) + { + bool bMatch = false; + int matchPos; + if (rx == null) + { + matchPos = strDataField.IndexOf(sp.SearchString, sp.ComparisonMode); + bMatch = matchPos >= 0; + } + else + { + var match = rx.Match(strDataField); + bMatch = match.Success; + matchPos = match.Index; + } + + if (!bMatch && (sp.DataTransformationFn != null)) + { + string strCmp = sp.DataTransformationFn(strDataField, pe); + if (!object.ReferenceEquals(strCmp, strDataField)) + { + if (rx == null) + { + matchPos = strCmp.IndexOf(sp.SearchString, sp.ComparisonMode); + bMatch = matchPos >= 0; + } + else + { + var match = rx.Match(strCmp); + bMatch = match.Success; + matchPos = match.Index; + } + } + } + + if (bMatch) + { + lResults.Add(pe); + + if (resultContexts != null) + { + // Trim the value if necessary + var contextString = strDataField; + if (contextString.Length > SearchContextStringMaxLength) + { + // Start 10% before actual data, and don't run over + var startPos = Math.Max(0, + Math.Min(matchPos - (SearchContextStringMaxLength / 10), + contextString.Length - SearchContextStringMaxLength)); + contextString = "… " + contextString.Substring(startPos, SearchContextStringMaxLength) + + ((startPos + SearchContextStringMaxLength < contextString.Length) + ? " …" + : null); + } + + resultContexts[pe.Uuid] = new KeyValuePair(contextFieldName, contextString); + } + } + } + + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/PwGroup.cs b/src/KeePassLib2AndroidSdkStyle/PwGroup.cs new file mode 100644 index 00000000..3dbae05d --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/PwGroup.cs @@ -0,0 +1,1573 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Collections; +using KeePassLib.Delegates; +using KeePassLib.Interfaces; +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib +{ + /// + /// A group containing subgroups and entries. + /// + public sealed partial 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 PwUuid m_uuid = PwUuid.Zero; + private PwGroup m_pParentGroup = null; + private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow; + private PwUuid m_puPrevParentGroup = PwUuid.Zero; + + private PwObjectList m_listGroups = new PwObjectList(); + private PwObjectList m_listEntries = new PwObjectList(); + + private string m_strName = string.Empty; + private string m_strNotes = string.Empty; + + private PwIcon m_pwIcon = PwIcon.Folder; + private PwUuid m_pwCustomIconID = PwUuid.Zero; + + private DateTime m_tCreation = PwDefs.DtDefaultNow; + private DateTime m_tLastMod = PwDefs.DtDefaultNow; + private DateTime m_tLastAccess = PwDefs.DtDefaultNow; + private DateTime m_tExpire = PwDefs.DtDefaultNow; + private bool m_bExpires = false; + private ulong m_uUsageCount = 0; + + private bool m_bIsExpanded = true; + private bool m_bVirtual = false; + + private string m_strDefaultAutoTypeSequence = string.Empty; + + private bool? m_bEnableAutoType = null; + private bool? m_bEnableSearching = null; + + private PwUuid m_pwLastTopVisibleEntry = PwUuid.Zero; + + private List m_lTags = new List(); + + private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); + + /// + /// UUID of this group. + /// + public PwUuid Uuid + { + get { return m_uuid; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_uuid = value; + } + } + + /// + /// Reference to the group to which this group belongs. May be null. + /// + public PwGroup ParentGroup + { + get { return m_pParentGroup; } + + // Plugins: use the PwGroup.AddGroup method instead. + // Internal: check depth using CanAddGroup/CheckCanAddGroup. + internal set { Debug.Assert(value != this); m_pParentGroup = value; } + } + + /// + /// The date/time when the location of the object was last changed. + /// + public DateTime LocationChanged + { + get { return m_tParentGroupLastMod; } + set { m_tParentGroupLastMod = value; } + } + + public PwUuid PreviousParentGroup + { + get { return m_puPrevParentGroup; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_puPrevParentGroup = value; + } + } + + /// + /// The name of this group. Cannot be null. + /// + public string Name + { + get { return m_strName; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_strName = value; + } + } + + /// + /// Comments about this group. Cannot be null. + /// + public string Notes + { + get { return m_strNotes; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_strNotes = value; + } + } + + /// + /// Icon of the group. + /// + public PwIcon IconId + { + get { return m_pwIcon; } + set { m_pwIcon = value; } + } + + /// + /// Get the custom icon ID. This value is 0, if no custom icon is + /// being used (i.e. the icon specified by the IconID property + /// should be displayed). + /// + public PwUuid CustomIconUuid + { + get { return m_pwCustomIconID; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_pwCustomIconID = value; + } + } + + /// + /// A flag that specifies if the group is shown as expanded or + /// collapsed in the user interface. + /// + public bool IsExpanded + { + get { return m_bIsExpanded; } + set { m_bIsExpanded = value; } + } + + /// + /// The date/time when this group was created. + /// + public DateTime CreationTime + { + get { return m_tCreation; } + set { m_tCreation = value; } + } + + /// + /// The date/time when this group was last modified. + /// + public DateTime LastModificationTime + { + get { return m_tLastMod; } + set { m_tLastMod = value; } + } + + /// + /// The date/time when this group was last accessed (read). + /// + public DateTime LastAccessTime + { + get { return m_tLastAccess; } + set { m_tLastAccess = value; } + } + + /// + /// The date/time when this group expires. + /// + public DateTime ExpiryTime + { + get { return m_tExpire; } + set { m_tExpire = value; } + } + + /// + /// Flag that determines if the group expires. + /// + public bool Expires + { + get { return m_bExpires; } + set { m_bExpires = value; } + } + + /// + /// Get or set the usage count of the group. To increase the usage + /// count by one, use the Touch function. + /// + public ulong UsageCount + { + get { return m_uUsageCount; } + set { m_uUsageCount = value; } + } + + /// + /// Get a list of subgroups in this group. + /// + public PwObjectList Groups + { + get { return m_listGroups; } + } + + /// + /// Get a list of entries in this group. + /// + public PwObjectList Entries + { + get { return m_listEntries; } + } + + /// + /// A flag specifying whether this group is virtual or not. Virtual + /// groups can contain links to entries stored in other groups. + /// Note that this flag has to be interpreted and set by the calling + /// code; it won't prevent you from accessing and modifying the list + /// of entries in this group in any way. + /// + public bool IsVirtual + { + get { return m_bVirtual; } + set { m_bVirtual = value; } + } + + /// + /// Default auto-type keystroke sequence for all entries in + /// this group. This property can be an empty string, which + /// means that the value should be inherited from the parent. + /// + public string DefaultAutoTypeSequence + { + get { return m_strDefaultAutoTypeSequence; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_strDefaultAutoTypeSequence = value; + } + } + + public bool? EnableAutoType + { + get { return m_bEnableAutoType; } + set { m_bEnableAutoType = value; } + } + + public bool? EnableSearching + { + get { return m_bEnableSearching; } + set { m_bEnableSearching = value; } + } + + public PwUuid LastTopVisibleEntry + { + get { return m_pwLastTopVisibleEntry; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_pwLastTopVisibleEntry = value; + } + } + + public List Tags + { + get { StrUtil.NormalizeTags(m_lTags); return m_lTags; } + set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_lTags = value; + } + } + + /// + /// Custom data container that can be used by plugins to store + /// own data in KeePass groups. + /// The data is stored in the encrypted part of encrypted + /// database files. + /// Use unique names for your items, e.g. "PluginName_ItemName". + /// + public StringDictionaryEx CustomData + { + get { return m_dCustomData; } + internal set + { + if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dCustomData = value; + } + } + + public static EventHandler GroupTouched; + public EventHandler Touched; + + /// + /// Construct a new, empty group. + /// + public PwGroup() + { + } + + /// + /// Construct a new, empty group. + /// + /// Create a new UUID for this group. + /// Set creation, last access and last modification times to the current time. + public PwGroup(bool bCreateNewUuid, bool bSetTimes) + { + if (bCreateNewUuid) m_uuid = new PwUuid(true); + + if (bSetTimes) + { + DateTime dtNow = DateTime.UtcNow; + m_tCreation = dtNow; + m_tLastMod = dtNow; + m_tLastAccess = dtNow; + m_tParentGroupLastMod = dtNow; + } + } + + /// + /// Construct a new group. + /// + /// Create a new UUID for this group. + /// Set creation, last access and last modification times to the current time. + /// Name of the new group. + /// Icon of the new group. + public PwGroup(bool bCreateNewUuid, bool bSetTimes, string strName, PwIcon pwIcon) + { + if (bCreateNewUuid) m_uuid = new PwUuid(true); + + if (bSetTimes) + { + DateTime dtNow = DateTime.UtcNow; + m_tCreation = dtNow; + m_tLastMod = dtNow; + m_tLastAccess = dtNow; + m_tParentGroupLastMod = dtNow; + } + + if (strName != null) m_strName = strName; + + m_pwIcon = pwIcon; + } + +#if DEBUG + // For display in debugger + public override string ToString() + { + return (@"PwGroup '" + m_strName + @"'"); + } +#endif + + /// + /// Deeply clone the current group. The returned group will be an exact + /// value copy of the current object (including UUID, etc.). + /// + /// Exact value copy of the current PwGroup object. + public PwGroup CloneDeep() + { + PwGroup pg = new PwGroup(false, false); + + pg.m_uuid = m_uuid; // PwUuid is immutable + + pg.m_listGroups = m_listGroups.CloneDeep(); + pg.m_listEntries = m_listEntries.CloneDeep(); + pg.TakeOwnership(true, true, false); + + pg.m_pParentGroup = m_pParentGroup; + pg.m_tParentGroupLastMod = m_tParentGroupLastMod; + pg.m_puPrevParentGroup = m_puPrevParentGroup; + + pg.m_strName = m_strName; + pg.m_strNotes = m_strNotes; + + pg.m_pwIcon = m_pwIcon; + pg.m_pwCustomIconID = m_pwCustomIconID; + + pg.m_tCreation = m_tCreation; + pg.m_tLastMod = m_tLastMod; + pg.m_tLastAccess = m_tLastAccess; + pg.m_tExpire = m_tExpire; + pg.m_bExpires = m_bExpires; + pg.m_uUsageCount = m_uUsageCount; + + pg.m_bIsExpanded = m_bIsExpanded; + pg.m_bVirtual = m_bVirtual; + + pg.m_strDefaultAutoTypeSequence = m_strDefaultAutoTypeSequence; + + pg.m_bEnableAutoType = m_bEnableAutoType; + pg.m_bEnableSearching = m_bEnableSearching; + + pg.m_pwLastTopVisibleEntry = m_pwLastTopVisibleEntry; + + pg.m_lTags.AddRange(m_lTags); + + pg.m_dCustomData = m_dCustomData.CloneDeep(); + + return pg; + } + + public PwGroup CloneStructure() + { + PwGroup pg = new PwGroup(false, false); + + pg.m_uuid = m_uuid; // PwUuid is immutable + pg.m_tParentGroupLastMod = m_tParentGroupLastMod; + // Do not assign m_pParentGroup + + foreach (PwGroup pgSub in m_listGroups) + pg.AddGroup(pgSub.CloneStructure(), true); + + foreach (PwEntry peSub in m_listEntries) + pg.AddEntry(peSub.CloneStructure(), true); + + return pg; + } + + public bool EqualsGroup(PwGroup pg, PwCompareOptions pwOpt, + MemProtCmpMode mpCmpStr) + { + if (pg == null) { Debug.Assert(false); return false; } + + bool bIgnoreLastAccess = ((pwOpt & PwCompareOptions.IgnoreLastAccess) != + PwCompareOptions.None); + bool bIgnoreLastMod = ((pwOpt & PwCompareOptions.IgnoreLastMod) != + PwCompareOptions.None); + + if (!m_uuid.Equals(pg.m_uuid)) return false; + if ((pwOpt & PwCompareOptions.IgnoreParentGroup) == PwCompareOptions.None) + { + if (m_pParentGroup != pg.m_pParentGroup) return false; + if (!bIgnoreLastMod && (m_tParentGroupLastMod != pg.m_tParentGroupLastMod)) + return false; + if (!m_puPrevParentGroup.Equals(pg.m_puPrevParentGroup)) + return false; + } + + if (m_strName != pg.m_strName) return false; + if (m_strNotes != pg.m_strNotes) return false; + + if (m_pwIcon != pg.m_pwIcon) return false; + if (!m_pwCustomIconID.Equals(pg.m_pwCustomIconID)) return false; + + if (m_tCreation != pg.m_tCreation) return false; + if (!bIgnoreLastMod && (m_tLastMod != pg.m_tLastMod)) return false; + if (!bIgnoreLastAccess && (m_tLastAccess != pg.m_tLastAccess)) return false; + if (m_tExpire != pg.m_tExpire) return false; + if (m_bExpires != pg.m_bExpires) return false; + if (!bIgnoreLastAccess && (m_uUsageCount != pg.m_uUsageCount)) return false; + + // if(m_bIsExpanded != pg.m_bIsExpanded) return false; + + if (m_strDefaultAutoTypeSequence != pg.m_strDefaultAutoTypeSequence) return false; + + if (m_bEnableAutoType.HasValue != pg.m_bEnableAutoType.HasValue) return false; + if (m_bEnableAutoType.HasValue) + { + if (m_bEnableAutoType.Value != pg.m_bEnableAutoType.Value) return false; + } + if (m_bEnableSearching.HasValue != pg.m_bEnableSearching.HasValue) return false; + if (m_bEnableSearching.HasValue) + { + if (m_bEnableSearching.Value != pg.m_bEnableSearching.Value) return false; + } + + if (!m_pwLastTopVisibleEntry.Equals(pg.m_pwLastTopVisibleEntry)) return false; + + // The Tags property normalizes + if (!MemUtil.ListsEqual(this.Tags, pg.Tags)) return false; + + if (!m_dCustomData.Equals(pg.m_dCustomData)) return false; + + if ((pwOpt & PwCompareOptions.PropertiesOnly) == PwCompareOptions.None) + { + if (m_listEntries.UCount != pg.m_listEntries.UCount) return false; + for (uint u = 0; u < m_listEntries.UCount; ++u) + { + PwEntry peA = m_listEntries.GetAt(u); + PwEntry peB = pg.m_listEntries.GetAt(u); + if (!peA.EqualsEntry(peB, pwOpt, mpCmpStr)) return false; + } + + if (m_listGroups.UCount != pg.m_listGroups.UCount) return false; + for (uint u = 0; u < m_listGroups.UCount; ++u) + { + PwGroup pgA = m_listGroups.GetAt(u); + PwGroup pgB = pg.m_listGroups.GetAt(u); + if (!pgA.EqualsGroup(pgB, pwOpt, mpCmpStr)) return false; + } + } + + return true; + } + + /// + /// Assign properties to the current group based on a template group. + /// + /// Template group. Must not be null. + /// Only set the properties of the template group + /// if it is newer than the current one. + /// If true, the + /// LocationChanged property is copied, otherwise not. + public void AssignProperties(PwGroup pgTemplate, bool bOnlyIfNewer, + bool bAssignLocationChanged) + { + Debug.Assert(pgTemplate != null); if (pgTemplate == null) throw new ArgumentNullException("pgTemplate"); + + if (bOnlyIfNewer && (TimeUtil.Compare(pgTemplate.m_tLastMod, m_tLastMod, + true) < 0)) + return; + + // Template UUID should be the same as the current one + Debug.Assert(m_uuid.Equals(pgTemplate.m_uuid)); + m_uuid = pgTemplate.m_uuid; + + if (bAssignLocationChanged) + { + m_tParentGroupLastMod = pgTemplate.m_tParentGroupLastMod; + m_puPrevParentGroup = pgTemplate.m_puPrevParentGroup; + } + + m_strName = pgTemplate.m_strName; + m_strNotes = pgTemplate.m_strNotes; + + m_pwIcon = pgTemplate.m_pwIcon; + m_pwCustomIconID = pgTemplate.m_pwCustomIconID; + + m_tCreation = pgTemplate.m_tCreation; + m_tLastMod = pgTemplate.m_tLastMod; + m_tLastAccess = pgTemplate.m_tLastAccess; + m_tExpire = pgTemplate.m_tExpire; + m_bExpires = pgTemplate.m_bExpires; + m_uUsageCount = pgTemplate.m_uUsageCount; + + m_strDefaultAutoTypeSequence = pgTemplate.m_strDefaultAutoTypeSequence; + + m_bEnableAutoType = pgTemplate.m_bEnableAutoType; + m_bEnableSearching = pgTemplate.m_bEnableSearching; + + m_pwLastTopVisibleEntry = pgTemplate.m_pwLastTopVisibleEntry; + + m_lTags = new List(pgTemplate.m_lTags); + + m_dCustomData = pgTemplate.m_dCustomData.CloneDeep(); + } + + /// + /// Touch the group. This function updates the internal last access + /// time. If the parameter is true, + /// the last modification time gets updated, too. + /// + /// Modify last modification time. + public void Touch(bool bModified) + { + Touch(bModified, true); + } + + /// + /// Touch the group. This function updates the internal last access + /// time. If the parameter is true, + /// the last modification time gets updated, too. + /// + /// Modify last modification time. + /// If true, all parent objects + /// get touched, too. + public void Touch(bool bModified, bool bTouchParents) + { + m_tLastAccess = DateTime.UtcNow; + ++m_uUsageCount; + + if (bModified) m_tLastMod = m_tLastAccess; + + if (this.Touched != null) + this.Touched(this, new ObjectTouchedEventArgs(this, + bModified, bTouchParents)); + if (PwGroup.GroupTouched != null) + PwGroup.GroupTouched(this, new ObjectTouchedEventArgs(this, + bModified, bTouchParents)); + + if (bTouchParents && (m_pParentGroup != null)) + m_pParentGroup.Touch(bModified, true); + } + + /// + /// Get number of groups and entries in the current group. This function + /// can also traverse through all subgroups and accumulate their counts + /// (recursive mode). + /// + /// If this parameter is true, all + /// subgroups and entries in subgroups will be counted and added to + /// the returned value. If it is false, only the number of + /// subgroups and entries of the current group is returned. + /// Number of subgroups. + /// Number of entries. + public void GetCounts(bool bRecursive, out uint uNumGroups, out uint uNumEntries) + { + if (bRecursive) + { + uint uTotalGroups = m_listGroups.UCount; + uint uTotalEntries = m_listEntries.UCount; + uint uSubGroupCount, uSubEntryCount; + + foreach (PwGroup pg in m_listGroups) + { + pg.GetCounts(true, out uSubGroupCount, out uSubEntryCount); + + uTotalGroups += uSubGroupCount; + uTotalEntries += uSubEntryCount; + } + + uNumGroups = uTotalGroups; + uNumEntries = uTotalEntries; + } + else // !bRecursive + { + uNumGroups = m_listGroups.UCount; + uNumEntries = m_listEntries.UCount; + } + } + + public uint GetEntriesCount(bool bRecursive) + { + uint uGroups, uEntries; + GetCounts(bRecursive, out uGroups, out uEntries); + return uEntries; + } + + /// + /// Traverse the group/entry tree in the current group. Various traversal + /// methods are available. + /// + /// Specifies the traversal method. + /// Function that performs an action on + /// the currently visited group (see GroupHandler for more). + /// This parameter may be null, in this case the tree is traversed but + /// you don't get notifications for each visited group. + /// Function that performs an action on + /// the currently visited entry (see EntryHandler for more). + /// This parameter may be null. + /// Returns true if all entries and groups have been + /// traversed. If the traversal has been canceled by one of the two + /// handlers, the return value is false. + public bool TraverseTree(TraversalMethod tm, GroupHandler groupHandler, EntryHandler entryHandler) + { + bool bRet = false; + + switch (tm) + { + case TraversalMethod.None: + bRet = true; + break; + case TraversalMethod.PreOrder: + bRet = PreOrderTraverseTree(groupHandler, entryHandler); + break; + default: + Debug.Assert(false); + break; + } + + return bRet; + } + + private bool PreOrderTraverseTree(GroupHandler groupHandler, EntryHandler entryHandler) + { + if (entryHandler != null) + { + foreach (PwEntry pe in m_listEntries) + { + if (!entryHandler(pe)) return false; + } + } + + foreach (PwGroup pg in m_listGroups) + { + if (groupHandler != null) + { + if (!groupHandler(pg)) return false; + } + + if (!pg.PreOrderTraverseTree(groupHandler, entryHandler)) + return false; + } + + return true; + } + + /// + /// Pack all groups into one flat linked list of references (recursively). + /// + /// Flat list of all groups. + public LinkedList GetFlatGroupList() + { + LinkedList list = new LinkedList(); + + foreach (PwGroup pg in m_listGroups) + { + list.AddLast(pg); + + if (pg.Groups.UCount != 0) + LinearizeGroupRecursive(list, pg, 1); + } + + return list; + } + + private void LinearizeGroupRecursive(LinkedList list, PwGroup pg, ushort uLevel) + { + Debug.Assert(pg != null); if (pg == null) return; + + foreach (PwGroup pwg in pg.Groups) + { + list.AddLast(pwg); + + if (pwg.Groups.UCount != 0) + LinearizeGroupRecursive(list, pwg, (ushort)(uLevel + 1)); + } + } + + /// + /// Pack all entries into one flat linked list of references. Temporary + /// group IDs are assigned automatically. + /// + /// A flat group list created by + /// GetFlatGroupList. + /// Flat list of all entries. + public static LinkedList GetFlatEntryList(LinkedList flatGroupList) + { + Debug.Assert(flatGroupList != null); if (flatGroupList == null) return null; + + LinkedList list = new LinkedList(); + foreach (PwGroup pg in flatGroupList) + { + foreach (PwEntry pe in pg.Entries) + list.AddLast(pe); + } + + return list; + } + + /// + /// Enable protection of a specific string field type. + /// + /// Name of the string field to protect or unprotect. + /// Enable protection or not. + /// Returns true, if the operation completed successfully, + /// otherwise false. + public bool EnableStringFieldProtection(string strFieldName, bool bEnable) + { + Debug.Assert(strFieldName != null); + + EntryHandler eh = delegate (PwEntry pe) + { + // Enable protection of current string + pe.Strings.EnableProtection(strFieldName, bEnable); + + // Do the same for all history items + foreach (PwEntry peHistory in pe.History) + { + peHistory.Strings.EnableProtection(strFieldName, bEnable); + } + + return true; + }; + + return PreOrderTraverseTree(null, eh); + } + + internal List GetTagsInherited(bool bNormalize) + { + List l = new List(); + + PwGroup pg = this; + while (pg != null) + { + l.AddRange(pg.Tags); + pg = pg.m_pParentGroup; + } + + if (bNormalize) StrUtil.NormalizeTags(l); + return l; + } + + public List BuildEntryTagsList() + { + return BuildEntryTagsList(false, false); + } + + public List BuildEntryTagsList(bool bSort) + { + return BuildEntryTagsList(bSort, false); + } + + internal List BuildEntryTagsList(bool bSort, bool bGroupTags) + { + Dictionary d = new Dictionary(); + + GroupHandler gh = null; + if (bGroupTags) + { + gh = delegate (PwGroup pg) + { + foreach (string strTag in pg.Tags) d[strTag] = true; + return true; + }; + } + + EntryHandler eh = delegate (PwEntry pe) + { + foreach (string strTag in pe.Tags) d[strTag] = true; + return true; + }; + + if (gh != null) gh(this); + TraverseTree(TraversalMethod.PreOrder, gh, eh); + + List l = new List(d.Keys); + if (bSort) l.Sort(StrUtil.CompareNaturally); + + return l; + } + +#if !KeePassLibSD + public IDictionary BuildEntryTagsDict(bool bSort) + { + Debug.Assert(!bSort); // Obsolete + + IDictionary d; + if (!bSort) d = new Dictionary(); + else d = new SortedDictionary(); + + GroupHandler gh = delegate (PwGroup pg) + { + foreach (string strTag in pg.Tags) + { + // For groups without entries + if (!d.ContainsKey(strTag)) d[strTag] = 0; + } + + return true; + }; + + EntryHandler eh = delegate (PwEntry pe) + { + foreach (string strTag in pe.GetTagsInherited()) + { + uint u; + d.TryGetValue(strTag, out u); + d[strTag] = u + 1; + } + + return true; + }; + + gh(this); + TraverseTree(TraversalMethod.PreOrder, gh, eh); + + return d; + } +#endif + + public void FindEntriesByTag(string strTag, PwObjectList listStorage, + bool bSearchRecursive) + { + if (strTag == null) throw new ArgumentNullException("strTag"); + + strTag = StrUtil.NormalizeTag(strTag); + if (string.IsNullOrEmpty(strTag)) return; + + EntryHandler eh = delegate (PwEntry pe) + { + foreach (string strEntryTag in pe.GetTagsInherited()) + { + if (strEntryTag == strTag) + { + listStorage.Add(pe); + break; + } + } + + return true; + }; + + if (bSearchRecursive) + TraverseTree(TraversalMethod.PreOrder, null, eh); + else + { + foreach (PwEntry pe in m_listEntries) eh(pe); + } + } + + /// + /// Find a group. + /// + /// UUID identifying the group the caller is looking for. + /// If true, the search is recursive. + /// Returns reference to found group, otherwise null. + public PwGroup FindGroup(PwUuid uuid, bool bSearchRecursive) + { + // Do not assert on PwUuid.Zero + if (m_uuid.Equals(uuid)) return this; + + if (bSearchRecursive) + { + PwGroup pgRec; + foreach (PwGroup pg in m_listGroups) + { + pgRec = pg.FindGroup(uuid, true); + if (pgRec != null) return pgRec; + } + } + else // Not recursive + { + foreach (PwGroup pg in m_listGroups) + { + if (pg.m_uuid.Equals(uuid)) + return pg; + } + } + + return null; + } + + /// + /// Find an object. + /// + /// UUID of the object to find. + /// Specifies whether to search recursively. + /// If null, groups and entries are + /// searched. If true, only entries are searched. If false, + /// only groups are searched. + /// Reference to the object, if found. Otherwise null. + public IStructureItem FindObject(PwUuid uuid, bool bRecursive, + bool? bEntries) + { + if (bEntries.HasValue) + { + if (bEntries.Value) return FindEntry(uuid, bRecursive); + else return FindGroup(uuid, bRecursive); + } + + PwGroup pg = FindGroup(uuid, bRecursive); + if (pg != null) return pg; + return FindEntry(uuid, bRecursive); + } + + /// + /// Try to find a subgroup and create it, if it doesn't exist yet. + /// + /// Name of the subgroup. + /// If the group isn't found: create it. + /// Returns a reference to the requested group or null if + /// it doesn't exist and shouldn't be created. + public PwGroup FindCreateGroup(string strName, bool bCreateIfNotFound) + { + Debug.Assert(strName != null); if (strName == null) throw new ArgumentNullException("strName"); + + foreach (PwGroup pg in m_listGroups) + { + if (pg.Name == strName) return pg; + } + + if (!bCreateIfNotFound) return null; + + PwGroup pgNew = new PwGroup(true, true, strName, PwIcon.Folder); + AddGroup(pgNew, true); + return pgNew; + } + + /// + /// Find an entry. + /// + /// UUID identifying the entry the caller is looking for. + /// If true, the search is recursive. + /// Returns reference to found entry, otherwise null. + public PwEntry FindEntry(PwUuid uuid, bool bSearchRecursive) + { + foreach (PwEntry pe in m_listEntries) + { + if (pe.Uuid.Equals(uuid)) return pe; + } + + if (bSearchRecursive) + { + PwEntry peSub; + foreach (PwGroup pg in m_listGroups) + { + peSub = pg.FindEntry(uuid, true); + if (peSub != null) return peSub; + } + } + + return null; + } + + /// + /// Get the full path of a group. + /// + /// Full path of the group. + public string GetFullPath() + { + return GetFullPath(".", false); + } + + /// + /// Get the full path of a group. + /// + /// String that separates the group + /// names. + /// Specifies whether the returned + /// path starts with the topmost group. + /// Full path of the group. + public string GetFullPath(string strSeparator, bool bIncludeTopMostGroup) + { + Debug.Assert(strSeparator != null); + if (strSeparator == null) throw new ArgumentNullException("strSeparator"); + + string strPath = m_strName; + + PwGroup pg = m_pParentGroup; + while (pg != null) + { + if (!bIncludeTopMostGroup && (pg.m_pParentGroup == null)) + break; + + strPath = pg.Name + strSeparator + strPath; + + pg = pg.m_pParentGroup; + } + + return strPath; + } + + /// + /// Assign new UUIDs to groups and entries. + /// + /// Create new UUIDs for subgroups. + /// Create new UUIDs for entries. + /// Recursive tree traversal. + public void CreateNewItemUuids(bool bNewGroups, bool bNewEntries, bool bRecursive) + { + if (bNewGroups) + { + foreach (PwGroup pg in m_listGroups) + pg.Uuid = new PwUuid(true); + } + + if (bNewEntries) + { + foreach (PwEntry pe in m_listEntries) + pe.SetUuid(new PwUuid(true), true); + } + + if (bRecursive) + { + foreach (PwGroup pg in m_listGroups) + pg.CreateNewItemUuids(bNewGroups, bNewEntries, true); + } + } + + public void TakeOwnership(bool bTakeSubGroups, bool bTakeEntries, bool bRecursive) + { + if (bTakeSubGroups) + { + foreach (PwGroup pg in m_listGroups) + pg.ParentGroup = this; + } + + if (bTakeEntries) + { + foreach (PwEntry pe in m_listEntries) + pe.ParentGroup = this; + } + + if (bRecursive) + { + foreach (PwGroup pg in m_listGroups) + pg.TakeOwnership(bTakeSubGroups, bTakeEntries, true); + } + } + +#if !KeePassLibSD + /// + /// Find/create a subtree of groups. + /// + /// Tree string. + /// Separators that delimit groups in the + /// strTree parameter. + public PwGroup FindCreateSubTree(string strTree, char[] vSeparators) + { + return FindCreateSubTree(strTree, vSeparators, true); + } + + public PwGroup FindCreateSubTree(string strTree, char[] vSeparators, + bool bAllowCreate) + { + if (vSeparators == null) { Debug.Assert(false); vSeparators = new char[0]; } + + string[] v = new string[vSeparators.Length]; + for (int i = 0; i < vSeparators.Length; ++i) + v[i] = new string(vSeparators[i], 1); + + return FindCreateSubTree(strTree, v, bAllowCreate); + } + + public PwGroup FindCreateSubTree(string strTree, string[] vSeparators, + bool bAllowCreate) + { + Debug.Assert(strTree != null); if (strTree == null) return this; + if (strTree.Length == 0) return this; + + string[] vGroups = strTree.Split(vSeparators, StringSplitOptions.None); + if ((vGroups == null) || (vGroups.Length == 0)) return this; + + PwGroup pgContainer = this; + for (int nGroup = 0; nGroup < vGroups.Length; ++nGroup) + { + if (string.IsNullOrEmpty(vGroups[nGroup])) continue; + + bool bFound = false; + foreach (PwGroup pg in pgContainer.Groups) + { + if (pg.Name == vGroups[nGroup]) + { + pgContainer = pg; + bFound = true; + break; + } + } + + if (!bFound) + { + if (!bAllowCreate) return null; + + PwGroup pg = new PwGroup(true, true, vGroups[nGroup], PwIcon.Folder); + pgContainer.AddGroup(pg, true); + pgContainer = pg; + } + } + + return pgContainer; + } +#endif + + /// + /// Get the depth of this group (i.e. the number of ancestors). + /// + /// Depth of this group. + public uint GetDepth() + { + PwGroup pg = m_pParentGroup; + uint d = 0; + + while (pg != null) + { + pg = pg.m_pParentGroup; + ++d; + } + + 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() + { + if (m_strDefaultAutoTypeSequence.Length > 0) + return m_strDefaultAutoTypeSequence; + + if (m_pParentGroup != null) + return m_pParentGroup.GetAutoTypeSequenceInherited(); + + return string.Empty; + } + + public bool GetAutoTypeEnabledInherited() + { + if (m_bEnableAutoType.HasValue) return m_bEnableAutoType.Value; + + if (m_pParentGroup != null) + return m_pParentGroup.GetAutoTypeEnabledInherited(); + + return DefaultAutoTypeEnabled; + } + + public bool GetSearchingEnabledInherited() + { + if (m_bEnableSearching.HasValue) return m_bEnableSearching.Value; + + if (m_pParentGroup != null) + return m_pParentGroup.GetSearchingEnabledInherited(); + + return DefaultSearchingEnabled; + } + + /// + /// Get a list of subgroups (not including this one). + /// + /// If true, subgroups are added + /// recursively, i.e. all child groups are returned, too. + /// List of subgroups. If is + /// true, it is guaranteed that subsubgroups appear after + /// subgroups. + public PwObjectList GetGroups(bool bRecursive) + { + if (!bRecursive) return m_listGroups; + + PwObjectList list = m_listGroups.CloneShallow(); + foreach (PwGroup pgSub in m_listGroups) + { + list.Add(pgSub.GetGroups(true)); + } + + return list; + } + + public PwObjectList GetEntries(bool bIncludeSubGroupEntries) + { + PwObjectList l = new PwObjectList(); + + GroupHandler gh = delegate (PwGroup pg) + { + l.Add(pg.Entries); + return true; + }; + + gh(this); + if (bIncludeSubGroupEntries) + PreOrderTraverseTree(gh, null); + + Debug.Assert(l.UCount == GetEntriesCount(bIncludeSubGroupEntries)); + return l; + } + + /// + /// Get objects contained in this group. + /// + /// Specifies whether to search recursively. + /// If null, the returned list contains + /// groups and entries. If true, the returned list contains only + /// entries. If false, the returned list contains only groups. + /// List of objects. + public List GetObjects(bool bRecursive, bool? bEntries) + { + List list = new List(); + + if (!bEntries.HasValue || !bEntries.Value) + { + PwObjectList lGroups = GetGroups(bRecursive); + foreach (PwGroup pg in lGroups) list.Add(pg); + } + + if (!bEntries.HasValue || bEntries.Value) + { + PwObjectList lEntries = GetEntries(bRecursive); + foreach (PwEntry pe in lEntries) list.Add(pe); + } + + return list; + } + + public bool IsContainedIn(PwGroup pgContainer) + { + PwGroup pgCur = m_pParentGroup; + while (pgCur != null) + { + if (pgCur == pgContainer) return true; + + pgCur = pgCur.m_pParentGroup; + } + + return false; + } + + /// + /// Add a subgroup to this group. + /// + /// Group to be added. Must not be null. + /// If this parameter is true, the + /// parent group reference of the subgroup will be set to the current + /// group (i.e. the current group takes ownership of the subgroup). + public void AddGroup(PwGroup subGroup, bool bTakeOwnership) + { + AddGroup(subGroup, bTakeOwnership, false); + } + + /// + /// Add a subgroup to this group. + /// + /// Group to be added. Must not be null. + /// If this parameter is true, the + /// parent group reference of the subgroup will be set to the current + /// group (i.e. the current group takes ownership of the subgroup). + /// If true, the + /// LocationChanged property of the subgroup is updated. + public void AddGroup(PwGroup subGroup, bool bTakeOwnership, + bool bUpdateLocationChangedOfSub) + { + if (subGroup == null) throw new ArgumentNullException("subGroup"); + + CheckCanAddGroup(subGroup); + m_listGroups.Add(subGroup); + + 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. + /// + /// Entry to be added. Must not be null. + /// If this parameter is true, the + /// parent group reference of the entry will be set to the current + /// group (i.e. the current group takes ownership of the entry). + public void AddEntry(PwEntry pe, bool bTakeOwnership) + { + AddEntry(pe, bTakeOwnership, false); + } + + /// + /// Add an entry to this group. + /// + /// Entry to be added. Must not be null. + /// If this parameter is true, the + /// parent group reference of the entry will be set to the current + /// group (i.e. the current group takes ownership of the entry). + /// If true, the + /// LocationChanged property of the entry is updated. + public void AddEntry(PwEntry pe, bool bTakeOwnership, + bool bUpdateLocationChangedOfEntry) + { + if (pe == null) throw new ArgumentNullException("pe"); + + m_listEntries.Add(pe); + + // Do not remove the entry from its previous parent group, + // only assign it to the new one + if (bTakeOwnership) pe.ParentGroup = this; + + if (bUpdateLocationChangedOfEntry) pe.LocationChanged = DateTime.UtcNow; + } + + public void SortSubGroups(bool bRecursive) + { + m_listGroups.Sort(new PwGroupComparer()); + + if (bRecursive) + { + foreach (PwGroup pgSub in m_listGroups) + pgSub.SortSubGroups(true); + } + } + + public void DeleteAllObjects(PwDatabase pdContext) + { + DateTime dtNow = DateTime.UtcNow; + + foreach (PwEntry pe in m_listEntries) + { + PwDeletedObject pdo = new PwDeletedObject(pe.Uuid, dtNow); + pdContext.DeletedObjects.Add(pdo); + } + m_listEntries.Clear(); + + foreach (PwGroup pg in m_listGroups) + { + pg.DeleteAllObjects(pdContext); + + PwDeletedObject pdo = new PwDeletedObject(pg.Uuid, dtNow); + pdContext.DeletedObjects.Add(pdo); + } + m_listGroups.Clear(); + } + + internal List GetTopSearchSkippedGroups() + { + List l = new List(); + + if (!GetSearchingEnabledInherited()) l.Add(this); + else GetTopSearchSkippedGroupsRec(l); + + return l; + } + + private void GetTopSearchSkippedGroupsRec(List l) + { + if (m_bEnableSearching.HasValue && !m_bEnableSearching.Value) + { + l.Add(this); + return; + } + else { Debug.Assert(GetSearchingEnabledInherited()); } + + foreach (PwGroup pgSub in m_listGroups) + pgSub.GetTopSearchSkippedGroupsRec(l); + } + + public void SetCreatedNow(bool bRecursive) + { + DateTime dt = DateTime.UtcNow; + + m_tCreation = dt; + m_tLastAccess = dt; + + if (!bRecursive) return; + + GroupHandler gh = delegate (PwGroup pg) + { + pg.m_tCreation = dt; + pg.m_tLastAccess = dt; + return true; + }; + + EntryHandler eh = delegate (PwEntry pe) + { + pe.CreationTime = dt; + pe.LastAccessTime = dt; + return true; + }; + + TraverseTree(TraversalMethod.PreOrder, gh, eh); + } + + public PwGroup Duplicate() + { + PwGroup pg = CloneDeep(); + + pg.Uuid = new PwUuid(true); + pg.CreateNewItemUuids(true, true, true); + + pg.SetCreatedNow(true); + + return pg; + } + + + + internal string[] GetAutoTypeSequences(bool bWithStd) + { + try + { + Dictionary d = new Dictionary(); + + Action fAdd = delegate (string str) + { + if (!string.IsNullOrEmpty(str)) d[str] = true; + }; + + if (bWithStd) + { + fAdd(PwDefs.DefaultAutoTypeSequence); + fAdd(PwDefs.DefaultAutoTypeSequenceTan); + } + + GroupHandler gh = delegate (PwGroup pg) + { + fAdd(pg.DefaultAutoTypeSequence); + return true; + }; + + EntryHandler eh = delegate (PwEntry pe) + { + AutoTypeConfig c = pe.AutoType; + + fAdd(c.DefaultSequence); + foreach (AutoTypeAssociation a in c.Associations) + { + fAdd(a.Sequence); + } + + return true; + }; + + gh(this); + TraverseTree(TraversalMethod.PreOrder, gh, eh); + + string[] v = new string[d.Count]; + if (d.Count != 0) + { + d.Keys.CopyTo(v, 0); + Array.Sort(v, StrUtil.CaseIgnoreComparer); + } + + return v; + } + catch (Exception) { Debug.Assert(false); } + + return new string[0]; + } + } + + public sealed class PwGroupComparer : IComparer + { + public PwGroupComparer() + { + } + + public int Compare(PwGroup a, PwGroup b) + { + return StrUtil.CompareNaturally(a.Name, b.Name); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/PwUuid.cs b/src/KeePassLib2AndroidSdkStyle/PwUuid.cs new file mode 100644 index 00000000..06cb77b4 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/PwUuid.cs @@ -0,0 +1,215 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Xml; +using System.Diagnostics; + +using KeePassLib.Utility; + +namespace KeePassLib +{ + // [ImmutableObject(true)] + /// + /// Represents an UUID of a password entry or group. Once created, + /// PwUuid objects aren't modifyable anymore (immutable). + /// + public sealed class PwUuid : IComparable, IEquatable + { + /// + /// Standard size in bytes of a UUID. + /// + public const uint UuidSize = 16; + + /// + /// Zero UUID (all bytes are zero). + /// + public static readonly PwUuid Zero = new PwUuid(false); + + private byte[] m_pbUuid = null; // Never null after constructor + + /// + /// Get the 16 UUID bytes. + /// + public byte[] UuidBytes + { + get { return m_pbUuid; } + } + + /// + /// Construct a new UUID object. + /// + /// If this parameter is true, a new + /// UUID is generated. If it is false, the UUID is initialized + /// to zero. + public PwUuid(bool bCreateNew) + { + if(bCreateNew) CreateNew(); + else SetZero(); + } + + /// + /// Construct a new UUID object. + /// + /// Initial value of the PwUuid object. + public PwUuid(byte[] uuidBytes) + { + SetValue(uuidBytes); + } + + /// + /// Create a new, random UUID. + /// + /// Returns true if a random UUID has been generated, + /// otherwise it returns false. + private void CreateNew() + { + Debug.Assert(m_pbUuid == null); // Only call from constructor + while(true) + { + m_pbUuid = Guid.NewGuid().ToByteArray(); + + if((m_pbUuid == null) || (m_pbUuid.Length != (int)UuidSize)) + { + Debug.Assert(false); + throw new InvalidOperationException(); + } + + // Zero is a reserved value -- do not generate Zero + if(!Equals(PwUuid.Zero)) break; + Debug.Assert(false); + } + } + + private void SetValue(byte[] uuidBytes) + { + Debug.Assert((uuidBytes != null) && (uuidBytes.Length == (int)UuidSize)); + if(uuidBytes == null) throw new ArgumentNullException("uuidBytes"); + if(uuidBytes.Length != (int)UuidSize) throw new ArgumentException(); + + Debug.Assert(m_pbUuid == null); // Only call from constructor + m_pbUuid = new byte[UuidSize]; + + Array.Copy(uuidBytes, m_pbUuid, (int)UuidSize); + } + + private void SetZero() + { + Debug.Assert(m_pbUuid == null); // Only call from constructor + m_pbUuid = new byte[UuidSize]; + + // Array.Clear(m_pbUuid, 0, (int)UuidSize); +#if DEBUG + List l = new List(m_pbUuid); + Debug.Assert(l.TrueForAll(bt => (bt == 0))); +#endif + } + + [Obsolete] + public bool EqualsValue(PwUuid uuid) + { + return Equals(uuid); + } + + public override bool Equals(object obj) + { + return Equals(obj as PwUuid); + } + + public bool Equals(PwUuid other) + { + if(other == null) { Debug.Assert(false); return false; } + + for(int i = 0; i < (int)UuidSize; ++i) + { + if(m_pbUuid[i] != other.m_pbUuid[i]) return false; + } + + return true; + } + + private int m_h = 0; + public override int GetHashCode() + { + if(m_h == 0) + m_h = (int)MemUtil.Hash32(m_pbUuid, 0, m_pbUuid.Length); + return m_h; + } + + public int CompareTo(PwUuid other) + { + if(other == null) + { + Debug.Assert(false); + throw new ArgumentNullException("other"); + } + + for(int i = 0; i < (int)UuidSize; ++i) + { + if(m_pbUuid[i] < other.m_pbUuid[i]) return -1; + if(m_pbUuid[i] > other.m_pbUuid[i]) return 1; + } + + return 0; + } + + /// + /// Convert the UUID to its string representation. + /// + /// String containing the UUID value. + public string ToHexString() + { + return MemUtil.ByteArrayToHexString(m_pbUuid); + } + +#if DEBUG + public override string ToString() + { + return ToHexString(); + } +#endif + } + + [Obsolete] + public sealed class PwUuidComparable : IComparable + { + private byte[] m_pbUuid = new byte[PwUuid.UuidSize]; + + public PwUuidComparable(PwUuid pwUuid) + { + if(pwUuid == null) throw new ArgumentNullException("pwUuid"); + + Array.Copy(pwUuid.UuidBytes, m_pbUuid, (int)PwUuid.UuidSize); + } + + public int CompareTo(PwUuidComparable other) + { + if(other == null) throw new ArgumentNullException("other"); + + for(int i = 0; i < (int)PwUuid.UuidSize; ++i) + { + if(m_pbUuid[i] < other.m_pbUuid[i]) return -1; + if(m_pbUuid[i] > other.m_pbUuid[i]) return 1; + } + + return 0; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Resources/AboutResources.txt b/src/KeePassLib2AndroidSdkStyle/Resources/AboutResources.txt new file mode 100644 index 00000000..6dff7bcc --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Resources/AboutResources.txt @@ -0,0 +1,44 @@ +Images, layout descriptions, binary blobs and string dictionaries can be included +in your application as resource files. Various Android APIs are designed to +operate on the resource IDs instead of dealing with images, strings or binary blobs +directly. + +For example, a sample Android app that contains a user interface layout (main.axml), +an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) +would keep its resources in the "Resources" directory of the application: + +Resources/ + drawable/ + icon.png + + layout/ + main.axml + + values/ + strings.xml + +In order to get the build system to recognize Android resources, set the build action to +"AndroidResource". The native Android APIs do not operate directly with filenames, but +instead operate on resource IDs. When you compile an Android application that uses resources, +the build system will package the resources for distribution and generate a class called "R" +(this is an Android convention) that contains the tokens for each one of the resources +included. For example, for the above Resources layout, this is what the R class would expose: + +public class R { + public class drawable { + public const int icon = 0x123; + } + + public class layout { + public const int main = 0x456; + } + + public class strings { + public const int first_string = 0xabc; + public const int second_string = 0xbcd; + } +} + +You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main +to reference the layout/main.axml file, or R.Strings.first_string to reference the first +string in the dictionary file values/strings.xml. diff --git a/src/KeePassLib2AndroidSdkStyle/Resources/KLRes.Generated.cs b/src/KeePassLib2AndroidSdkStyle/Resources/KLRes.Generated.cs new file mode 100644 index 00000000..194dd121 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Resources/KLRes.Generated.cs @@ -0,0 +1,663 @@ +// This is a generated file! +// Do not edit manually, changes will be overwritten. + +using System; +using System.Collections.Generic; + +namespace KeePassLib.Resources +{ + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + public static class KLRes + { + private static string TryGetEx(Dictionary dictNew, + string strName, string strDefault) + { + string strTemp; + + if (dictNew.TryGetValue(strName, out strTemp)) + return strTemp; + + return strDefault; + } + + public static void SetTranslatedStrings(Dictionary dictNew) + { + if (dictNew == null) throw new ArgumentNullException("dictNew"); + + m_strAlgorithmUnknown = TryGetEx(dictNew, "AlgorithmUnknown", m_strAlgorithmUnknown); + m_strCharSetInvalid = TryGetEx(dictNew, "CharSetInvalid", m_strCharSetInvalid); + m_strCharSetTooFewChars = TryGetEx(dictNew, "CharSetTooFewChars", m_strCharSetTooFewChars); + m_strCryptoStreamFailed = TryGetEx(dictNew, "CryptoStreamFailed", m_strCryptoStreamFailed); + m_strEncDataTooLarge = TryGetEx(dictNew, "EncDataTooLarge", m_strEncDataTooLarge); + m_strErrorInClipboard = TryGetEx(dictNew, "ErrorInClipboard", m_strErrorInClipboard); + m_strExpect100Continue = TryGetEx(dictNew, "Expect100Continue", m_strExpect100Continue); + m_strFatalError = TryGetEx(dictNew, "FatalError", m_strFatalError); + m_strFatalErrorText = TryGetEx(dictNew, "FatalErrorText", m_strFatalErrorText); + m_strFileCorrupted = TryGetEx(dictNew, "FileCorrupted", m_strFileCorrupted); + m_strFileHeaderCorrupted = TryGetEx(dictNew, "FileHeaderCorrupted", m_strFileHeaderCorrupted); + m_strFileIncomplete = TryGetEx(dictNew, "FileIncomplete", m_strFileIncomplete); + m_strFileIncompleteExpc = TryGetEx(dictNew, "FileIncompleteExpc", m_strFileIncompleteExpc); + m_strFileLoadFailed = TryGetEx(dictNew, "FileLoadFailed", m_strFileLoadFailed); + m_strFileLockedWrite = TryGetEx(dictNew, "FileLockedWrite", m_strFileLockedWrite); + m_strFileNewVerOrPlgReq = TryGetEx(dictNew, "FileNewVerOrPlgReq", m_strFileNewVerOrPlgReq); + m_strFileNewVerReq = TryGetEx(dictNew, "FileNewVerReq", m_strFileNewVerReq); + m_strFileSaveCorruptionWarning = TryGetEx(dictNew, "FileSaveCorruptionWarning", m_strFileSaveCorruptionWarning); + m_strFileSaveFailed2 = TryGetEx(dictNew, "FileSaveFailed2", m_strFileSaveFailed2); + m_strFileSigInvalid = TryGetEx(dictNew, "FileSigInvalid", m_strFileSigInvalid); + m_strFileUnknownCipher = TryGetEx(dictNew, "FileUnknownCipher", m_strFileUnknownCipher); + m_strFileUnknownCompression = TryGetEx(dictNew, "FileUnknownCompression", m_strFileUnknownCompression); + m_strFileVersionUnsupported = TryGetEx(dictNew, "FileVersionUnsupported", m_strFileVersionUnsupported); + m_strFinalKeyCreationFailed = TryGetEx(dictNew, "FinalKeyCreationFailed", m_strFinalKeyCreationFailed); + m_strFrameworkNotImplExcp = TryGetEx(dictNew, "FrameworkNotImplExcp", m_strFrameworkNotImplExcp); + m_strGeneral = TryGetEx(dictNew, "General", m_strGeneral); + m_strInvalidCompositeKey = TryGetEx(dictNew, "InvalidCompositeKey", m_strInvalidCompositeKey); + m_strInvalidCompositeKeyHint = TryGetEx(dictNew, "InvalidCompositeKeyHint", m_strInvalidCompositeKeyHint); + m_strInvalidDataWhileDecoding = TryGetEx(dictNew, "InvalidDataWhileDecoding", m_strInvalidDataWhileDecoding); + m_strKeePass1xHint = TryGetEx(dictNew, "KeePass1xHint", m_strKeePass1xHint); + m_strKeyBits = TryGetEx(dictNew, "KeyBits", m_strKeyBits); + m_strKeyFileDbSel = TryGetEx(dictNew, "KeyFileDbSel", m_strKeyFileDbSel); + m_strKeyHashMismatch = TryGetEx(dictNew, "KeyHashMismatch", m_strKeyHashMismatch); + m_strMasterSeedLengthInvalid = TryGetEx(dictNew, "MasterSeedLengthInvalid", m_strMasterSeedLengthInvalid); + m_strOldFormat = TryGetEx(dictNew, "OldFormat", m_strOldFormat); + m_strPassive = TryGetEx(dictNew, "Passive", m_strPassive); + m_strPathBackslash = TryGetEx(dictNew, "PathBackslash", m_strPathBackslash); + m_strPatternInvalid = TryGetEx(dictNew, "PatternInvalid", m_strPatternInvalid); + m_strPreAuth = TryGetEx(dictNew, "PreAuth", m_strPreAuth); + m_strPwGenFailed = TryGetEx(dictNew, "PwGenFailed", m_strPwGenFailed); + m_strStructsTooDeep = TryGetEx(dictNew, "StructsTooDeep", m_strStructsTooDeep); + m_strTimeout = TryGetEx(dictNew, "Timeout", m_strTimeout); + m_strTryAgainSecs = TryGetEx(dictNew, "TryAgainSecs", m_strTryAgainSecs); + m_strUnknownError = TryGetEx(dictNew, "UnknownError", m_strUnknownError); + m_strUnknownHeaderId = TryGetEx(dictNew, "UnknownHeaderId", m_strUnknownHeaderId); + m_strUnknownKdf = TryGetEx(dictNew, "UnknownKdf", m_strUnknownKdf); + m_strUserAccountKeyError = TryGetEx(dictNew, "UserAccountKeyError", m_strUserAccountKeyError); + m_strUserAgent = TryGetEx(dictNew, "UserAgent", m_strUserAgent); + } + + private static readonly string[] m_vKeyNames = { + "AlgorithmUnknown", + "CharSetInvalid", + "CharSetTooFewChars", + "CryptoStreamFailed", + "EncDataTooLarge", + "ErrorInClipboard", + "Expect100Continue", + "FatalError", + "FatalErrorText", + "FileCorrupted", + "FileHeaderCorrupted", + "FileIncomplete", + "FileIncompleteExpc", + "FileLoadFailed", + "FileLockedWrite", + "FileNewVerOrPlgReq", + "FileNewVerReq", + "FileSaveCorruptionWarning", + "FileSaveFailed2", + "FileSigInvalid", + "FileUnknownCipher", + "FileUnknownCompression", + "FileVersionUnsupported", + "FinalKeyCreationFailed", + "FrameworkNotImplExcp", + "General", + "InvalidCompositeKey", + "InvalidCompositeKeyHint", + "InvalidDataWhileDecoding", + "KeePass1xHint", + "KeyBits", + "KeyFileDbSel", + "KeyHashMismatch", + "MasterSeedLengthInvalid", + "OldFormat", + "Passive", + "PathBackslash", + "PatternInvalid", + "PreAuth", + "PwGenFailed", + "StructsTooDeep", + "Timeout", + "TryAgainSecs", + "UnknownError", + "UnknownHeaderId", + "UnknownKdf", + "UserAccountKeyError", + "UserAgent" + }; + + public static string[] GetKeyNames() + { + return m_vKeyNames; + } + + private static string m_strAlgorithmUnknown = + @"The algorithm is unknown."; + /// + /// Look up a localized string similar to + /// 'The algorithm is unknown.'. + /// + public static string AlgorithmUnknown + { + get { return m_strAlgorithmUnknown; } + } + + private static string m_strCharSetInvalid = + @"The character set is invalid."; + /// + /// Look up a localized string similar to + /// 'The character set is invalid.'. + /// + public static string CharSetInvalid + { + get { return m_strCharSetInvalid; } + } + + private static string m_strCharSetTooFewChars = + @"There are too few characters in the character set."; + /// + /// Look up a localized string similar to + /// 'There are too few characters in the character set.'. + /// + public static string CharSetTooFewChars + { + get { return m_strCharSetTooFewChars; } + } + + private static string m_strCryptoStreamFailed = + @"Failed to initialize encryption/decryption stream!"; + /// + /// Look up a localized string similar to + /// 'Failed to initialize encryption/decryption stream!'. + /// + public static string CryptoStreamFailed + { + get { return m_strCryptoStreamFailed; } + } + + private static string m_strEncDataTooLarge = + @"The data is too large to be encrypted/decrypted securely using {PARAM}."; + /// + /// Look up a localized string similar to + /// 'The data is too large to be encrypted/decrypted securely using {PARAM}.'. + /// + public static string EncDataTooLarge + { + get { return m_strEncDataTooLarge; } + } + + private static string m_strErrorInClipboard = + @"An extended error report has been copied to the clipboard."; + /// + /// Look up a localized string similar to + /// 'An extended error report has been copied to the clipboard.'. + /// + public static string ErrorInClipboard + { + get { return m_strErrorInClipboard; } + } + + private static string m_strExpect100Continue = + @"Expect 100-Continue responses"; + /// + /// Look up a localized string similar to + /// 'Expect 100-Continue responses'. + /// + public static string Expect100Continue + { + get { return m_strExpect100Continue; } + } + + private static string m_strFatalError = + @"Fatal Error"; + /// + /// Look up a localized string similar to + /// 'Fatal Error'. + /// + public static string FatalError + { + get { return m_strFatalError; } + } + + private static string m_strFatalErrorText = + @"A fatal error has occurred!"; + /// + /// Look up a localized string similar to + /// 'A fatal error has occurred!'. + /// + public static string FatalErrorText + { + get { return m_strFatalErrorText; } + } + + private static string m_strFileCorrupted = + @"The file is corrupted."; + /// + /// Look up a localized string similar to + /// 'The file is corrupted.'. + /// + public static string FileCorrupted + { + get { return m_strFileCorrupted; } + } + + private static string m_strFileHeaderCorrupted = + @"The file header is corrupted."; + /// + /// Look up a localized string similar to + /// 'The file header is corrupted.'. + /// + public static string FileHeaderCorrupted + { + get { return m_strFileHeaderCorrupted; } + } + + private static string m_strFileIncomplete = + @"Data is missing at the end of the file, i.e. the file is incomplete."; + /// + /// Look up a localized string similar to + /// 'Data is missing at the end of the file, i.e. the file is incomplete.'. + /// + public static string FileIncomplete + { + get { return m_strFileIncomplete; } + } + + private static string m_strFileIncompleteExpc = + @"Less data than expected could be read from the file."; + /// + /// Look up a localized string similar to + /// 'Less data than expected could be read from the file.'. + /// + public static string FileIncompleteExpc + { + get { return m_strFileIncompleteExpc; } + } + + private static string m_strFileLoadFailed = + @"Failed to load the specified file!"; + /// + /// Look up a localized string similar to + /// 'Failed to load the specified file!'. + /// + public static string FileLoadFailed + { + get { return m_strFileLoadFailed; } + } + + private static string m_strFileLockedWrite = + @"The file is locked, because the following user is currently writing to it:"; + /// + /// Look up a localized string similar to + /// 'The file is locked, because the following user is currently writing to it:'. + /// + public static string FileLockedWrite + { + get { return m_strFileLockedWrite; } + } + + private static string m_strFileNewVerOrPlgReq = + @"A newer KeePass version or a plugin is required to open this file."; + /// + /// Look up a localized string similar to + /// 'A newer KeePass version or a plugin is required to open this file.'. + /// + public static string FileNewVerOrPlgReq + { + get { return m_strFileNewVerOrPlgReq; } + } + + private static string m_strFileNewVerReq = + @"A newer KeePass version is required to open this file."; + /// + /// Look up a localized string similar to + /// 'A newer KeePass version is required to open this file.'. + /// + public static string FileNewVerReq + { + get { return m_strFileNewVerReq; } + } + + private static string m_strFileSaveCorruptionWarning = + @"The target file might be corrupted. Please try saving again. If that fails, save the database to a different location."; + /// + /// Look up a localized string similar to + /// 'The target file might be corrupted. Please try saving again. If that fails, save the database to a different location.'. + /// + public static string FileSaveCorruptionWarning + { + get { return m_strFileSaveCorruptionWarning; } + } + + private static string m_strFileSaveFailed2 = + @"Failed to save to the specified file!"; + /// + /// Look up a localized string similar to + /// 'Failed to save to the specified file!'. + /// + public static string FileSaveFailed2 + { + get { return m_strFileSaveFailed2; } + } + + private static string m_strFileSigInvalid = + @"The file signature is invalid. Either the file isn't a KeePass database file at all or it is corrupted."; + /// + /// Look up a localized string similar to + /// 'The file signature is invalid. Either the file isn't a KeePass database file at all or it is corrupted.'. + /// + public static string FileSigInvalid + { + get { return m_strFileSigInvalid; } + } + + private static string m_strFileUnknownCipher = + @"The file is encrypted using an unknown encryption algorithm!"; + /// + /// Look up a localized string similar to + /// 'The file is encrypted using an unknown encryption algorithm!'. + /// + public static string FileUnknownCipher + { + get { return m_strFileUnknownCipher; } + } + + private static string m_strFileUnknownCompression = + @"The file is compressed using an unknown compression algorithm!"; + /// + /// Look up a localized string similar to + /// 'The file is compressed using an unknown compression algorithm!'. + /// + public static string FileUnknownCompression + { + get { return m_strFileUnknownCompression; } + } + + private static string m_strFileVersionUnsupported = + @"The file version is unsupported."; + /// + /// Look up a localized string similar to + /// 'The file version is unsupported.'. + /// + public static string FileVersionUnsupported + { + get { return m_strFileVersionUnsupported; } + } + + private static string m_strFinalKeyCreationFailed = + @"Failed to create the final encryption/decryption key!"; + /// + /// Look up a localized string similar to + /// 'Failed to create the final encryption/decryption key!'. + /// + public static string FinalKeyCreationFailed + { + get { return m_strFinalKeyCreationFailed; } + } + + private static string m_strFrameworkNotImplExcp = + @"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.'. + /// + public static string FrameworkNotImplExcp + { + get { return m_strFrameworkNotImplExcp; } + } + + private static string m_strGeneral = + @"General"; + /// + /// Look up a localized string similar to + /// 'General'. + /// + public static string General + { + get { return m_strGeneral; } + } + + private static string m_strInvalidCompositeKey = + @"The master key is invalid!"; + /// + /// Look up a localized string similar to + /// 'The master key is invalid!'. + /// + public static string InvalidCompositeKey + { + get { return m_strInvalidCompositeKey; } + } + + private static string m_strInvalidCompositeKeyHint = + @"Make sure that the master key is correct and try it again."; + /// + /// Look up a localized string similar to + /// 'Make sure that the master key is correct and try it again.'. + /// + public static string InvalidCompositeKeyHint + { + get { return m_strInvalidCompositeKeyHint; } + } + + private static string m_strInvalidDataWhileDecoding = + @"Found invalid data while decoding."; + /// + /// Look up a localized string similar to + /// 'Found invalid data while decoding.'. + /// + public static string InvalidDataWhileDecoding + { + get { return m_strInvalidDataWhileDecoding; } + } + + private static string m_strKeePass1xHint = + @"In order to import KeePass 1.x KDB files, create a new 2.x database file and click 'File' -> 'Import' in the main menu. In the import dialog, choose 'KeePass KDB (1.x)' as file format."; + /// + /// Look up a localized string similar to + /// 'In order to import KeePass 1.x KDB files, create a new 2.x database file and click 'File' -> 'Import' in the main menu. In the import dialog, choose 'KeePass KDB (1.x)' as file format.'. + /// + public static string KeePass1xHint + { + get { return m_strKeePass1xHint; } + } + + private static string m_strKeyBits = + @"{PARAM}-bit key"; + /// + /// Look up a localized string similar to + /// '{PARAM}-bit key'. + /// + public static string KeyBits + { + get { return m_strKeyBits; } + } + + private static string m_strKeyFileDbSel = + @"Database files cannot be used as key files."; + /// + /// Look up a localized string similar to + /// 'Database files cannot be used as key files.'. + /// + public static string KeyFileDbSel + { + get { return m_strKeyFileDbSel; } + } + + private static string m_strKeyHashMismatch = + @"The key and the hash do not match, i.e. the key or the hash is invalid."; + /// + /// Look up a localized string similar to + /// 'The key and the hash do not match, i.e. the key or the hash is invalid.'. + /// + public static string KeyHashMismatch + { + get { return m_strKeyHashMismatch; } + } + + private static string m_strMasterSeedLengthInvalid = + @"The length of the master key seed is invalid!"; + /// + /// Look up a localized string similar to + /// 'The length of the master key seed is invalid!'. + /// + public static string MasterSeedLengthInvalid + { + get { return m_strMasterSeedLengthInvalid; } + } + + private static string m_strOldFormat = + @"The selected file appears to be an old format"; + /// + /// Look up a localized string similar to + /// 'The selected file appears to be an old format'. + /// + public static string OldFormat + { + get { return m_strOldFormat; } + } + + private static string m_strPassive = + @"Passive"; + /// + /// Look up a localized string similar to + /// 'Passive'. + /// + public static string Passive + { + get { return m_strPassive; } + } + + private static string m_strPathBackslash = + @"The path contains a backslash. Such paths are not supported (for security reasons)."; + /// + /// Look up a localized string similar to + /// 'The path contains a backslash. Such paths are not supported (for security reasons).'. + /// + public static string PathBackslash + { + get { return m_strPathBackslash; } + } + + private static string m_strPatternInvalid = + @"The pattern is invalid."; + /// + /// Look up a localized string similar to + /// 'The pattern is invalid.'. + /// + public static string PatternInvalid + { + get { return m_strPatternInvalid; } + } + + private static string m_strPreAuth = + @"Pre-authenticate"; + /// + /// Look up a localized string similar to + /// 'Pre-authenticate'. + /// + public static string PreAuth + { + get { return m_strPreAuth; } + } + + private static string m_strPwGenFailed = + @"Failed to generate a password."; + /// + /// Look up a localized string similar to + /// 'Failed to generate a password.'. + /// + public static string PwGenFailed + { + get { return m_strPwGenFailed; } + } + + 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"; + /// + /// Look up a localized string similar to + /// 'Timeout'. + /// + public static string Timeout + { + get { return m_strTimeout; } + } + + private static string m_strTryAgainSecs = + @"Please try it again in a few seconds."; + /// + /// Look up a localized string similar to + /// 'Please try it again in a few seconds.'. + /// + public static string TryAgainSecs + { + get { return m_strTryAgainSecs; } + } + + private static string m_strUnknownError = + @"An unknown error occurred."; + /// + /// Look up a localized string similar to + /// 'An unknown error occurred.'. + /// + public static string UnknownError + { + get { return m_strUnknownError; } + } + + private static string m_strUnknownHeaderId = + @"Unknown header ID!"; + /// + /// Look up a localized string similar to + /// 'Unknown header ID!'. + /// + public static string UnknownHeaderId + { + get { return m_strUnknownHeaderId; } + } + + private static string m_strUnknownKdf = + @"Unknown key derivation function!"; + /// + /// Look up a localized string similar to + /// 'Unknown key derivation function!'. + /// + public static string UnknownKdf + { + get { return m_strUnknownKdf; } + } + + private static string m_strUserAccountKeyError = + @"The operating system did not grant KeePass read/write access to the user profile folder, where the protected user key is stored."; + /// + /// Look up a localized string similar to + /// 'The operating system did not grant KeePass read/write access to the user profile folder, where the protected user key is stored.'. + /// + public static string UserAccountKeyError + { + get { return m_strUserAccountKeyError; } + } + + private static string m_strUserAgent = + @"User agent"; + /// + /// Look up a localized string similar to + /// 'User agent'. + /// + public static string UserAgent + { + get { return m_strUserAgent; } + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Resources/KSRes.Generated.cs b/src/KeePassLib2AndroidSdkStyle/Resources/KSRes.Generated.cs new file mode 100644 index 00000000..b1ab9708 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Resources/KSRes.Generated.cs @@ -0,0 +1,52 @@ +// This is a generated file! +// Do not edit manually, changes will be overwritten. + +using System; +using System.Collections.Generic; + +namespace KeePassLib.Resources +{ + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + public static class KSRes + { + private static string TryGetEx(Dictionary dictNew, + string strName, string strDefault) + { + string strTemp; + + if(dictNew.TryGetValue(strName, out strTemp)) + return strTemp; + + return strDefault; + } + + public static void SetTranslatedStrings(Dictionary dictNew) + { + if(dictNew == null) throw new ArgumentNullException("dictNew"); + + m_strTest = TryGetEx(dictNew, "Test", m_strTest); + } + + private static readonly string[] m_vKeyNames = { + "Test" + }; + + public static string[] GetKeyNames() + { + return m_vKeyNames; + } + + private static string m_strTest = + @"Test"; + /// + /// Look up a localized string similar to + /// 'Test'. + /// + public static string Test + { + get { return m_strTest; } + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Resources/values/Strings.xml b/src/KeePassLib2AndroidSdkStyle/Resources/values/Strings.xml new file mode 100644 index 00000000..17aa6e6a --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Resources/values/Strings.xml @@ -0,0 +1,4 @@ + + + KeePassLib2Android + diff --git a/src/KeePassLib2AndroidSdkStyle/Security/ProtectedBinary.cs b/src/KeePassLib2AndroidSdkStyle/Security/ProtectedBinary.cs new file mode 100644 index 00000000..7dfe46c3 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Security/ProtectedBinary.cs @@ -0,0 +1,403 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Diagnostics; +using System.Threading; + +#if !KeePassUAP +using System.Security.Cryptography; +#endif + +using KeePassLib.Cryptography; +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Native; +using KeePassLib.Utility; + +#if KeePassLibSD +using KeePassLibSD; +#endif + +namespace KeePassLib.Security +{ + [Flags] + public enum PbCryptFlags + { + None = 0, + Encrypt = 1, + Decrypt = 2 + } + + public delegate void PbCryptDelegate(byte[] pbData, PbCryptFlags cf, + long lID); + + /// + /// Represents a protected binary, i.e. a byte array that is encrypted + /// in memory. A ProtectedBinary object is immutable and + /// thread-safe. + /// + public sealed class ProtectedBinary : IEquatable + { + private const int BlockSize = 16; + + private static PbCryptDelegate g_fExtCrypt = null; + /// + /// A plugin can provide a custom memory protection method + /// by assigning a non-null delegate to this property. + /// + public static PbCryptDelegate ExtCrypt + { + get { return g_fExtCrypt; } + set { g_fExtCrypt = value; } + } + + // Local copy of the delegate that was used for encryption, + // in order to allow correct decryption even when the global + // delegate changes + private PbCryptDelegate m_fExtCrypt = null; + + private enum PbMemProt + { + None = 0, + ProtectedMemory, + ChaCha20, + ExtCrypt + } + + // ProtectedMemory is supported only on Windows 2000 SP3 and higher +#if !KeePassLibSD + private static bool? g_obProtectedMemorySupported = null; +#endif + private static bool ProtectedMemorySupported + { + get + { +#if KeePassLibSD + return false; +#else + bool? ob = g_obProtectedMemorySupported; + if(ob.HasValue) return ob.Value; + + // Mono does not implement any encryption for ProtectedMemory; + // https://sourceforge.net/p/keepass/feature-requests/1907/ + if(NativeLib.IsUnix()) + { + g_obProtectedMemorySupported = false; + return false; + } + + ob = false; + try // Test whether ProtectedMemory is supported + { + // BlockSize * 3 in order to test encryption for multiple + // blocks, but not introduce a power of 2 as factor + byte[] pb = new byte[ProtectedBinary.BlockSize * 3]; + for(int i = 0; i < pb.Length; ++i) pb[i] = (byte)i; + + throw new NotSupportedException(); + for(int i = 0; i < pb.Length; ++i) + { + if(pb[i] != (byte)i) { ob = true; break; } + } + } + catch(Exception) { } // Windows 98 / ME + + g_obProtectedMemorySupported = ob; + return ob.Value; +#endif + } + } + + private static long g_lCurID = 0; + private long m_lID; + + private byte[] m_pbData; // Never null + + // The real length of the data; this value can be different from + // m_pbData.Length, as the length of m_pbData always is a multiple + // of BlockSize (required for ProtectedMemory) + private uint m_uDataLen; + + private bool m_bProtected; // Protection requested by the caller + + private PbMemProt m_mp = PbMemProt.None; // Actual protection + + private object m_objSync = new object(); + + private static byte[] g_pbKey32 = null; + + /// + /// A flag specifying whether the ProtectedBinary object has + /// turned on memory protection or not. + /// + public bool IsProtected + { + get { return m_bProtected; } + } + + /// + /// Length of the stored data. + /// + public uint Length + { + get { return m_uDataLen; } + } + + /// + /// Construct a new, empty protected binary data object. + /// Protection is disabled. + /// + public ProtectedBinary() + { + Init(false, MemUtil.EmptyByteArray, 0, 0); + } + + /// + /// Construct a new protected binary data object. + /// + /// If this paremeter is true, + /// the data will be encrypted in memory. If it is false, the + /// data is stored in plain-text in the process memory. + /// Value of the protected object. + /// The input parameter is not modified and + /// ProtectedBinary doesn't take ownership of the data, + /// i.e. the caller is responsible for clearing it. + public ProtectedBinary(bool bEnableProtection, byte[] pbData) + { + if(pbData == null) throw new ArgumentNullException("pbData"); + + Init(bEnableProtection, pbData, 0, pbData.Length); + } + + /// + /// Construct a new protected binary data object. + /// + /// If this paremeter is true, + /// the data will be encrypted in memory. If it is false, the + /// data is stored in plain-text in the process memory. + /// Value of the protected object. + /// The input parameter is not modified and + /// ProtectedBinary doesn't take ownership of the data, + /// i.e. the caller is responsible for clearing it. + /// Offset for . + /// Size for . + public ProtectedBinary(bool bEnableProtection, byte[] pbData, + int iOffset, int cbSize) + { + Init(bEnableProtection, pbData, iOffset, cbSize); + } + + /// + /// Construct a new protected binary data object. Copy the data from + /// a XorredBuffer object. + /// + /// Enable protection or not. + /// XorredBuffer object used to + /// initialize the ProtectedBinary object. + public ProtectedBinary(bool bEnableProtection, XorredBuffer xbProtected) + { + Debug.Assert(xbProtected != null); + if(xbProtected == null) throw new ArgumentNullException("xbProtected"); + + byte[] pb = xbProtected.ReadPlainText(); + Init(bEnableProtection, pb, 0, pb.Length); + + if(bEnableProtection) MemUtil.ZeroByteArray(pb); + } + + private void Init(bool bEnableProtection, byte[] pbData, int iOffset, + int cbSize) + { + if(pbData == null) throw new ArgumentNullException("pbData"); + if(iOffset < 0) throw new ArgumentOutOfRangeException("iOffset"); + if(cbSize < 0) throw new ArgumentOutOfRangeException("cbSize"); + if(iOffset > (pbData.Length - cbSize)) + throw new ArgumentOutOfRangeException("cbSize"); + +#if KeePassLibSD + m_lID = ++g_lCurID; +#else + m_lID = Interlocked.Increment(ref g_lCurID); +#endif + + m_bProtected = bEnableProtection; + m_uDataLen = (uint)cbSize; + + const int bs = ProtectedBinary.BlockSize; + int nBlocks = cbSize / bs; + if((nBlocks * bs) < cbSize) ++nBlocks; + Debug.Assert((nBlocks * bs) >= cbSize); + + m_pbData = new byte[nBlocks * bs]; + Array.Copy(pbData, iOffset, m_pbData, 0, cbSize); + + Encrypt(); + } + + private void Encrypt() + { + Debug.Assert(m_mp == PbMemProt.None); + + // Nothing to do if caller didn't request protection + if(!m_bProtected) return; + + // ProtectedMemory.Protect throws for data size == 0 + if(m_pbData.Length == 0) return; + + PbCryptDelegate f = g_fExtCrypt; + if(f != null) + { + f(m_pbData, PbCryptFlags.Encrypt, m_lID); + + m_fExtCrypt = f; + m_mp = PbMemProt.ExtCrypt; + return; + } + + + + byte[] pbKey32 = g_pbKey32; + if(pbKey32 == null) + { + pbKey32 = CryptoRandom.Instance.GetRandomBytes(32); + + byte[] pbUpd = Interlocked.Exchange(ref g_pbKey32, pbKey32); + if(pbUpd != null) pbKey32 = pbUpd; + } + + byte[] pbIV = new byte[12]; + MemUtil.UInt64ToBytesEx((ulong)m_lID, pbIV, 4); + using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey32, pbIV, true)) + { + c.Encrypt(m_pbData, 0, m_pbData.Length); + } + m_mp = PbMemProt.ChaCha20; + } + + private void Decrypt() + { + if(m_pbData.Length == 0) return; + + else if(m_mp == PbMemProt.ChaCha20) + { + byte[] pbIV = new byte[12]; + MemUtil.UInt64ToBytesEx((ulong)m_lID, pbIV, 4); + using(ChaCha20Cipher c = new ChaCha20Cipher(g_pbKey32, pbIV, true)) + { + c.Decrypt(m_pbData, 0, m_pbData.Length); + } + } + else if(m_mp == PbMemProt.ExtCrypt) + m_fExtCrypt(m_pbData, PbCryptFlags.Decrypt, m_lID); + else { Debug.Assert(m_mp == PbMemProt.None); } + + m_mp = PbMemProt.None; + } + + /// + /// Get a copy of the protected data as a byte array. + /// Please note that the returned byte array is not protected and + /// can therefore been read by any other application. + /// Make sure that your clear it properly after usage. + /// + /// Unprotected byte array. This is always a copy of the internal + /// protected data and can therefore be cleared safely. + public byte[] ReadData() + { + if(m_uDataLen == 0) return MemUtil.EmptyByteArray; + + byte[] pbReturn = new byte[m_uDataLen]; + + lock(m_objSync) + { + Decrypt(); + Array.Copy(m_pbData, pbReturn, (int)m_uDataLen); + Encrypt(); + } + + return pbReturn; + } + + /// + /// Read the protected data and return it protected with a sequence + /// of bytes generated by a random stream. + /// + /// Random number source. + public byte[] ReadXorredData(CryptoRandomStream crsRandomSource) + { + Debug.Assert(crsRandomSource != null); + if(crsRandomSource == null) throw new ArgumentNullException("crsRandomSource"); + + byte[] pbData = ReadData(); + uint uLen = (uint)pbData.Length; + + byte[] randomPad = crsRandomSource.GetRandomBytes(uLen); + Debug.Assert(randomPad.Length == pbData.Length); + + for(uint i = 0; i < uLen; ++i) + pbData[i] ^= randomPad[i]; + + return pbData; + } + + private int? m_hash = null; + public override int GetHashCode() + { + if(m_hash.HasValue) return m_hash.Value; + + int h = (m_bProtected ? 0x7B11D289 : 0); + + byte[] pb = ReadData(); + unchecked + { + for(int i = 0; i < pb.Length; ++i) + h = (h << 3) + h + (int)pb[i]; + } + MemUtil.ZeroByteArray(pb); + + m_hash = h; + return h; + } + + public override bool Equals(object obj) + { + return Equals(obj as ProtectedBinary); + } + + public bool Equals(ProtectedBinary other) + { + if(other == null) return false; // No assert + + if(m_bProtected != other.m_bProtected) return false; + if(m_uDataLen != other.m_uDataLen) return false; + + byte[] pbL = ReadData(); + byte[] pbR = other.ReadData(); + bool bEq = MemUtil.ArraysEqual(pbL, pbR); + MemUtil.ZeroByteArray(pbL); + MemUtil.ZeroByteArray(pbR); + +#if DEBUG + if(bEq) { Debug.Assert(GetHashCode() == other.GetHashCode()); } +#endif + + return bEq; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Security/ProtectedString.cs b/src/KeePassLib2AndroidSdkStyle/Security/ProtectedString.cs new file mode 100644 index 00000000..ffe26dd3 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Security/ProtectedString.cs @@ -0,0 +1,434 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Cryptography; +using KeePassLib.Utility; + +#if KeePassLibSD +using KeePassLibSD; +#endif + +// SecureString objects are limited to 65536 characters, don't use + +namespace KeePassLib.Security +{ + /// + /// A string that is protected in process memory. + /// ProtectedString objects are immutable and thread-safe. + /// +#if (DEBUG && !KeePassLibSD) + [DebuggerDisplay("{ReadString()}")] +#endif + public sealed class ProtectedString + { + // Exactly one of the following will be non-null + private ProtectedBinary m_pbUtf8 = null; + private string m_strPlainText = null; + + private bool m_bIsProtected; + + private static readonly ProtectedString m_psEmpty = new ProtectedString(); + /// + /// Get an empty ProtectedString object, without protection. + /// + public static ProtectedString Empty + { + get { return m_psEmpty; } + } + + private static readonly ProtectedString m_psEmptyEx = new ProtectedString( + true, new byte[0]); + /// + /// Get an empty ProtectedString object, with protection turned on. + /// + public static ProtectedString EmptyEx + { + get { return m_psEmptyEx; } + } + + /// + /// A flag specifying whether the ProtectedString object + /// has turned on memory protection or not. + /// + public bool IsProtected + { + get { return m_bIsProtected; } + } + + public bool IsEmpty + { + get + { + ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety + if (p != null) return (p.Length == 0); + + Debug.Assert(m_strPlainText != null); + return (m_strPlainText.Length == 0); + } + } + + private int m_nCachedLength = -1; + /// + /// Length of the protected string, in characters. + /// + public int Length + { + get + { + if (m_nCachedLength >= 0) return m_nCachedLength; + + ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety + if (p != null) + { + byte[] pbPlain = p.ReadData(); + try { m_nCachedLength = StrUtil.Utf8.GetCharCount(pbPlain); } + finally { MemUtil.ZeroByteArray(pbPlain); } + } + else + { + Debug.Assert(m_strPlainText != null); + m_nCachedLength = m_strPlainText.Length; + } + + return m_nCachedLength; + } + } + + /// + /// Construct a new protected string object. Protection is + /// disabled. + /// + public ProtectedString() + { + Init(false, string.Empty); + } + + /// + /// Construct a new protected string. The string is initialized + /// to the value supplied in the parameters. + /// + /// If this parameter is true, + /// the string will be protected in memory (encrypted). If it + /// is false, the string will be stored as plain-text. + /// The initial string value. + public ProtectedString(bool bEnableProtection, string strValue) + { + Init(bEnableProtection, strValue); + } + + /// + /// Construct a new protected string. The string is initialized + /// to the value supplied in the parameters (UTF-8 encoded string). + /// + /// If this parameter is true, + /// the string will be protected in memory (encrypted). If it + /// is false, the string will be stored as plain-text. + /// The initial string value, encoded as + /// UTF-8 byte array. This parameter won't be modified; the caller + /// is responsible for clearing it. + public ProtectedString(bool bEnableProtection, byte[] vUtf8Value) + { + Init(bEnableProtection, vUtf8Value); + } + + /// + /// Construct a new protected string. The string is initialized + /// to the value passed in the XorredBuffer object. + /// + /// Enable protection or not. + /// XorredBuffer object containing the + /// string in UTF-8 representation. The UTF-8 string must not + /// be null-terminated. + public ProtectedString(bool bEnableProtection, XorredBuffer xb) + { + if (xb == null) { Debug.Assert(false); throw new ArgumentNullException("xb"); } + + byte[] pb = xb.ReadPlainText(); + try { Init(bEnableProtection, pb); } + finally { if (bEnableProtection) MemUtil.ZeroByteArray(pb); } + } + + private void Init(bool bEnableProtection, string str) + { + if (str == null) throw new ArgumentNullException("str"); + + m_bIsProtected = bEnableProtection; + + // As the string already is in memory and immutable, + // protection would be useless + m_strPlainText = str; + } + + private void Init(bool bEnableProtection, byte[] pbUtf8) + { + if (pbUtf8 == null) throw new ArgumentNullException("pbUtf8"); + + m_bIsProtected = bEnableProtection; + + if (bEnableProtection) + m_pbUtf8 = new ProtectedBinary(true, pbUtf8); + else + m_strPlainText = StrUtil.Utf8.GetString(pbUtf8, 0, pbUtf8.Length); + } + + /// + /// Convert the protected string to a standard string object. + /// Be careful with this function, as the returned string object + /// isn't protected anymore and stored in plain-text in the + /// process memory. + /// + /// Plain-text string. Is never null. + public string ReadString() + { + if (m_strPlainText != null) return m_strPlainText; + + byte[] pb = ReadUtf8(); + string str = ((pb.Length == 0) ? string.Empty : + StrUtil.Utf8.GetString(pb, 0, pb.Length)); + // No need to clear pb + + // As the text is now visible in process memory anyway, + // there's no need to protect it anymore (strings are + // immutable and thus cannot be overwritten) + m_strPlainText = str; + m_pbUtf8 = null; // Thread-safe order + + return str; + } + + /// + /// Read out the string and return it as a char array. + /// The returned array is not protected and should be cleared by + /// the caller. + /// + /// Plain-text char array. + public char[] ReadChars() + { + if (m_strPlainText != null) return m_strPlainText.ToCharArray(); + + byte[] pb = ReadUtf8(); + char[] v; + try { v = StrUtil.Utf8.GetChars(pb); } + finally { MemUtil.ZeroByteArray(pb); } + return v; + } + + /// + /// Read out the string and return a byte array that contains the + /// string encoded using UTF-8. + /// The returned array is not protected and should be cleared by + /// the caller. + /// + /// Plain-text UTF-8 byte array. + public byte[] ReadUtf8() + { + ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety + if (p != null) return p.ReadData(); + + return StrUtil.Utf8.GetBytes(m_strPlainText); + } + + /// + /// Get the string as an UTF-8 sequence xorred with bytes + /// from a CryptoRandomStream. + /// + public byte[] ReadXorredString(CryptoRandomStream crsRandomSource) + { + if (crsRandomSource == null) { Debug.Assert(false); throw new ArgumentNullException("crsRandomSource"); } + + byte[] pbData = ReadUtf8(); + int cb = pbData.Length; + + byte[] pbPad = crsRandomSource.GetRandomBytes((uint)cb); + Debug.Assert(pbPad.Length == cb); + + for (int i = 0; i < cb; ++i) + pbData[i] ^= pbPad[i]; + + MemUtil.ZeroByteArray(pbPad); + return pbData; + } + + public ProtectedString WithProtection(bool bProtect) + { + if (bProtect == m_bIsProtected) return this; + + byte[] pb = ReadUtf8(); + + // No need to clear pb; either the current or the new object is unprotected + return new ProtectedString(bProtect, pb); + } + + public bool Equals(ProtectedString ps, bool bCheckProtEqual) + { + if (ps == null) throw new ArgumentNullException("ps"); + if (object.ReferenceEquals(this, ps)) return true; // Perf. opt. + + bool bPA = m_bIsProtected, bPB = ps.m_bIsProtected; + if (bCheckProtEqual && (bPA != bPB)) return false; + if (!bPA && !bPB) return (ReadString() == ps.ReadString()); + + byte[] pbA = ReadUtf8(), pbB = null; + bool bEq; + try + { + pbB = ps.ReadUtf8(); + bEq = MemUtil.ArraysEqual(pbA, pbB); + } + finally + { + if (bPA) MemUtil.ZeroByteArray(pbA); + if (bPB && (pbB != null)) MemUtil.ZeroByteArray(pbB); + } + + return bEq; + } + + public ProtectedString Insert(int iStart, string strInsert) + { + if (iStart < 0) throw new ArgumentOutOfRangeException("iStart"); + if (strInsert == null) throw new ArgumentNullException("strInsert"); + if (strInsert.Length == 0) return this; + + if (!m_bIsProtected) + return new ProtectedString(false, ReadString().Insert( + iStart, strInsert)); + + UTF8Encoding utf8 = StrUtil.Utf8; + char[] v = ReadChars(), vNew = null; + byte[] pbNew = null; + ProtectedString ps; + + try + { + if (iStart > v.Length) + throw new ArgumentOutOfRangeException("iStart"); + + char[] vIns = strInsert.ToCharArray(); + + vNew = new char[v.Length + vIns.Length]; + Array.Copy(v, 0, vNew, 0, iStart); + Array.Copy(vIns, 0, vNew, iStart, vIns.Length); + Array.Copy(v, iStart, vNew, iStart + vIns.Length, + v.Length - iStart); + + pbNew = utf8.GetBytes(vNew); + ps = new ProtectedString(true, pbNew); + + Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) == + ReadString().Insert(iStart, strInsert)); + } + finally + { + MemUtil.ZeroArray(v); + if (vNew != null) MemUtil.ZeroArray(vNew); + if (pbNew != null) MemUtil.ZeroByteArray(pbNew); + } + + return ps; + } + + public ProtectedString Remove(int iStart, int nCount) + { + if (iStart < 0) throw new ArgumentOutOfRangeException("iStart"); + if (nCount < 0) throw new ArgumentOutOfRangeException("nCount"); + if (nCount == 0) return this; + + if (!m_bIsProtected) + return new ProtectedString(false, ReadString().Remove( + iStart, nCount)); + + UTF8Encoding utf8 = StrUtil.Utf8; + char[] v = ReadChars(), vNew = null; + byte[] pbNew = null; + ProtectedString ps; + + try + { + if ((iStart + nCount) > v.Length) + throw new ArgumentException("(iStart + nCount) > v.Length"); + + vNew = new char[v.Length - nCount]; + Array.Copy(v, 0, vNew, 0, iStart); + Array.Copy(v, iStart + nCount, vNew, iStart, v.Length - + (iStart + nCount)); + + pbNew = utf8.GetBytes(vNew); + ps = new ProtectedString(true, pbNew); + + Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) == + ReadString().Remove(iStart, nCount)); + } + finally + { + MemUtil.ZeroArray(v); + if (vNew != null) MemUtil.ZeroArray(vNew); + if (pbNew != null) MemUtil.ZeroByteArray(pbNew); + } + + return ps; + } + + public static ProtectedString operator +(ProtectedString a, ProtectedString b) + { + if (a == null) throw new ArgumentNullException("a"); + if (b == null) throw new ArgumentNullException("b"); + + if (b.IsEmpty) return a.WithProtection(a.IsProtected || b.IsProtected); + if (a.IsEmpty) return b.WithProtection(a.IsProtected || b.IsProtected); + if (!a.IsProtected && !b.IsProtected) + return new ProtectedString(false, a.ReadString() + b.ReadString()); + + char[] vA = a.ReadChars(), vB = null, vNew = null; + byte[] pbNew = null; + ProtectedString ps; + + try + { + vB = b.ReadChars(); + + vNew = new char[vA.Length + vB.Length]; + Array.Copy(vA, vNew, vA.Length); + Array.Copy(vB, 0, vNew, vA.Length, vB.Length); + + pbNew = StrUtil.Utf8.GetBytes(vNew); + ps = new ProtectedString(true, pbNew); + } + finally + { + MemUtil.ZeroArray(vA); + if (vB != null) MemUtil.ZeroArray(vB); + if (vNew != null) MemUtil.ZeroArray(vNew); + if (pbNew != null) MemUtil.ZeroByteArray(pbNew); + } + + return ps; + } + + public static ProtectedString operator +(ProtectedString a, string b) + { + ProtectedString psB = new ProtectedString(false, b); + return (a + psB); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Security/XorredBuffer.cs b/src/KeePassLib2AndroidSdkStyle/Security/XorredBuffer.cs new file mode 100644 index 00000000..025b8944 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Security/XorredBuffer.cs @@ -0,0 +1,109 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Diagnostics; + +using KeePassLib.Utility; + +namespace KeePassLib.Security +{ + /// + /// A XorredBuffer object stores data that is encrypted + /// using a XOR pad. + /// + public sealed class XorredBuffer : IDisposable + { + private byte[] m_pbCT; + private byte[] m_pbXorPad; + + public uint Length + { + get + { + if (m_pbCT == null) { Debug.Assert(false); throw new ObjectDisposedException(null); } + return (uint)m_pbCT.Length; + } + } + + /// + /// Construct a new XorredBuffer object. + /// The byte array must have the same + /// length as the byte array. + /// The XorredBuffer object takes ownership of the two byte + /// arrays, i.e. the caller must not use them afterwards. + /// + /// Data with XOR pad applied. + /// XOR pad that can be used to decrypt the + /// byte array. + public XorredBuffer(byte[] pbCT, byte[] pbXorPad) + { + if (pbCT == null) { Debug.Assert(false); throw new ArgumentNullException("pbCT"); } + if (pbXorPad == null) { Debug.Assert(false); throw new ArgumentNullException("pbXorPad"); } + if (pbCT.Length != pbXorPad.Length) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("pbXorPad"); + } + + m_pbCT = pbCT; + m_pbXorPad = pbXorPad; + } + +#if DEBUG + ~XorredBuffer() + { + Debug.Assert((m_pbCT == null) && (m_pbXorPad == null)); + } +#endif + + public void Dispose() + { + if (m_pbCT == null) return; + + MemUtil.ZeroByteArray(m_pbCT); + m_pbCT = null; + + MemUtil.ZeroByteArray(m_pbXorPad); + m_pbXorPad = null; + } + + /// + /// Get a copy of the plain-text. The caller is responsible + /// for clearing the byte array safely after using it. + /// + /// Plain-text byte array. + public byte[] ReadPlainText() + { + byte[] pbCT = m_pbCT, pbX = m_pbXorPad; + if ((pbCT == null) || (pbX == null) || (pbCT.Length != pbX.Length)) + { + Debug.Assert(false); + throw new ObjectDisposedException(null); + } + + byte[] pbPT = new byte[pbCT.Length]; + + for (int i = 0; i < pbPT.Length; ++i) + pbPT[i] = (byte)(pbCT[i] ^ pbX[i]); + + return pbPT; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Serialization/BinaryReaderEx.cs b/src/KeePassLib2AndroidSdkStyle/Serialization/BinaryReaderEx.cs new file mode 100644 index 00000000..3ff99873 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Serialization/BinaryReaderEx.cs @@ -0,0 +1,92 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public sealed class BinaryReaderEx + { + private Stream m_s; + // private Encoding m_enc; // See constructor + + private string m_strReadExcp; // May be null + public string ReadExceptionText + { + get { return m_strReadExcp; } + set { m_strReadExcp = value; } + } + + private Stream m_sCopyTo = null; + /// + /// If this property is set to a non-null stream, all data that + /// is read from the input stream is automatically written to + /// the copy stream (before returning the read data). + /// + public Stream CopyDataTo + { + get { return m_sCopyTo; } + set { m_sCopyTo = value; } + } + + public BinaryReaderEx(Stream input, Encoding encoding, + string strReadExceptionText) + { + if(input == null) throw new ArgumentNullException("input"); + + m_s = input; + // m_enc = encoding; // Not used yet + m_strReadExcp = strReadExceptionText; + } + + public byte[] ReadBytes(int nCount) + { + try + { + byte[] pb = MemUtil.Read(m_s, nCount); + if((pb == null) || (pb.Length != nCount)) + { + if(!string.IsNullOrEmpty(m_strReadExcp)) + throw new EndOfStreamException(m_strReadExcp); + else throw new EndOfStreamException(); + } + + if(m_sCopyTo != null) m_sCopyTo.Write(pb, 0, pb.Length); + return pb; + } + catch(Exception) + { + if(!string.IsNullOrEmpty(m_strReadExcp)) + throw new IOException(m_strReadExcp); + else throw; + } + } + + public byte ReadByte() + { + byte[] pb = ReadBytes(1); + return pb[0]; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Serialization/FileLock.cs b/src/KeePassLib2AndroidSdkStyle/Serialization/FileLock.cs new file mode 100644 index 00000000..e62322b2 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Serialization/FileLock.cs @@ -0,0 +1,261 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; + +using KeePassLib.Cryptography; +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public sealed class FileLockException : Exception + { + private readonly string m_strMsg; + + public override string Message + { + get { return m_strMsg; } + } + + public FileLockException(string strBaseFile, string strUser) + { + StringBuilder sb = new StringBuilder(); + + if(!string.IsNullOrEmpty(strBaseFile)) + { + sb.Append(strBaseFile); + sb.Append(MessageService.NewParagraph); + } + + sb.Append(KLRes.FileLockedWrite); + sb.Append(MessageService.NewLine); + + if(!string.IsNullOrEmpty(strUser)) sb.Append(strUser); + else sb.Append("?"); + + sb.Append(MessageService.NewParagraph); + sb.Append(KLRes.TryAgainSecs); + + m_strMsg = sb.ToString(); + } + } + + public sealed class FileLock : IDisposable + { + private const string LockFileExt = ".lock"; + private const string LockFileHeader = "KeePass Lock File"; + + private IOConnectionInfo m_iocLockFile; + + private sealed class LockFileInfo + { + public readonly string ID; + public readonly DateTime Time; + public readonly string UserName; + public readonly string Machine; + public readonly string Domain; + + private LockFileInfo(string strID, string strTime, string strUserName, + string strMachine, string strDomain) + { + this.ID = (strID ?? string.Empty).Trim(); + + DateTime dt; + if(TimeUtil.TryDeserializeUtc(strTime.Trim(), out dt)) + this.Time = dt; + else + { + Debug.Assert(false); + this.Time = DateTime.UtcNow; + } + + this.UserName = (strUserName ?? string.Empty).Trim(); + this.Machine = (strMachine ?? string.Empty).Trim(); + this.Domain = (strDomain ?? string.Empty).Trim(); + + if(this.Domain.Equals(this.Machine, StrUtil.CaseIgnoreCmp)) + this.Domain = string.Empty; + } + + public string GetOwner() + { + StringBuilder sb = new StringBuilder(); + sb.Append((this.UserName.Length > 0) ? this.UserName : "?"); + + bool bMachine = (this.Machine.Length > 0); + bool bDomain = (this.Domain.Length > 0); + if(bMachine || bDomain) + { + sb.Append(" ("); + sb.Append(this.Machine); + if(bMachine && bDomain) sb.Append(" @ "); + sb.Append(this.Domain); + sb.Append(")"); + } + + return sb.ToString(); + } + + public static LockFileInfo Load(IOConnectionInfo iocLockFile) + { + Stream s = null; + try + { + s = IOConnection.OpenRead(iocLockFile); + if(s == null) return null; + StreamReader sr = new StreamReader(s, StrUtil.Utf8); + string str = sr.ReadToEnd(); + sr.Close(); + if(str == null) { Debug.Assert(false); return null; } + + str = StrUtil.NormalizeNewLines(str, false); + string[] v = str.Split('\n'); + if((v == null) || (v.Length < 6)) { Debug.Assert(false); return null; } + + if(!v[0].StartsWith(LockFileHeader)) { Debug.Assert(false); return null; } + return new LockFileInfo(v[1], v[2], v[3], v[4], v[5]); + } + catch(FileNotFoundException) { } + catch(Exception) { Debug.Assert(false); } + finally { if(s != null) s.Close(); } + + return null; + } + + // Throws on error + public static LockFileInfo Create(IOConnectionInfo iocLockFile) + { + LockFileInfo lfi; + Stream s = null; + try + { + byte[] pbID = CryptoRandom.Instance.GetRandomBytes(16); + string strTime = TimeUtil.SerializeUtc(DateTime.UtcNow); + + lfi = new LockFileInfo(Convert.ToBase64String(pbID), strTime, +#if KeePassUAP + EnvironmentExt.UserName, EnvironmentExt.MachineName, + EnvironmentExt.UserDomainName); +#elif KeePassLibSD + string.Empty, string.Empty, string.Empty); +#else + Environment.UserName, Environment.MachineName, + Environment.UserDomainName); +#endif + + StringBuilder sb = new StringBuilder(); +#if !KeePassLibSD + sb.AppendLine(LockFileHeader); + sb.AppendLine(lfi.ID); + sb.AppendLine(strTime); + sb.AppendLine(lfi.UserName); + sb.AppendLine(lfi.Machine); + sb.AppendLine(lfi.Domain); +#else + sb.Append(LockFileHeader + MessageService.NewLine); + sb.Append(lfi.ID + MessageService.NewLine); + sb.Append(strTime + MessageService.NewLine); + sb.Append(lfi.UserName + MessageService.NewLine); + sb.Append(lfi.Machine + MessageService.NewLine); + sb.Append(lfi.Domain + MessageService.NewLine); +#endif + + byte[] pbFile = StrUtil.Utf8.GetBytes(sb.ToString()); + + s = IOConnection.OpenWrite(iocLockFile); + if(s == null) throw new IOException(UrlUtil.GetFileName(iocLockFile.Path)); + s.Write(pbFile, 0, pbFile.Length); + } + finally { if(s != null) s.Close(); } + + return lfi; + } + } + + public FileLock(IOConnectionInfo iocBaseFile) + { + if(iocBaseFile == null) throw new ArgumentNullException("strBaseFile"); + + m_iocLockFile = iocBaseFile.CloneDeep(); + m_iocLockFile.Path += LockFileExt; + + LockFileInfo lfiEx = LockFileInfo.Load(m_iocLockFile); + if(lfiEx != null) + { + m_iocLockFile = null; // Otherwise Dispose deletes the existing one + throw new FileLockException(UrlUtil.GetFileName(iocBaseFile.Path), lfiEx.GetOwner()); + } + + LockFileInfo.Create(m_iocLockFile); + } + + ~FileLock() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool bDisposing) + { + if(m_iocLockFile == null) return; + + bool bFileDeleted = false; + for(int r = 0; r < 5; ++r) + { + // if(!OwnLockFile()) { bFileDeleted = true; break; } + + try + { + IOConnection.DeleteFile(m_iocLockFile); + bFileDeleted = true; + } + catch(Exception) { Debug.Assert(false); } + + if(bFileDeleted) break; + + if(bDisposing) Thread.Sleep(50); + } + + // if(bDisposing && !bFileDeleted) + // IOConnection.DeleteFile(m_iocLockFile); // Possibly with exception + + m_iocLockFile = null; + } + + // private bool OwnLockFile() + // { + // if(m_iocLockFile == null) { Debug.Assert(false); return false; } + // if(m_strLockID == null) { Debug.Assert(false); return false; } + // LockFileInfo lfi = LockFileInfo.Load(m_iocLockFile); + // if(lfi == null) return false; + // return m_strLockID.Equals(lfi.ID); + // } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Serialization/FileTransactionEx.cs b/src/KeePassLib2AndroidSdkStyle/Serialization/FileTransactionEx.cs new file mode 100644 index 00000000..f93360d8 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Serialization/FileTransactionEx.cs @@ -0,0 +1,165 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +#if (!KeePassLibSD && !KeePassUAP) +using System.Security.AccessControl; +#endif + +using KeePassLib.Native; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public sealed class FileTransactionEx + { + private bool m_bTransacted; + private IOConnectionInfo m_iocBase; + private IOConnectionInfo m_iocTemp; + + private bool m_bMadeUnhidden = false; + + private const string StrTempSuffix = ".tmp"; + + private static Dictionary g_dEnabled = + new Dictionary(StrUtil.CaseIgnoreComparer); + + public FileTransactionEx(IOConnectionInfo iocBaseFile) + { + Initialize(iocBaseFile, true); + } + + public FileTransactionEx(IOConnectionInfo iocBaseFile, bool bTransacted) + { + Initialize(iocBaseFile, bTransacted); + } + + private void Initialize(IOConnectionInfo iocBaseFile, bool bTransacted) + { + if(iocBaseFile == null) throw new ArgumentNullException("iocBaseFile"); + + m_bTransacted = bTransacted; + m_iocBase = iocBaseFile.CloneDeep(); + + // Prevent transactions for FTP URLs under .NET 4.0 in order to + // avoid/workaround .NET bug 621450: + // https://connect.microsoft.com/VisualStudio/feedback/details/621450/problem-renaming-file-on-ftp-server-using-ftpwebrequest-in-net-framework-4-0-vs2010-only + if(m_iocBase.Path.StartsWith("ftp:", StrUtil.CaseIgnoreCmp) && + (Environment.Version.Major >= 4) && !NativeLib.IsUnix()) + m_bTransacted = false; + + if(m_bTransacted) + { + m_iocTemp = m_iocBase.CloneDeep(); + m_iocTemp.Path += "."+new PwUuid(true).ToHexString().Substring(0,6)+ StrTempSuffix; + } + else m_iocTemp = m_iocBase; + } + + public Stream OpenWrite() + { + if(!m_bTransacted) m_bMadeUnhidden = UrlUtil.UnhideFile(m_iocTemp.Path); + else // m_bTransacted + { + try { IOConnection.DeleteFile(m_iocTemp); } + catch(Exception) { } + } + + return IOConnection.OpenWrite(m_iocTemp); + } + + public void CommitWrite() + { + if(m_bTransacted) CommitWriteTransaction(); + else // !m_bTransacted + { + if(m_bMadeUnhidden) UrlUtil.HideFile(m_iocTemp.Path, true); // Hide again + } + } + + private void CommitWriteTransaction() + { + bool bMadeUnhidden = UrlUtil.UnhideFile(m_iocBase.Path); + +#if (!KeePassLibSD && !KeePassUAP) + + bool bEfsEncrypted = false; +#endif + + if(IOConnection.FileExists(m_iocBase)) + { +#if (!KeePassLibSD && !KeePassRT) + if(m_iocBase.IsLocalFile()) + { + try + { + FileAttributes faBase = File.GetAttributes(m_iocBase.Path); + bEfsEncrypted = ((long)(faBase & FileAttributes.Encrypted) != 0); + + DateTime tCreation = File.GetCreationTime(m_iocBase.Path); + + + File.SetCreationTime(m_iocTemp.Path, tCreation); + } + catch(Exception) { Debug.Assert(false); } + } +#endif + + IOConnection.DeleteFile(m_iocBase); + } + + IOConnection.RenameFile(m_iocTemp, m_iocBase); + +#if (!KeePassLibSD && !KeePassUAP) + if(m_iocBase.IsLocalFile()) + { + try + { + if(bEfsEncrypted) + { + try { File.Encrypt(m_iocBase.Path); } + catch(Exception) { Debug.Assert(false); } + } + + } + catch(Exception) { Debug.Assert(false); } + } +#endif + + if(bMadeUnhidden) UrlUtil.HideFile(m_iocBase.Path, true); // Hide again + } + + // For plugins + public static void Configure(string strPrefix, bool? obTransacted) + { + if(string.IsNullOrEmpty(strPrefix)) { Debug.Assert(false); return; } + + if(obTransacted.HasValue) + g_dEnabled[strPrefix] = obTransacted.Value; + else g_dEnabled.Remove(strPrefix); + } +} +} diff --git a/src/KeePassLib2AndroidSdkStyle/Serialization/HashedBlockStream.cs b/src/KeePassLib2AndroidSdkStyle/Serialization/HashedBlockStream.cs new file mode 100644 index 00000000..5ea52799 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Serialization/HashedBlockStream.cs @@ -0,0 +1,352 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Diagnostics; +using System.IO; +using System.Text; + +using KeePassLib.Cryptography; +using KeePassLib.Native; +using KeePassLib.Utility; + +#if KeePassLibSD +using KeePassLibSD; +#endif + +namespace KeePassLib.Serialization +{ + + [System.Serializable] + public class InvalidDataException: Exception + { + /// + /// Initializes a new instance of the class + /// + public InvalidDataException () + { + } + + /// + /// Initializes a new instance of the class + /// + /// A that describes the exception. + public InvalidDataException (string message) : base (message) + { + } + + /// + /// Initializes a new instance of the class + /// + /// A that describes the exception. + /// The exception that is the cause of the current exception. + public InvalidDataException (string message, Exception inner) : base (message, inner) + { + } + + /// + /// Initializes a new instance of the class + /// + /// The contextual information about the source or destination. + /// The object that holds the serialized object data. + protected InvalidDataException (System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base (info, context) + { + } + } + + public sealed class HashedBlockStream : Stream + { + private const int NbDefaultBufferSize = 1024 * 1024; // 1 MB + + private Stream m_sBaseStream; + private bool m_bWriting; + private bool m_bVerify; + private bool m_bEos = false; + + private BinaryReader m_brInput; + private BinaryWriter m_bwOutput; + + private byte[] m_pbBuffer; + private int m_nBufferPos = 0; + + private uint m_uBlockIndex = 0; + + public override bool CanRead + { + get { return !m_bWriting; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return m_bWriting; } + } + + public override long Length + { + get { Debug.Assert(false); throw new NotSupportedException(); } + } + + public override long Position + { + get { Debug.Assert(false); throw new NotSupportedException(); } + set { Debug.Assert(false); throw new NotSupportedException(); } + } + + public HashedBlockStream(Stream sBaseStream, bool bWriting) + { + Initialize(sBaseStream, bWriting, 0, true); + } + + public HashedBlockStream(Stream sBaseStream, bool bWriting, int nBufferSize) + { + Initialize(sBaseStream, bWriting, nBufferSize, true); + } + + public HashedBlockStream(Stream sBaseStream, bool bWriting, int nBufferSize, + bool bVerify) + { + Initialize(sBaseStream, bWriting, nBufferSize, bVerify); + } + + private void Initialize(Stream sBaseStream, bool bWriting, int nBufferSize, + bool bVerify) + { + if(sBaseStream == null) throw new ArgumentNullException("sBaseStream"); + if(nBufferSize < 0) throw new ArgumentOutOfRangeException("nBufferSize"); + + if(nBufferSize == 0) nBufferSize = NbDefaultBufferSize; + + m_sBaseStream = sBaseStream; + m_bWriting = bWriting; + m_bVerify = bVerify; + + UTF8Encoding utf8 = StrUtil.Utf8; + if(!m_bWriting) // Reading mode + { + if(!m_sBaseStream.CanRead) + throw new InvalidOperationException(); + + m_brInput = new BinaryReader(sBaseStream, utf8); + + m_pbBuffer = MemUtil.EmptyByteArray; + } + else // Writing mode + { + if(!m_sBaseStream.CanWrite) + throw new InvalidOperationException(); + + m_bwOutput = new BinaryWriter(sBaseStream, utf8); + + m_pbBuffer = new byte[nBufferSize]; + } + } + + protected override void Dispose(bool disposing) + { + if(disposing && (m_sBaseStream != null)) + { + if(!m_bWriting) // Reading mode + { + m_brInput.Close(); + m_brInput = null; + } + else // Writing mode + { + if(m_nBufferPos == 0) // No data left in buffer + WriteHashedBlock(); // Write terminating block + else + { + WriteHashedBlock(); // Write remaining buffered data + WriteHashedBlock(); // Write terminating block + } + + Flush(); + m_bwOutput.Close(); + m_bwOutput = null; + } + + m_sBaseStream.Close(); + m_sBaseStream = null; + } + + base.Dispose(disposing); + } + + public override void Flush() + { + if(m_bWriting) m_bwOutput.Flush(); + } + + public override long Seek(long lOffset, SeekOrigin soOrigin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long lValue) + { + throw new NotSupportedException(); + } + + public override int Read(byte[] pbBuffer, int nOffset, int nCount) + { + if(m_bWriting) throw new InvalidOperationException(); + + int nRemaining = nCount; + while(nRemaining > 0) + { + if(m_nBufferPos == m_pbBuffer.Length) + { + if(ReadHashedBlock() == false) + return (nCount - nRemaining); // Bytes actually read + } + + int nCopy = Math.Min(m_pbBuffer.Length - m_nBufferPos, nRemaining); + + Array.Copy(m_pbBuffer, m_nBufferPos, pbBuffer, nOffset, nCopy); + + nOffset += nCopy; + m_nBufferPos += nCopy; + + nRemaining -= nCopy; + } + + return nCount; + } + + private bool ReadHashedBlock() + { + if(m_bEos) return false; // End of stream reached already + + m_nBufferPos = 0; + + if(m_brInput.ReadUInt32() != m_uBlockIndex) + throw new InvalidDataException(); + ++m_uBlockIndex; + + byte[] pbStoredHash = m_brInput.ReadBytes(32); + if((pbStoredHash == null) || (pbStoredHash.Length != 32)) + throw new InvalidDataException(); + + int nBufferSize = 0; + try { nBufferSize = m_brInput.ReadInt32(); } + catch(NullReferenceException) // Mono bug workaround (LaunchPad 783268) + { + if(!NativeLib.IsUnix()) throw; + } + + if(nBufferSize < 0) + throw new InvalidDataException(); + + if(nBufferSize == 0) + { + for(int iHash = 0; iHash < 32; ++iHash) + { + if(pbStoredHash[iHash] != 0) + throw new InvalidDataException(); + } + + m_bEos = true; + m_pbBuffer = MemUtil.EmptyByteArray; + return false; + } + + m_pbBuffer = m_brInput.ReadBytes(nBufferSize); + if((m_pbBuffer == null) || ((m_pbBuffer.Length != nBufferSize) && m_bVerify)) + throw new InvalidDataException(); + + if(m_bVerify) + { + byte[] pbComputedHash = CryptoUtil.HashSha256(m_pbBuffer); + if((pbComputedHash == null) || (pbComputedHash.Length != 32)) + throw new InvalidOperationException(); + + if(!MemUtil.ArraysEqual(pbStoredHash, pbComputedHash)) + throw new InvalidDataException(); + } + + return true; + } + + public override void Write(byte[] pbBuffer, int nOffset, int nCount) + { + if(!m_bWriting) throw new InvalidOperationException(); + + while(nCount > 0) + { + if(m_nBufferPos == m_pbBuffer.Length) + WriteHashedBlock(); + + int nCopy = Math.Min(m_pbBuffer.Length - m_nBufferPos, nCount); + + Array.Copy(pbBuffer, nOffset, m_pbBuffer, m_nBufferPos, nCopy); + + nOffset += nCopy; + m_nBufferPos += nCopy; + + nCount -= nCopy; + } + } + + private void WriteHashedBlock() + { + m_bwOutput.Write(m_uBlockIndex); + ++m_uBlockIndex; + + if(m_nBufferPos > 0) + { + byte[] pbHash = CryptoUtil.HashSha256(m_pbBuffer, 0, m_nBufferPos); + + + // SHA256Managed sha256 = new SHA256Managed(); + // byte[] pbHash; + // if(m_nBufferPos == m_pbBuffer.Length) + // pbHash = sha256.ComputeHash(m_pbBuffer); + // else + // { + // byte[] pbData = new byte[m_nBufferPos]; + // Array.Copy(m_pbBuffer, 0, pbData, 0, m_nBufferPos); + // pbHash = sha256.ComputeHash(pbData); + // } + + m_bwOutput.Write(pbHash); + } + else + { + m_bwOutput.Write((ulong)0); // Zero hash + m_bwOutput.Write((ulong)0); + m_bwOutput.Write((ulong)0); + m_bwOutput.Write((ulong)0); + } + + m_bwOutput.Write(m_nBufferPos); + + if(m_nBufferPos > 0) + m_bwOutput.Write(m_pbBuffer, 0, m_nBufferPos); + + m_nBufferPos = 0; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Serialization/HmacBlockStream.cs b/src/KeePassLib2AndroidSdkStyle/Serialization/HmacBlockStream.cs new file mode 100644 index 00000000..b9dd414a --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Serialization/HmacBlockStream.cs @@ -0,0 +1,324 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +#if !KeePassUAP +using System.Security.Cryptography; +#endif + +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public sealed class HmacBlockStream : Stream + { + private const int NbDefaultBufferSize = 1024 * 1024; // 1 MB + + private Stream m_sBase; + private readonly bool m_bWriting; + private readonly bool m_bVerify; + private byte[] m_pbKey; + + private bool m_bEos = false; + private byte[] m_pbBuffer; + private int m_iBufferPos = 0; + + private ulong m_uBlockIndex = 0; + + public override bool CanRead + { + get { return !m_bWriting; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return m_bWriting; } + } + + public override long Length + { + get { Debug.Assert(false); throw new NotSupportedException(); } + } + + public override long Position + { + get { Debug.Assert(false); throw new NotSupportedException(); } + set { Debug.Assert(false); throw new NotSupportedException(); } + } + + public HmacBlockStream(Stream sBase, bool bWriting, bool bVerify, + byte[] pbKey) + { + if(sBase == null) throw new ArgumentNullException("sBase"); + if(pbKey == null) throw new ArgumentNullException("pbKey"); + + m_sBase = sBase; + m_bWriting = bWriting; + m_bVerify = bVerify; + m_pbKey = pbKey; + + if(!m_bWriting) // Reading mode + { + if(!m_sBase.CanRead) throw new InvalidOperationException(); + + m_pbBuffer = MemUtil.EmptyByteArray; + } + else // Writing mode + { + if(!m_sBase.CanWrite) throw new InvalidOperationException(); + + m_pbBuffer = new byte[NbDefaultBufferSize]; + } + } + + protected override void Dispose(bool disposing) + { + if(disposing && (m_sBase != null)) + { + if(m_bWriting) + { + if(m_iBufferPos == 0) // No data left in buffer + WriteSafeBlock(); // Write terminating block + else + { + WriteSafeBlock(); // Write remaining buffered data + WriteSafeBlock(); // Write terminating block + } + + Flush(); + } + + m_sBase.Close(); + m_sBase = null; + } + + base.Dispose(disposing); + } + + public override void Flush() + { + Debug.Assert(m_sBase != null); // Object should not be disposed + if(m_bWriting && (m_sBase != null)) m_sBase.Flush(); + } + + public override long Seek(long lOffset, SeekOrigin soOrigin) + { + Debug.Assert(false); + throw new NotSupportedException(); + } + + public override void SetLength(long lValue) + { + Debug.Assert(false); + throw new NotSupportedException(); + } + + internal static byte[] GetHmacKey64(byte[] pbKey, ulong uBlockIndex) + { + if(pbKey == null) throw new ArgumentNullException("pbKey"); + Debug.Assert(pbKey.Length == 64); + + // We are computing the HMAC using SHA-256, whose internal + // block size is 512 bits; thus create a key that is 512 + // bits long (using SHA-512) + + byte[] pbBlockKey; + using(SHA512Managed h = new SHA512Managed()) + { + byte[] pbIndex = MemUtil.UInt64ToBytes(uBlockIndex); + + h.TransformBlock(pbIndex, 0, pbIndex.Length, pbIndex, 0); + h.TransformBlock(pbKey, 0, pbKey.Length, pbKey, 0); + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + pbBlockKey = h.Hash; + } + +#if DEBUG + byte[] pbZero = new byte[64]; + Debug.Assert((pbBlockKey.Length == 64) && !MemUtil.ArraysEqual( + pbBlockKey, pbZero)); // Ensure we own pbBlockKey +#endif + return pbBlockKey; + } + + public override int Read(byte[] pbBuffer, int iOffset, int nCount) + { + if(m_bWriting) throw new InvalidOperationException(); + + int nRemaining = nCount; + while(nRemaining > 0) + { + if(m_iBufferPos == m_pbBuffer.Length) + { + if(!ReadSafeBlock()) + return (nCount - nRemaining); // Bytes actually read + } + + int nCopy = Math.Min(m_pbBuffer.Length - m_iBufferPos, nRemaining); + Debug.Assert(nCopy > 0); + + Array.Copy(m_pbBuffer, m_iBufferPos, pbBuffer, iOffset, nCopy); + + iOffset += nCopy; + m_iBufferPos += nCopy; + + nRemaining -= nCopy; + } + + return nCount; + } + + private bool ReadSafeBlock() + { + if(m_bEos) return false; // End of stream reached already + + byte[] pbStoredHmac = MemUtil.Read(m_sBase, 32); + if((pbStoredHmac == null) || (pbStoredHmac.Length != 32)) + throw new EndOfStreamException(KLRes.FileCorrupted + " " + + KLRes.FileIncomplete); + + // Block index is implicit: it's used in the HMAC computation, + // but does not need to be stored + // byte[] pbBlockIndex = MemUtil.Read(m_sBase, 8); + // if((pbBlockIndex == null) || (pbBlockIndex.Length != 8)) + // throw new EndOfStreamException(); + // ulong uBlockIndex = MemUtil.BytesToUInt64(pbBlockIndex); + // if((uBlockIndex != m_uBlockIndex) && m_bVerify) + // throw new InvalidDataException(); + byte[] pbBlockIndex = MemUtil.UInt64ToBytes(m_uBlockIndex); + + byte[] pbBlockSize = MemUtil.Read(m_sBase, 4); + if((pbBlockSize == null) || (pbBlockSize.Length != 4)) + throw new EndOfStreamException(KLRes.FileCorrupted + " " + + KLRes.FileIncomplete); + int nBlockSize = MemUtil.BytesToInt32(pbBlockSize); + if(nBlockSize < 0) + throw new InvalidDataException(KLRes.FileCorrupted); + + m_iBufferPos = 0; + + m_pbBuffer = MemUtil.Read(m_sBase, nBlockSize); + if((m_pbBuffer == null) || ((m_pbBuffer.Length != nBlockSize) && m_bVerify)) + throw new EndOfStreamException(KLRes.FileCorrupted + " " + + KLRes.FileIncompleteExpc); + + if(m_bVerify) + { + byte[] pbCmpHmac; + byte[] pbBlockKey = GetHmacKey64(m_pbKey, m_uBlockIndex); + using(HMACSHA256 h = new HMACSHA256(pbBlockKey)) + { + h.TransformBlock(pbBlockIndex, 0, pbBlockIndex.Length, + pbBlockIndex, 0); + h.TransformBlock(pbBlockSize, 0, pbBlockSize.Length, + pbBlockSize, 0); + + if(m_pbBuffer.Length > 0) + h.TransformBlock(m_pbBuffer, 0, m_pbBuffer.Length, + m_pbBuffer, 0); + + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + pbCmpHmac = h.Hash; + } + MemUtil.ZeroByteArray(pbBlockKey); + + if(!MemUtil.ArraysEqual(pbCmpHmac, pbStoredHmac)) + throw new InvalidDataException(KLRes.FileCorrupted); + } + + ++m_uBlockIndex; + + if(nBlockSize == 0) + { + m_bEos = true; + return false; // No further data available + } + return true; + } + + public override void Write(byte[] pbBuffer, int iOffset, int nCount) + { + if(!m_bWriting) throw new InvalidOperationException(); + + while(nCount > 0) + { + if(m_iBufferPos == m_pbBuffer.Length) + WriteSafeBlock(); + + int nCopy = Math.Min(m_pbBuffer.Length - m_iBufferPos, nCount); + Debug.Assert(nCopy > 0); + + Array.Copy(pbBuffer, iOffset, m_pbBuffer, m_iBufferPos, nCopy); + + iOffset += nCopy; + m_iBufferPos += nCopy; + + nCount -= nCopy; + } + } + + private void WriteSafeBlock() + { + byte[] pbBlockIndex = MemUtil.UInt64ToBytes(m_uBlockIndex); + + int cbBlockSize = m_iBufferPos; + byte[] pbBlockSize = MemUtil.Int32ToBytes(cbBlockSize); + + byte[] pbBlockHmac; + byte[] pbBlockKey = GetHmacKey64(m_pbKey, m_uBlockIndex); + using(HMACSHA256 h = new HMACSHA256(pbBlockKey)) + { + h.TransformBlock(pbBlockIndex, 0, pbBlockIndex.Length, + pbBlockIndex, 0); + h.TransformBlock(pbBlockSize, 0, pbBlockSize.Length, + pbBlockSize, 0); + + if(cbBlockSize > 0) + h.TransformBlock(m_pbBuffer, 0, cbBlockSize, m_pbBuffer, 0); + + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + pbBlockHmac = h.Hash; + } + MemUtil.ZeroByteArray(pbBlockKey); + + MemUtil.Write(m_sBase, pbBlockHmac); + // MemUtil.Write(m_sBase, pbBlockIndex); // Implicit + MemUtil.Write(m_sBase, pbBlockSize); + if(cbBlockSize > 0) + m_sBase.Write(m_pbBuffer, 0, cbBlockSize); + + ++m_uBlockIndex; + m_iBufferPos = 0; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Serialization/IOConnection.cs b/src/KeePassLib2AndroidSdkStyle/Serialization/IOConnection.cs new file mode 100644 index 00000000..60cfe0b4 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Serialization/IOConnection.cs @@ -0,0 +1,694 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2013 Dominik Reichl + + Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Net; +using System.Diagnostics; + +#if (!KeePassLibSD && !KeePassRT) +using System.Net.Cache; +using System.Net.Security; +#endif + +#if !KeePassRT +using System.Security.Cryptography.X509Certificates; +#endif + +using KeePassLib.Native; +using KeePassLib.Utility; +using keepass2android; + +namespace KeePassLib.Serialization +{ +#if (!KeePassLibSD && !KeePassRT) + public sealed class IOWebClient : WebClient + { + protected override WebRequest GetWebRequest(Uri address) + { + WebRequest request = base.GetWebRequest(address); + IOConnection.ConfigureWebRequest(request); + return request; + } + } +#endif + + public static class IOConnection + { +#if (!KeePassLibSD && !KeePassRT) + private static ProxyServerType m_pstProxyType = ProxyServerType.System; + private static string m_strProxyAddr = string.Empty; + private static string m_strProxyPort = string.Empty; + private static string m_strProxyUserName = string.Empty; + private static string m_strProxyPassword = string.Empty; + + private static bool m_bSslCertsAcceptInvalid = false; + internal static bool SslCertsAcceptInvalid + { + // get { return m_bSslCertsAcceptInvalid; } + set { m_bSslCertsAcceptInvalid = value; } + } + + public static RemoteCertificateValidationCallback CertificateValidationCallback { get; set; } +#endif + + // Web request methods + public const string WrmDeleteFile = "DELETEFILE"; + public const string WrmMoveFile = "MOVEFILE"; + + // Web request headers + public const string WrhMoveFileTo = "MoveFileTo"; + + public static event EventHandler IOAccessPre; + +#if (!KeePassLibSD && !KeePassRT) + // Allow self-signed certificates, expired certificates, etc. + private static bool AcceptCertificate(object sender, + X509Certificate certificate, X509Chain chain, + SslPolicyErrors sslPolicyErrors) + { + return true; + } + + internal static void SetProxy(ProxyServerType pst, string strAddr, + string strPort, string strUserName, string strPassword) + { + m_pstProxyType = pst; + m_strProxyAddr = (strAddr ?? string.Empty); + m_strProxyPort = (strPort ?? string.Empty); + m_strProxyUserName = (strUserName ?? string.Empty); + m_strProxyPassword = (strPassword ?? string.Empty); + } + + internal static void ConfigureWebRequest(WebRequest request) + { + if(request == null) { Debug.Assert(false); return; } // No throw + + // WebDAV support + if(request is HttpWebRequest) + { + request.PreAuthenticate = true; // Also auth GET + if(request.Method == WebRequestMethods.Http.Post) + request.Method = WebRequestMethods.Http.Put; + } + // else if(request is FtpWebRequest) + // { + // Debug.Assert(((FtpWebRequest)request).UsePassive); + // } + + // Not implemented and ignored in Mono < 2.10 + try + { + //deactivated. No longer supported in Mono 4.8? + //request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore); + } + catch(NotImplementedException) { } + catch(Exception) { Debug.Assert(false); } + + try + { + IWebProxy prx; + if(GetWebProxy(out prx)) request.Proxy = prx; + } + catch(Exception) { Debug.Assert(false); } + } + + internal static void ConfigureWebClient(WebClient wc) + { + + try + { + IWebProxy prx; + if(GetWebProxy(out prx)) wc.Proxy = prx; + } + catch(Exception) { Debug.Assert(false); } + } + + private static bool GetWebProxy(out IWebProxy prx) + { + prx = null; + + if(m_pstProxyType == ProxyServerType.None) + return true; // Use null proxy + if(m_pstProxyType == ProxyServerType.Manual) + { + try + { + if(m_strProxyPort.Length > 0) + prx = new WebProxy(m_strProxyAddr, int.Parse(m_strProxyPort)); + else prx = new WebProxy(m_strProxyAddr); + + if((m_strProxyUserName.Length > 0) || (m_strProxyPassword.Length > 0)) + prx.Credentials = new NetworkCredential(m_strProxyUserName, + m_strProxyPassword); + + return true; // Use manual proxy + } + catch(Exception exProxy) + { + string strInfo = m_strProxyAddr; + if(m_strProxyPort.Length > 0) strInfo += ":" + m_strProxyPort; + MessageService.ShowWarning(strInfo, exProxy.Message); + } + + return false; // Use default + } + + if((m_strProxyUserName.Length == 0) && (m_strProxyPassword.Length == 0)) + return false; // Use default proxy, no auth + + try + { + prx = WebRequest.DefaultWebProxy; + if(prx == null) prx = WebRequest.GetSystemWebProxy(); + if(prx == null) throw new InvalidOperationException(); + + prx.Credentials = new NetworkCredential(m_strProxyUserName, + m_strProxyPassword); + return true; + } + catch(Exception) { Debug.Assert(false); } + + return false; + } + + private static void PrepareWebAccess() + { + /* + ServicePointManager.ServerCertificateValidationCallback = + IOConnection.AcceptCertificate;*/ + ServicePointManager.ServerCertificateValidationCallback = CertificateValidationCallback; + } + + private static IOWebClient CreateWebClient(IOConnectionInfo ioc, bool digestAuth) + { + PrepareWebAccess(); + + IOWebClient wc = new IOWebClient(); + ConfigureWebClient(wc); + + if ((ioc.UserName.Length > 0) || (ioc.Password.Length > 0)) + { + //set the credentials without a cache (in case the cache below fails: + + //check for backslash to determine whether we need to specify the domain: + int backslashPos = ioc.UserName.IndexOf("\\", StringComparison.Ordinal); + if (backslashPos > 0) + { + string domain = ioc.UserName.Substring(0, backslashPos); + string user = ioc.UserName.Substring(backslashPos + 1); + wc.Credentials = new NetworkCredential(user, ioc.Password, domain); + } + else + { + wc.Credentials = new NetworkCredential(ioc.UserName, ioc.Password); + } + + + if (digestAuth) + { + //try to use the credential cache to access with Digest support: + try + { + var credentialCache = new CredentialCache(); + + credentialCache.Add( + new Uri(new Uri(ioc.Path).GetLeftPart(UriPartial.Authority)), + "Digest", + new NetworkCredential(ioc.UserName, ioc.Password) + ); + + credentialCache.Add( + new Uri(new Uri(ioc.Path).GetLeftPart(UriPartial.Authority)), + "NTLM", + new NetworkCredential(ioc.UserName, ioc.Password) + ); + + + wc.Credentials = credentialCache; + } catch (NotImplementedException e) + { + Kp2aLog.Log(e.ToString()); + } catch (Exception e) + { + Kp2aLog.LogUnexpectedError(e); + Debug.Assert(false); + } + } + } + else if(NativeLib.IsUnix()) // Mono requires credentials + wc.Credentials = new NetworkCredential("anonymous", string.Empty); + + return wc; + } + + private static WebRequest CreateWebRequest(IOConnectionInfo ioc, bool digestAuth) + { + PrepareWebAccess(); + + WebRequest req = WebRequest.Create(ioc.Path); + ConfigureWebRequest(req); + + if((ioc.UserName.Length > 0) || (ioc.Password.Length > 0)) + { + req.Credentials = new NetworkCredential(ioc.UserName, ioc.Password); + + if (digestAuth) + { + var credentialCache = new CredentialCache(); + credentialCache.Add( + new Uri(new Uri(ioc.Path).GetLeftPart(UriPartial.Authority)), // request url's host + "Digest", // authentication type + new NetworkCredential(ioc.UserName, ioc.Password) // credentials + ); + credentialCache.Add( + new Uri(new Uri(ioc.Path).GetLeftPart(UriPartial.Authority)), // request url's host + "NTLM", // authentication type + new NetworkCredential(ioc.UserName, ioc.Password) // credentials + ); + + req.Credentials = credentialCache; + } + } + else if(NativeLib.IsUnix()) // Mono requires credentials + req.Credentials = new NetworkCredential("anonymous", string.Empty); + + return req; + } + + public static Stream OpenRead(IOConnectionInfo ioc) + { + RaiseIOAccessPreEvent(ioc, IOAccessType.Read); + + if(StrUtil.IsDataUri(ioc.Path)) + { + byte[] pbData = StrUtil.DataUriToData(ioc.Path); + if (pbData != null) + return new MemoryStream(pbData, false); + } + + if (ioc.IsLocalFile()) + return OpenReadLocal(ioc); + + try + { + return CreateWebClient(ioc, false).OpenRead(new Uri(ioc.Path)); + } catch (WebException ex) + { + if ((ex.Response is HttpWebResponse) && (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.Unauthorized)) + return CreateWebClient(ioc, true).OpenRead(new Uri(ioc.Path)); + else + throw; + } + + } +#else + public static Stream OpenRead(IOConnectionInfo ioc) + { + RaiseIOAccessPreEvent(ioc, IOAccessType.Read); + + return OpenReadLocal(ioc); + } +#endif + + private static Stream OpenReadLocal(IOConnectionInfo ioc) + { + return new FileStream(ioc.Path, FileMode.Open, FileAccess.Read, + FileShare.Read); + } + +#if (!KeePassLibSD && !KeePassRT) + + class UploadOnCloseMemoryStream: MemoryStream + { + IOConnectionInfo ioc; + string method; + Uri destinationFilePath; + + public UploadOnCloseMemoryStream(IOConnectionInfo _ioc, string _method, Uri _destinationFilePath) + { + ioc = _ioc; + this.method = _method; + this.destinationFilePath = _destinationFilePath; + } + + public UploadOnCloseMemoryStream(IOConnectionInfo _ioc, Uri _destinationFilePath) + { + this.ioc = _ioc; + this.method = null; + this.destinationFilePath = _destinationFilePath; + } + + public override void Close() + { + base.Close(); + + WebRequest testReq = WebRequest.Create(ioc.Path); + if (testReq is HttpWebRequest) + { + RepeatWithDigestOnFail(ioc, req => + { + req.Headers.Add("Translate: f"); + + if (method != null) + req.Method = method; + var data = this.ToArray(); + + using (Stream s = req.GetRequestStream()) + { + s.Write(data, 0, data.Length); + req.GetResponse(); + s.Close(); + } + }); + } + else + { + try + { + uploadData(IOConnection.CreateWebClient(ioc, false)); + } + catch (WebException ex) + { + //todo: does this make sense for FTP at all? Remove? + if ((ex.Response is HttpWebResponse) && (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.Unauthorized)) + uploadData(IOConnection.CreateWebClient(ioc, true)); + else + throw; + } + } + + + + } + + void uploadData(WebClient webClient) + { + if (method != null) + { + webClient.UploadData(destinationFilePath, method, this.ToArray()); + } + else + { + webClient.UploadData(destinationFilePath, this.ToArray()); + } + } + + + } + + public static Stream OpenWrite(IOConnectionInfo ioc) + { + if(ioc == null) { Debug.Assert(false); return null; } + + RaiseIOAccessPreEvent(ioc, IOAccessType.Write); + + if(ioc.IsLocalFile()) return OpenWriteLocal(ioc); + + Uri uri = new Uri(ioc.Path); + + // Mono does not set HttpWebRequest.Method to POST for writes, + // so one needs to set the method to PUT explicitly + if(NativeLib.IsUnix() && (uri.Scheme.Equals(Uri.UriSchemeHttp, + StrUtil.CaseIgnoreCmp) || uri.Scheme.Equals(Uri.UriSchemeHttps, + StrUtil.CaseIgnoreCmp))) + return new UploadOnCloseMemoryStream(ioc, WebRequestMethods.Http.Put, uri); + + return new UploadOnCloseMemoryStream(ioc, uri); + } +#else + public static Stream OpenWrite(IOConnectionInfo ioc) + { + RaiseIOAccessPreEvent(ioc, IOAccessType.Write); + + return OpenWriteLocal(ioc); + } +#endif + + private static Stream OpenWriteLocal(IOConnectionInfo ioc) + { + return new FileStream(ioc.Path, FileMode.Create, FileAccess.Write, + FileShare.None); + } + + public static bool FileExists(IOConnectionInfo ioc) + { + return FileExists(ioc, false); + } + + public static bool FileExists(IOConnectionInfo ioc, bool bThrowErrors) + { + if(ioc == null) { Debug.Assert(false); return false; } + + RaiseIOAccessPreEvent(ioc, IOAccessType.Exists); + + if(ioc.IsLocalFile()) return File.Exists(ioc.Path); + +#if (!KeePassLibSD && !KeePassRT) + if(ioc.Path.StartsWith("ftp://", StrUtil.CaseIgnoreCmp)) + { + bool b = SendCommand(ioc, WebRequestMethods.Ftp.GetDateTimestamp); + if(!b && bThrowErrors) throw new InvalidOperationException(); + return b; + } +#endif + + try + { + Stream s = OpenRead(ioc); + if(s == null) throw new Java.IO.FileNotFoundException(); + + try { s.ReadByte(); } + catch(Exception) { } + + // We didn't download the file completely; close may throw + // an exception -- that's okay + try { s.Close(); } + catch(Exception) { } + } + catch(Exception) + { + if(bThrowErrors) throw; + return false; + } + + return true; + } + + delegate void DoWithRequest(WebRequest req); + + + static void RepeatWithDigestOnFail(IOConnectionInfo ioc, DoWithRequest f) + { + WebRequest req = CreateWebRequest(ioc, false); + try{ + f(req); + } + catch (WebException ex) + { + if ((ex.Response is HttpWebResponse) && (((HttpWebResponse) ex.Response).StatusCode == HttpStatusCode.Unauthorized)) + { + req = CreateWebRequest(ioc, true); + f(req); + } + else throw; + } + } + + public static void DeleteFile(IOConnectionInfo ioc) + { + RaiseIOAccessPreEvent(ioc, IOAccessType.Delete); + + //in case a user entered a directory instead of a filename, make sure we're never + //deleting their whole WebDAV/FTP content + if (ioc.Path.EndsWith("/")) + throw new IOException("Delete file does not expect directory URIs."); + + if(ioc.IsLocalFile()) { File.Delete(ioc.Path); return; } + +#if (!KeePassLibSD && !KeePassRT) + RepeatWithDigestOnFail(ioc, (WebRequest req) => { + if(req != null) + { + if(req is HttpWebRequest) req.Method = "DELETE"; + else if(req is FtpWebRequest) req.Method = WebRequestMethods.Ftp.DeleteFile; + else if(req is FileWebRequest) + { + File.Delete(UrlUtil.FileUrlToPath(ioc.Path)); + return; + } + else req.Method = WrmDeleteFile; + + DisposeResponse(req.GetResponse(), true); + } + }); +#endif + } + + /// + /// Rename/move a file. For local file system and WebDAV, the + /// specified file is moved, i.e. the file destination can be + /// in a different directory/path. In contrast, for FTP the + /// file is renamed, i.e. its destination must be in the same + /// directory/path. + /// + /// Source file path. + /// Target file path. + public static void RenameFile(IOConnectionInfo iocFrom, IOConnectionInfo iocTo) + { + RaiseIOAccessPreEvent(iocFrom, iocTo, IOAccessType.Move); + + if(iocFrom.IsLocalFile()) { File.Move(iocFrom.Path, iocTo.Path); return; } + +#if (!KeePassLibSD && !KeePassRT) + RepeatWithDigestOnFail(iocFrom, (WebRequest req)=> { if(req != null) + { + if(req is HttpWebRequest) + { + req.Method = "MOVE"; + req.Headers.Set("Destination", iocTo.Path); // Full URL supported + } + else if(req is FtpWebRequest) + { + req.Method = WebRequestMethods.Ftp.Rename; + string strTo = UrlUtil.GetFileName(iocTo.Path); + + // We're affected by .NET bug 621450: + // https://connect.microsoft.com/VisualStudio/feedback/details/621450/problem-renaming-file-on-ftp-server-using-ftpwebrequest-in-net-framework-4-0-vs2010-only + // Prepending "./", "%2E/" or "Dummy/../" doesn't work. + + ((FtpWebRequest)req).RenameTo = strTo; + } + else if(req is FileWebRequest) + { + File.Move(UrlUtil.FileUrlToPath(iocFrom.Path), + UrlUtil.FileUrlToPath(iocTo.Path)); + return; + } + else + { + req.Method = WrmMoveFile; + req.Headers.Set(WrhMoveFileTo, iocTo.Path); + } + + DisposeResponse(req.GetResponse(), true); + } + }); + +#endif + + // using(Stream sIn = IOConnection.OpenRead(iocFrom)) + // { + // using(Stream sOut = IOConnection.OpenWrite(iocTo)) + // { + // MemUtil.CopyStream(sIn, sOut); + // sOut.Close(); + // } + // + // sIn.Close(); + // } + // DeleteFile(iocFrom); + } + +#if (!KeePassLibSD && !KeePassRT) + private static bool SendCommand(IOConnectionInfo ioc, string strMethod) + { + try + { + RepeatWithDigestOnFail(ioc, (WebRequest req)=> { + req.Method = strMethod; + DisposeResponse(req.GetResponse(), true); + + }); + } + catch(Exception) { return false; } + + return true; + } +#endif + + private static void DisposeResponse(WebResponse wr, bool bGetStream) + { + if(wr == null) return; + + try + { + if(bGetStream) + { + Stream s = wr.GetResponseStream(); + if(s != null) s.Close(); + } + } + catch(Exception) { Debug.Assert(false); } + + try { wr.Close(); } + catch(Exception) { Debug.Assert(false); } + } + + public static byte[] ReadFile(IOConnectionInfo ioc) + { + Stream sIn = null; + MemoryStream ms = null; + try + { + sIn = IOConnection.OpenRead(ioc); + if (sIn == null) return null; + + ms = new MemoryStream(); + MemUtil.CopyStream(sIn, ms); + + return ms.ToArray(); + } + catch (Exception e) + { + Kp2aLog.Log("error opening file: " + e); + } + finally + { + if(sIn != null) sIn.Close(); + if(ms != null) ms.Close(); + } + + return null; + } + + private static void RaiseIOAccessPreEvent(IOConnectionInfo ioc, IOAccessType t) + { + RaiseIOAccessPreEvent(ioc, null, t); + } + + private static void RaiseIOAccessPreEvent(IOConnectionInfo ioc, + IOConnectionInfo ioc2, IOAccessType t) + { + if(ioc == null) { Debug.Assert(false); return; } + // ioc2 may be null + + if(IOConnection.IOAccessPre != null) + { + IOConnectionInfo ioc2Lcl = ((ioc2 != null) ? ioc2.CloneDeep() : null); + IOAccessEventArgs e = new IOAccessEventArgs(ioc.CloneDeep(), ioc2Lcl, t); + IOConnection.IOAccessPre(null, e); + } + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Serialization/IOConnectionInfo.cs b/src/KeePassLib2AndroidSdkStyle/Serialization/IOConnectionInfo.cs new file mode 100644 index 00000000..96401093 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Serialization/IOConnectionInfo.cs @@ -0,0 +1,371 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Xml.Serialization; + +using KeePassLib.Interfaces; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public enum IOCredSaveMode + { + /// + /// Do not remember user name or password. + /// + NoSave = 0, + + /// + /// Remember the user name only, not the password. + /// + UserNameOnly, + + /// + /// Save both user name and password. + /// + SaveCred + } + + public enum IOCredProtMode + { + None = 0, + Obf + } + + /* public enum IOFileFormatHint + { + None = 0, + Deprecated + } */ + + public sealed class IOConnectionInfo : IDeepCloneable + { + // private IOFileFormatHint m_ioHint = IOFileFormatHint.None; + + private string m_strUrl = string.Empty; + public string Path + { + get { return m_strUrl; } + set + { + Debug.Assert(value != null); + if(value == null) throw new ArgumentNullException("value"); + + m_strUrl = value; + } + } + + private string m_strUser = string.Empty; + [DefaultValue("")] + public string UserName + { + get { return m_strUser; } + set + { + Debug.Assert(value != null); + if(value == null) throw new ArgumentNullException("value"); + + m_strUser = value; + } + } + + private string m_strPassword = string.Empty; + [DefaultValue("")] + public string Password + { + get { return m_strPassword; } + set + { + Debug.Assert(value != null); + if(value == null) throw new ArgumentNullException("value"); + + m_strPassword = value; + } + } + + private IOCredProtMode m_ioCredProtMode = IOCredProtMode.None; + public IOCredProtMode CredProtMode + { + get { return m_ioCredProtMode; } + set { m_ioCredProtMode = value; } + } + + private IOCredSaveMode m_ioCredSaveMode = IOCredSaveMode.NoSave; + public IOCredSaveMode CredSaveMode + { + get { return m_ioCredSaveMode; } + set { m_ioCredSaveMode = value; } + } + + private bool m_bComplete = false; + [XmlIgnore] + public bool IsComplete // Credentials etc. fully specified + { + get { return m_bComplete; } + set { m_bComplete = value; } + } + + /* public IOFileFormatHint FileFormatHint + { + get { return m_ioHint; } + set { m_ioHint = value; } + } */ + + private IocProperties m_props = new IocProperties(); + [XmlIgnore] + public IocProperties Properties + { + get { return m_props; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_props = value; + } + } + + /// + /// For serialization only; use Properties in code. + /// + [DefaultValue("")] + public string PropertiesEx + { + get { return m_props.Serialize(); } + set + { + if(value == null) throw new ArgumentNullException("value"); + + IocProperties p = IocProperties.Deserialize(value); + Debug.Assert(p != null); + m_props = (p ?? new IocProperties()); + } + } + + public IOConnectionInfo CloneDeep() + { + IOConnectionInfo ioc = (IOConnectionInfo)this.MemberwiseClone(); + ioc.m_props = m_props.CloneDeep(); + return ioc; + } + +#if DEBUG // For debugger display only + public override string ToString() + { + return GetDisplayName(); + } +#endif + + + /// + /// Serialize the current connection info to a string. Credentials + /// are serialized based on the CredSaveMode property. + /// + /// Input object to be serialized. + /// Serialized object as string. + public static string SerializeToString(IOConnectionInfo iocToCompile) + { + Debug.Assert(iocToCompile != null); + if(iocToCompile == null) throw new ArgumentNullException("iocToCompile"); + + string strUrl = iocToCompile.Path; + string strUser = TransformUnreadable(iocToCompile.UserName, true); + string strPassword = TransformUnreadable(iocToCompile.Password, true); + + string strAll = strUrl + strUser + strPassword + "CUN"; + char chSep = StrUtil.GetUnusedChar(strAll); + if(chSep == char.MinValue) throw new FormatException(); + + StringBuilder sb = new StringBuilder(); + sb.Append(chSep); + sb.Append(strUrl); + sb.Append(chSep); + + if(iocToCompile.CredSaveMode == IOCredSaveMode.SaveCred) + { + sb.Append('C'); + sb.Append(chSep); + sb.Append(strUser); + sb.Append(chSep); + sb.Append(strPassword); + } + else if(iocToCompile.CredSaveMode == IOCredSaveMode.UserNameOnly) + { + sb.Append('U'); + sb.Append(chSep); + sb.Append(strUser); + sb.Append(chSep); + } + else // Don't remember credentials + { + sb.Append('N'); + sb.Append(chSep); + sb.Append(chSep); + } + + return sb.ToString(); + } + + public static IOConnectionInfo UnserializeFromString(string strToDecompile) + { + Debug.Assert(strToDecompile != null); + if(strToDecompile == null) throw new ArgumentNullException("strToDecompile"); + if(strToDecompile.Length <= 1) throw new ArgumentException(); + + char chSep = strToDecompile[0]; + string[] vParts = strToDecompile.Substring(1, strToDecompile.Length - + 1).Split(new char[]{ chSep }); + if(vParts.Length < 4) throw new ArgumentException(); + + IOConnectionInfo s = new IOConnectionInfo(); + s.Path = vParts[0]; + + if(vParts[1] == "C") + s.CredSaveMode = IOCredSaveMode.SaveCred; + else if(vParts[1] == "U") + s.CredSaveMode = IOCredSaveMode.UserNameOnly; + else + s.CredSaveMode = IOCredSaveMode.NoSave; + + s.UserName = TransformUnreadable(vParts[2], false); + s.Password = TransformUnreadable(vParts[3], false); + return s; + } + + + /// + /// Very simple string protection. Doesn't really encrypt the input + /// string, only encodes it that it's not readable on the first glance. + /// + /// The string to encode/decode. + /// If true, the string will be encoded, + /// otherwise it'll be decoded. + /// Encoded/decoded string. + private static string TransformUnreadable(string strToEncode, bool bEncode) + { + Debug.Assert(strToEncode != null); + if(strToEncode == null) throw new ArgumentNullException("strToEncode"); + + if(bEncode) + { + byte[] pbUtf8 = StrUtil.Utf8.GetBytes(strToEncode); + + unchecked + { + for(int iPos = 0; iPos < pbUtf8.Length; ++iPos) + pbUtf8[iPos] += (byte)(iPos * 11); + } + + return Convert.ToBase64String(pbUtf8); + } + else // Decode + { + byte[] pbBase = Convert.FromBase64String(strToEncode); + + unchecked + { + for(int iPos = 0; iPos < pbBase.Length; ++iPos) + pbBase[iPos] -= (byte)(iPos * 11); + } + + return StrUtil.Utf8.GetString(pbBase, 0, pbBase.Length); + } + } + + + public string GetDisplayName() + { + string str = m_strUrl; + + if(m_strUser.Length > 0) + str += (" (" + m_strUser + ")"); + + return str; + } + + public bool IsEmpty() + { + return (m_strUrl.Length == 0); + } + + public static IOConnectionInfo FromPath(string strPath) + { + IOConnectionInfo ioc = new IOConnectionInfo(); + + ioc.Path = strPath; + ioc.CredSaveMode = IOCredSaveMode.NoSave; + + return ioc; + } + + public bool CanProbablyAccess() + { + if(IsLocalFile()) return File.Exists(m_strUrl); + + return true; + } + + public bool IsLocalFile() + { + // Not just ":/", see e.g. AppConfigEx.ChangePathRelAbs + return (m_strUrl.IndexOf("://") < 0); + } + + public void ClearCredentials(bool bDependingOnRememberMode) + { + if((bDependingOnRememberMode == false) || + (m_ioCredSaveMode == IOCredSaveMode.NoSave)) + { + m_strUser = string.Empty; + } + + if((bDependingOnRememberMode == false) || + (m_ioCredSaveMode == IOCredSaveMode.NoSave) || + (m_ioCredSaveMode == IOCredSaveMode.UserNameOnly)) + { + m_strPassword = string.Empty; + } + } + + public void Obfuscate(bool bObf) + { + if(bObf && (m_ioCredProtMode == IOCredProtMode.None)) + { + m_strPassword = StrUtil.Obfuscate(m_strPassword); + m_ioCredProtMode = IOCredProtMode.Obf; + } + else if(!bObf && (m_ioCredProtMode == IOCredProtMode.Obf)) + { + m_strPassword = StrUtil.Deobfuscate(m_strPassword); + m_ioCredProtMode = IOCredProtMode.None; + } + } + + public bool IsSameFileAs(IOConnectionInfo other) + { + if (other == null) + return false; + return Path == other.Path && UserName == other.UserName; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Serialization/IocProperties.cs b/src/KeePassLib2AndroidSdkStyle/Serialization/IocProperties.cs new file mode 100644 index 00000000..337e662c --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Serialization/IocProperties.cs @@ -0,0 +1,192 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Text; +using System.Xml; + +using KeePassLib.Interfaces; +using KeePassLib.Utility; + +using StrDict = System.Collections.Generic.Dictionary; + +namespace KeePassLib.Serialization +{ + public interface IHasIocProperties + { + IocProperties IOConnectionProperties { get; set; } + } + + public sealed class IocProperties : IDeepCloneable + { + private StrDict m_dict = new StrDict(); + + public IocProperties() + { + } + + public IocProperties CloneDeep() + { + IocProperties p = new IocProperties(); + p.m_dict = new StrDict(m_dict); + return p; + } + + public string Get(string strKey) + { + if(string.IsNullOrEmpty(strKey)) return null; + + foreach(KeyValuePair kvp in m_dict) + { + if(kvp.Key.Equals(strKey, StrUtil.CaseIgnoreCmp)) + return kvp.Value; + } + + return null; + } + + public void Set(string strKey, string strValue) + { + if(string.IsNullOrEmpty(strKey)) { Debug.Assert(false); return; } + + foreach(KeyValuePair kvp in m_dict) + { + if(kvp.Key.Equals(strKey, StrUtil.CaseIgnoreCmp)) + { + if(string.IsNullOrEmpty(strValue)) m_dict.Remove(kvp.Key); + else m_dict[kvp.Key] = strValue; + return; + } + } + + if(!string.IsNullOrEmpty(strValue)) m_dict[strKey] = strValue; + } + + public bool? GetBool(string strKey) + { + string str = Get(strKey); + if(string.IsNullOrEmpty(str)) return null; + + return StrUtil.StringToBool(str); + } + + public void SetBool(string strKey, bool? ob) + { + if(ob.HasValue) Set(strKey, (ob.Value ? "1" : "0")); + else Set(strKey, null); + } + + public long? GetLong(string strKey) + { + string str = Get(strKey); + if(string.IsNullOrEmpty(str)) return null; + + long l; + if(StrUtil.TryParseLongInvariant(str, out l)) return l; + Debug.Assert(false); + return null; + } + + public void SetLong(string strKey, long? ol) + { + if(ol.HasValue) + Set(strKey, ol.Value.ToString(NumberFormatInfo.InvariantInfo)); + else Set(strKey, null); + } + + public string Serialize() + { + if(m_dict.Count == 0) return string.Empty; + + StringBuilder sbAll = new StringBuilder(); + foreach(KeyValuePair kvp in m_dict) + { + sbAll.Append(kvp.Key); + sbAll.Append(kvp.Value); + } + + string strAll = sbAll.ToString(); + char chSepOuter = ';'; + if(strAll.IndexOf(chSepOuter) >= 0) + chSepOuter = StrUtil.GetUnusedChar(strAll); + + strAll += chSepOuter; + char chSepInner = '='; + if(strAll.IndexOf(chSepInner) >= 0) + chSepInner = StrUtil.GetUnusedChar(strAll); + + StringBuilder sb = new StringBuilder(); + sb.Append(chSepOuter); + sb.Append(chSepInner); + + foreach(KeyValuePair kvp in m_dict) + { + sb.Append(chSepOuter); + sb.Append(kvp.Key); + sb.Append(chSepInner); + sb.Append(kvp.Value); + } + + return sb.ToString(); + } + + public static IocProperties Deserialize(string strSerialized) + { + IocProperties p = new IocProperties(); + if(string.IsNullOrEmpty(strSerialized)) return p; // No assert + + char chSepOuter = strSerialized[0]; + string[] v = strSerialized.Substring(1).Split(new char[] { chSepOuter }); + if((v == null) || (v.Length < 2)) { Debug.Assert(false); return p; } + + string strMeta = v[0]; + if(string.IsNullOrEmpty(strMeta)) { Debug.Assert(false); return p; } + + char chSepInner = strMeta[0]; + char[] vSepInner = new char[] { chSepInner }; + + for(int i = 1; i < v.Length; ++i) + { + string strProp = v[i]; + if(string.IsNullOrEmpty(strProp)) { Debug.Assert(false); continue; } + + string[] vProp = strProp.Split(vSepInner); + if((vProp == null) || (vProp.Length < 2)) { Debug.Assert(false); continue; } + Debug.Assert(vProp.Length == 2); + + p.Set(vProp[0], vProp[1]); + } + + return p; + } + + public void CopyTo(IocProperties p) + { + if(p == null) { Debug.Assert(false); return; } + + foreach(KeyValuePair kvp in m_dict) + { + p.m_dict[kvp.Key] = kvp.Value; + } + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Serialization/IocPropertyInfo.cs b/src/KeePassLib2AndroidSdkStyle/Serialization/IocPropertyInfo.cs new file mode 100644 index 00000000..e84cf0d8 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Serialization/IocPropertyInfo.cs @@ -0,0 +1,99 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public sealed class IocPropertyInfo + { + private readonly string m_strName; + public string Name + { + get { return m_strName; } + } + + private readonly Type m_t; + public Type Type + { + get { return m_t; } + } + + private string m_strDisplayName; + public string DisplayName + { + get { return m_strDisplayName; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strDisplayName = value; + } + } + + private List m_lProtocols = new List(); + public IEnumerable Protocols + { + get { return m_lProtocols; } + } + + public IocPropertyInfo(string strName, Type t, string strDisplayName, + string[] vProtocols) + { + if(strName == null) throw new ArgumentNullException("strName"); + if(t == null) throw new ArgumentNullException("t"); + if(strDisplayName == null) throw new ArgumentNullException("strDisplayName"); + + m_strName = strName; + m_t = t; + m_strDisplayName = strDisplayName; + + AddProtocols(vProtocols); + } + + public void AddProtocols(string[] v) + { + if(v == null) { Debug.Assert(false); return; } + + foreach(string strProtocol in v) + { + if(strProtocol == null) continue; + + string str = strProtocol.Trim(); + if(str.Length == 0) continue; + + bool bFound = false; + foreach(string strEx in m_lProtocols) + { + if(strEx.Equals(str, StrUtil.CaseIgnoreCmp)) + { + bFound = true; + break; + } + } + + if(!bFound) m_lProtocols.Add(str); + } + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Serialization/IocPropertyInfoPool.cs b/src/KeePassLib2AndroidSdkStyle/Serialization/IocPropertyInfoPool.cs new file mode 100644 index 00000000..a7c8d871 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Serialization/IocPropertyInfoPool.cs @@ -0,0 +1,123 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public static class IocKnownProtocols + { + public const string Http = "HTTP"; + public const string Https = "HTTPS"; + public const string WebDav = "WebDAV"; + public const string Ftp = "FTP"; + } + + public static class IocKnownProperties + { + public const string Timeout = "Timeout"; + public const string PreAuth = "PreAuth"; + + public const string UserAgent = "UserAgent"; + public const string Expect100Continue = "Expect100Continue"; + + public const string Passive = "Passive"; + } + + public static class IocPropertyInfoPool + { + private static List m_l = null; + public static IEnumerable PropertyInfos + { + get { EnsureInitialized(); return m_l; } + } + + private static void EnsureInitialized() + { + if(m_l != null) return; + + string strGen = KLRes.General; + string strHttp = IocKnownProtocols.Http; + string strHttps = IocKnownProtocols.Https; + string strWebDav = IocKnownProtocols.WebDav; + string strFtp = IocKnownProtocols.Ftp; + + string[] vGen = new string[] { strGen }; + string[] vHttp = new string[] { strHttp, strHttps, strWebDav }; + string[] vFtp = new string[] { strFtp }; + + List l = new List(); + + l.Add(new IocPropertyInfo(IocKnownProperties.Timeout, + typeof(long), KLRes.Timeout + " [ms]", vGen)); + l.Add(new IocPropertyInfo(IocKnownProperties.PreAuth, + typeof(bool), KLRes.PreAuth, vGen)); + + l.Add(new IocPropertyInfo(IocKnownProperties.UserAgent, + typeof(string), KLRes.UserAgent, vHttp)); + l.Add(new IocPropertyInfo(IocKnownProperties.Expect100Continue, + typeof(bool), KLRes.Expect100Continue, vHttp)); + + l.Add(new IocPropertyInfo(IocKnownProperties.Passive, + typeof(bool), KLRes.Passive, vFtp)); + + // l.Add(new IocPropertyInfo("Test", typeof(bool), + // "Long long long long long long long long long long long long long long long long long long long long", + // new string[] { "Proto 1/9", "Proto 2/9", "Proto 3/9", "Proto 4/9", "Proto 5/9", + // "Proto 6/9", "Proto 7/9", "Proto 8/9", "Proto 9/9" })); + + m_l = l; + } + + public static IocPropertyInfo Get(string strName) + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; } + + EnsureInitialized(); + foreach(IocPropertyInfo pi in m_l) + { + if(pi.Name.Equals(strName, StrUtil.CaseIgnoreCmp)) + return pi; + } + + return null; + } + + public static bool Add(IocPropertyInfo pi) + { + if(pi == null) { Debug.Assert(false); return false; } + + // Name must be non-empty + string strName = pi.Name; + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return false; } + + IocPropertyInfo piEx = Get(strName); // Ensures initialized + if(piEx != null) { Debug.Assert(false); return false; } // Exists already + + m_l.Add(pi); + return true; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Serialization/KdbxFile.Read.Streamed.cs b/src/KeePassLib2AndroidSdkStyle/Serialization/KdbxFile.Read.Streamed.cs new file mode 100644 index 00000000..630aa2e9 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Serialization/KdbxFile.Read.Streamed.cs @@ -0,0 +1,1093 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Xml; +using keepass2android; + +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Interfaces; +using KeePassLib.Resources; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + /// + /// Serialization to KeePass KDBX files. + /// + public sealed partial class KdbxFile + { + private enum KdbContext + { + Null, + KeePassFile, + Meta, + Root, + MemoryProtection, + CustomIcons, + CustomIcon, + Binaries, + CustomData, + CustomDataItem, + RootDeletedObjects, + DeletedObject, + Group, + GroupTimes, + GroupCustomData, + GroupCustomDataItem, + Entry, + EntryTimes, + EntryString, + EntryBinary, + EntryAutoType, + EntryAutoTypeItem, + EntryHistory, + EntryCustomData, + EntryCustomDataItem + } + + private bool m_bReadNextNode = true; + private Stack m_ctxGroups = new Stack(); + private PwGroup m_ctxGroup = null; + private PwEntry m_ctxEntry = null; + private string m_ctxStringName = null; + private ProtectedString m_ctxStringValue = null; + private string m_ctxBinaryName = null; + private ProtectedBinary m_ctxBinaryValue = null; + private string m_ctxATName = null; + private string m_ctxATSeq = null; + private bool m_bEntryInHistory = false; + private PwEntry m_ctxHistoryBase = null; + private PwDeletedObject m_ctxDeletedObject = null; + private PwUuid m_uuidCustomIconID = PwUuid.Zero; + private byte[] m_pbCustomIconData = null; + private string m_strCustomIconName = null; + private DateTime? m_odtCustomIconLastMod = null; + private string m_strCustomDataKey = null; + private string m_strCustomDataValue = null; + private DateTime? m_odtCustomDataLastMod = null; + private string m_strGroupCustomDataKey = null; + private string m_strGroupCustomDataValue = null; + private string m_strEntryCustomDataKey = null; + private string m_strEntryCustomDataValue = null; + + private void ReadXmlStreamed(Stream sXml, Stream sParent) + { + using (XmlReader xr = XmlUtilEx.CreateXmlReader(sXml)) + { + ReadDocumentStreamed(xr, sParent); + } + } + + private void ReadDocumentStreamed(XmlReader xr, Stream sParentStream) + { + Debug.Assert(xr != null); + if (xr == null) throw new ArgumentNullException("xr"); + + m_ctxGroups.Clear(); + + KdbContext ctx = KdbContext.Null; + + uint uTagCounter = 0; + + bool bSupportsStatus = (m_slLogger != null); + long lStreamLength = 1; + try + { + sParentStream.Position.ToString(); // Test Position support + lStreamLength = sParentStream.Length; + } + catch (Exception) { bSupportsStatus = false; } + if (lStreamLength <= 0) { Debug.Assert(false); lStreamLength = 1; } + + m_bReadNextNode = true; + + while (true) + { + if (m_bReadNextNode) + { + if (!xr.Read()) break; + } + else m_bReadNextNode = true; + + switch (xr.NodeType) + { + case XmlNodeType.Element: + ctx = ReadXmlElement(ctx, xr); + break; + + case XmlNodeType.EndElement: + ctx = EndXmlElement(ctx, xr); + break; + + case XmlNodeType.XmlDeclaration: + break; // Ignore + + default: + Debug.Assert(false); + break; + } + + ++uTagCounter; + if (((uTagCounter & 0xFFU) == 0) && bSupportsStatus) + { + Debug.Assert(lStreamLength == sParentStream.Length); + uint uPct = (uint)((sParentStream.Position * 100) / + lStreamLength); + + // Clip percent value in case the stream reports incorrect + // position/length values (M120413) + if (uPct > 100) { Debug.Assert(false); uPct = 100; } + + if (!m_slLogger.SetProgress(uPct)) + throw new OperationCanceledException(); + } + } + + Debug.Assert(ctx == KdbContext.Null); + if (ctx != KdbContext.Null) throw new FormatException(); + + Debug.Assert(m_ctxGroups.Count == 0); + if (m_ctxGroups.Count != 0) throw new FormatException(); + } + + private KdbContext ReadXmlElement(KdbContext ctx, XmlReader xr) + { + Debug.Assert(xr.NodeType == XmlNodeType.Element); + + switch (ctx) + { + case KdbContext.Null: + if (xr.Name == ElemDocNode) + return SwitchContext(ctx, KdbContext.KeePassFile, xr); + else ReadUnknown(xr); + break; + + case KdbContext.KeePassFile: + if (xr.Name == ElemMeta) + return SwitchContext(ctx, KdbContext.Meta, xr); + else if (xr.Name == ElemRoot) + return SwitchContext(ctx, KdbContext.Root, xr); + else ReadUnknown(xr); + break; + + case KdbContext.Meta: + if (xr.Name == ElemGenerator) + ReadString(xr); // Ignore + else if (xr.Name == ElemHeaderHash) + { + // The header hash is typically only stored in + // KDBX <= 3.1 files, not in KDBX >= 4 files + // (here, the header is verified via a HMAC), + // but we also support it for KDBX >= 4 files + // (i.e. if it's present, we check it) + + string strHash = ReadString(xr); + if (!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) && + !m_bRepairMode) + { + Debug.Assert(m_uFileVersion < FileVersion32_4); + + byte[] pbHash = Convert.FromBase64String(strHash); + if (!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader)) + throw new InvalidDataException(KLRes.FileCorrupted); + } + } + else if (xr.Name == ElemSettingsChanged) + m_pwDatabase.SettingsChanged = ReadTime(xr); + else if (xr.Name == ElemDbName) + m_pwDatabase.Name = ReadString(xr); + else if (xr.Name == ElemDbNameChanged) + m_pwDatabase.NameChanged = ReadTime(xr); + else if (xr.Name == ElemDbDesc) + m_pwDatabase.Description = ReadString(xr); + else if (xr.Name == ElemDbDescChanged) + m_pwDatabase.DescriptionChanged = ReadTime(xr); + else if (xr.Name == ElemDbDefaultUser) + m_pwDatabase.DefaultUserName = ReadString(xr); + else if (xr.Name == ElemDbDefaultUserChanged) + m_pwDatabase.DefaultUserNameChanged = ReadTime(xr); + else if (xr.Name == ElemDbMntncHistoryDays) + m_pwDatabase.MaintenanceHistoryDays = ReadUInt(xr, 365); + else if (xr.Name == ElemDbColor) + { + string strColor = ReadString(xr); + if (!string.IsNullOrEmpty(strColor)) + m_pwDatabase.Color = ColorTranslator.FromHtml(strColor); + } + else if (xr.Name == ElemDbKeyChanged) + m_pwDatabase.MasterKeyChanged = ReadTime(xr); + else if (xr.Name == ElemDbKeyChangeRec) + m_pwDatabase.MasterKeyChangeRec = ReadLong(xr, -1); + else if (xr.Name == ElemDbKeyChangeForce) + m_pwDatabase.MasterKeyChangeForce = ReadLong(xr, -1); + else if (xr.Name == ElemDbKeyChangeForceOnce) + m_pwDatabase.MasterKeyChangeForceOnce = ReadBool(xr, false); + else if (xr.Name == ElemMemoryProt) + return SwitchContext(ctx, KdbContext.MemoryProtection, xr); + else if (xr.Name == ElemCustomIcons) + return SwitchContext(ctx, KdbContext.CustomIcons, xr); + else if (xr.Name == ElemRecycleBinEnabled) + m_pwDatabase.RecycleBinEnabled = ReadBool(xr, true); + else if (xr.Name == ElemRecycleBinUuid) + m_pwDatabase.RecycleBinUuid = ReadUuid(xr); + else if (xr.Name == ElemRecycleBinChanged) + m_pwDatabase.RecycleBinChanged = ReadTime(xr); + else if (xr.Name == ElemEntryTemplatesGroup) + m_pwDatabase.EntryTemplatesGroup = ReadUuid(xr); + else if (xr.Name == ElemEntryTemplatesGroupChanged) + m_pwDatabase.EntryTemplatesGroupChanged = ReadTime(xr); + else if (xr.Name == ElemHistoryMaxItems) + m_pwDatabase.HistoryMaxItems = ReadInt(xr, -1); + else if (xr.Name == ElemHistoryMaxSize) + m_pwDatabase.HistoryMaxSize = ReadLong(xr, -1); + else if (xr.Name == ElemLastSelectedGroup) + m_pwDatabase.LastSelectedGroup = ReadUuid(xr); + else if (xr.Name == ElemLastTopVisibleGroup) + m_pwDatabase.LastTopVisibleGroup = ReadUuid(xr); + else if (xr.Name == ElemBinaries) + return SwitchContext(ctx, KdbContext.Binaries, xr); + else if (xr.Name == ElemCustomData) + return SwitchContext(ctx, KdbContext.CustomData, xr); + else ReadUnknown(xr); + break; + + case KdbContext.MemoryProtection: + if (xr.Name == ElemProtTitle) + m_pwDatabase.MemoryProtection.ProtectTitle = ReadBool(xr, false); + else if (xr.Name == ElemProtUserName) + m_pwDatabase.MemoryProtection.ProtectUserName = ReadBool(xr, false); + else if (xr.Name == ElemProtPassword) + m_pwDatabase.MemoryProtection.ProtectPassword = ReadBool(xr, true); + else if (xr.Name == ElemProtUrl) + m_pwDatabase.MemoryProtection.ProtectUrl = ReadBool(xr, false); + else if (xr.Name == ElemProtNotes) + m_pwDatabase.MemoryProtection.ProtectNotes = ReadBool(xr, false); + // else if(xr.Name == ElemProtAutoHide) + // m_pwDatabase.MemoryProtection.AutoEnableVisualHiding = ReadBool(xr, true); + else ReadUnknown(xr); + break; + + case KdbContext.CustomIcons: + if (xr.Name == ElemCustomIconItem) + return SwitchContext(ctx, KdbContext.CustomIcon, xr); + else ReadUnknown(xr); + break; + + case KdbContext.CustomIcon: + if (xr.Name == ElemCustomIconItemID) + m_uuidCustomIconID = ReadUuid(xr); + else if (xr.Name == ElemCustomIconItemData) + { + string strData = ReadString(xr); + if (!string.IsNullOrEmpty(strData)) + m_pbCustomIconData = Convert.FromBase64String(strData); + else { Debug.Assert(false); } + } + else if (xr.Name == ElemName) + m_strCustomIconName = ReadString(xr); + else if (xr.Name == ElemLastModTime) + m_odtCustomIconLastMod = ReadTime(xr); + else ReadUnknown(xr); + break; + + case KdbContext.Binaries: + if (xr.Name == ElemBinary) + { + if (xr.MoveToAttribute(AttrId)) + { + string strKey = xr.Value; + ProtectedBinary pbData = ReadProtectedBinary(xr); + + int iKey; + if (!StrUtil.TryParseIntInvariant(strKey, out iKey)) + throw new FormatException(); + if (iKey < 0) throw new FormatException(); + + Debug.Assert(m_pbsBinaries.Get(iKey) == null); + Debug.Assert(m_pbsBinaries.Find(pbData) < 0); + m_pbsBinaries.Set(iKey, pbData); + } + else ReadUnknown(xr); + } + else ReadUnknown(xr); + break; + + case KdbContext.CustomData: + if (xr.Name == ElemStringDictExItem) + return SwitchContext(ctx, KdbContext.CustomDataItem, xr); + else ReadUnknown(xr); + break; + + case KdbContext.CustomDataItem: + if (xr.Name == ElemKey) + m_strCustomDataKey = ReadString(xr); + else if (xr.Name == ElemValue) + m_strCustomDataValue = ReadString(xr); + else if (xr.Name == ElemLastModTime) + m_odtCustomDataLastMod = ReadTime(xr); + else ReadUnknown(xr); + break; + + case KdbContext.Root: + if (xr.Name == ElemGroup) + { + Debug.Assert(m_ctxGroups.Count == 0); + if (m_ctxGroups.Count != 0) throw new FormatException(); + + m_pwDatabase.RootGroup = new PwGroup(false, false); + m_ctxGroups.Push(m_pwDatabase.RootGroup); + m_ctxGroup = m_ctxGroups.Peek(); + + return SwitchContext(ctx, KdbContext.Group, xr); + } + else if (xr.Name == ElemDeletedObjects) + return SwitchContext(ctx, KdbContext.RootDeletedObjects, xr); + else ReadUnknown(xr); + break; + + case KdbContext.Group: + if (xr.Name == ElemUuid) + m_ctxGroup.Uuid = ReadUuid(xr); + else if (xr.Name == ElemName) + m_ctxGroup.Name = ReadString(xr); + else if (xr.Name == ElemNotes) + m_ctxGroup.Notes = ReadString(xr); + else if (xr.Name == ElemIcon) + m_ctxGroup.IconId = ReadIconId(xr, PwIcon.Folder); + else if (xr.Name == ElemCustomIconID) + m_ctxGroup.CustomIconUuid = ReadUuid(xr); + else if (xr.Name == ElemTimes) + return SwitchContext(ctx, KdbContext.GroupTimes, xr); + else if (xr.Name == ElemIsExpanded) + m_ctxGroup.IsExpanded = ReadBool(xr, true); + else if (xr.Name == ElemGroupDefaultAutoTypeSeq) + m_ctxGroup.DefaultAutoTypeSequence = ReadString(xr); + else if (xr.Name == ElemEnableAutoType) + m_ctxGroup.EnableAutoType = StrUtil.StringToBoolEx(ReadString(xr)); + else if (xr.Name == ElemEnableSearching) + m_ctxGroup.EnableSearching = StrUtil.StringToBoolEx(ReadString(xr)); + else if (xr.Name == ElemLastTopVisibleEntry) + m_ctxGroup.LastTopVisibleEntry = ReadUuid(xr); + else if (xr.Name == ElemPreviousParentGroup) + m_ctxGroup.PreviousParentGroup = ReadUuid(xr); + else if (xr.Name == ElemTags) + m_ctxGroup.Tags = StrUtil.StringToTags(ReadString(xr)); + else if (xr.Name == ElemCustomData) + return SwitchContext(ctx, KdbContext.GroupCustomData, xr); + else if (xr.Name == ElemGroup) + { + m_ctxGroup = new PwGroup(false, false); + m_ctxGroups.Peek().AddGroup(m_ctxGroup, true); + + m_ctxGroups.Push(m_ctxGroup); + + return SwitchContext(ctx, KdbContext.Group, xr); + } + else if (xr.Name == ElemEntry) + { + m_ctxEntry = new PwEntry(false, false); + m_ctxGroup.AddEntry(m_ctxEntry, true); + + m_bEntryInHistory = false; + return SwitchContext(ctx, KdbContext.Entry, xr); + } + else ReadUnknown(xr); + break; + + case KdbContext.GroupCustomData: + if (xr.Name == ElemStringDictExItem) + return SwitchContext(ctx, KdbContext.GroupCustomDataItem, xr); + else ReadUnknown(xr); + break; + + case KdbContext.GroupCustomDataItem: + if (xr.Name == ElemKey) + m_strGroupCustomDataKey = ReadString(xr); + else if (xr.Name == ElemValue) + m_strGroupCustomDataValue = ReadString(xr); + else ReadUnknown(xr); + break; + + case KdbContext.Entry: + if (xr.Name == ElemUuid) + m_ctxEntry.Uuid = ReadUuid(xr); + else if (xr.Name == ElemIcon) + m_ctxEntry.IconId = ReadIconId(xr, PwIcon.Key); + else if (xr.Name == ElemCustomIconID) + m_ctxEntry.CustomIconUuid = ReadUuid(xr); + else if (xr.Name == ElemFgColor) + { + string strColor = ReadString(xr); + if (!string.IsNullOrEmpty(strColor)) + m_ctxEntry.ForegroundColor = ColorTranslator.FromHtml(strColor); + } + else if (xr.Name == ElemBgColor) + { + string strColor = ReadString(xr); + if (!string.IsNullOrEmpty(strColor)) + m_ctxEntry.BackgroundColor = ColorTranslator.FromHtml(strColor); + } + else if (xr.Name == ElemOverrideUrl) + m_ctxEntry.OverrideUrl = ReadString(xr); + else if (xr.Name == ElemQualityCheck) + m_ctxEntry.QualityCheck = ReadBool(xr, true); + else if (xr.Name == ElemTags) + m_ctxEntry.Tags = StrUtil.StringToTags(ReadString(xr)); + else if (xr.Name == ElemPreviousParentGroup) + m_ctxEntry.PreviousParentGroup = ReadUuid(xr); + else if (xr.Name == ElemTimes) + return SwitchContext(ctx, KdbContext.EntryTimes, xr); + else if (xr.Name == ElemString) + return SwitchContext(ctx, KdbContext.EntryString, xr); + else if (xr.Name == ElemBinary) + return SwitchContext(ctx, KdbContext.EntryBinary, xr); + else if (xr.Name == ElemAutoType) + return SwitchContext(ctx, KdbContext.EntryAutoType, xr); + else if (xr.Name == ElemCustomData) + return SwitchContext(ctx, KdbContext.EntryCustomData, xr); + else if (xr.Name == ElemHistory) + { + Debug.Assert(m_bEntryInHistory == false); + + if (m_bEntryInHistory == false) + { + m_ctxHistoryBase = m_ctxEntry; + return SwitchContext(ctx, KdbContext.EntryHistory, xr); + } + else ReadUnknown(xr); + } + else ReadUnknown(xr); + break; + + case KdbContext.GroupTimes: + case KdbContext.EntryTimes: + ITimeLogger tl = ((ctx == KdbContext.GroupTimes) ? + (ITimeLogger)m_ctxGroup : (ITimeLogger)m_ctxEntry); + Debug.Assert(tl != null); + + if (xr.Name == ElemCreationTime) + tl.CreationTime = ReadTime(xr); + else if (xr.Name == ElemLastModTime) + tl.LastModificationTime = ReadTime(xr); + else if (xr.Name == ElemLastAccessTime) + tl.LastAccessTime = ReadTime(xr); + else if (xr.Name == ElemExpiryTime) + tl.ExpiryTime = ReadTime(xr); + else if (xr.Name == ElemExpires) + tl.Expires = ReadBool(xr, false); + else if (xr.Name == ElemUsageCount) + tl.UsageCount = ReadULong(xr, 0); + else if (xr.Name == ElemLocationChanged) + tl.LocationChanged = ReadTime(xr); + else ReadUnknown(xr); + break; + + case KdbContext.EntryString: + if (xr.Name == ElemKey) + m_ctxStringName = ReadString(xr); + else if (xr.Name == ElemValue) + m_ctxStringValue = ReadProtectedString(xr); + else ReadUnknown(xr); + break; + + case KdbContext.EntryBinary: + if (xr.Name == ElemKey) + m_ctxBinaryName = ReadString(xr); + else if (xr.Name == ElemValue) + m_ctxBinaryValue = ReadProtectedBinary(xr); + else ReadUnknown(xr); + break; + + case KdbContext.EntryAutoType: + if (xr.Name == ElemAutoTypeEnabled) + m_ctxEntry.AutoType.Enabled = ReadBool(xr, true); + else if (xr.Name == ElemAutoTypeObfuscation) + m_ctxEntry.AutoType.ObfuscationOptions = + (AutoTypeObfuscationOptions)ReadInt(xr, 0); + else if (xr.Name == ElemAutoTypeDefaultSeq) + m_ctxEntry.AutoType.DefaultSequence = ReadString(xr); + else if (xr.Name == ElemAutoTypeItem) + return SwitchContext(ctx, KdbContext.EntryAutoTypeItem, xr); + else ReadUnknown(xr); + break; + + case KdbContext.EntryAutoTypeItem: + if (xr.Name == ElemWindow) + m_ctxATName = ReadString(xr); + else if (xr.Name == ElemKeystrokeSequence) + m_ctxATSeq = ReadString(xr); + else ReadUnknown(xr); + break; + + case KdbContext.EntryCustomData: + if (xr.Name == ElemStringDictExItem) + return SwitchContext(ctx, KdbContext.EntryCustomDataItem, xr); + else ReadUnknown(xr); + break; + + case KdbContext.EntryCustomDataItem: + if (xr.Name == ElemKey) + m_strEntryCustomDataKey = ReadString(xr); + else if (xr.Name == ElemValue) + m_strEntryCustomDataValue = ReadString(xr); + else ReadUnknown(xr); + break; + + case KdbContext.EntryHistory: + if (xr.Name == ElemEntry) + { + m_ctxEntry = new PwEntry(false, false); + m_ctxHistoryBase.History.Add(m_ctxEntry); + + m_bEntryInHistory = true; + return SwitchContext(ctx, KdbContext.Entry, xr); + } + else ReadUnknown(xr); + break; + + case KdbContext.RootDeletedObjects: + if (xr.Name == ElemDeletedObject) + { + m_ctxDeletedObject = new PwDeletedObject(); + m_pwDatabase.DeletedObjects.Add(m_ctxDeletedObject); + + return SwitchContext(ctx, KdbContext.DeletedObject, xr); + } + else ReadUnknown(xr); + break; + + case KdbContext.DeletedObject: + if (xr.Name == ElemUuid) + m_ctxDeletedObject.Uuid = ReadUuid(xr); + else if (xr.Name == ElemDeletionTime) + m_ctxDeletedObject.DeletionTime = ReadTime(xr); + else ReadUnknown(xr); + break; + + default: + ReadUnknown(xr); + break; + } + + return ctx; + } + + private KdbContext EndXmlElement(KdbContext ctx, XmlReader xr) + { + Debug.Assert(xr.NodeType == XmlNodeType.EndElement); + + if ((ctx == KdbContext.KeePassFile) && (xr.Name == ElemDocNode)) + return KdbContext.Null; + else if ((ctx == KdbContext.Meta) && (xr.Name == ElemMeta)) + return KdbContext.KeePassFile; + else if ((ctx == KdbContext.Root) && (xr.Name == ElemRoot)) + return KdbContext.KeePassFile; + else if ((ctx == KdbContext.MemoryProtection) && (xr.Name == ElemMemoryProt)) + return KdbContext.Meta; + else if ((ctx == KdbContext.CustomIcons) && (xr.Name == ElemCustomIcons)) + return KdbContext.Meta; + else if ((ctx == KdbContext.CustomIcon) && (xr.Name == ElemCustomIconItem)) + { + if (!m_uuidCustomIconID.Equals(PwUuid.Zero) && + (m_pbCustomIconData != null)) + { + PwCustomIcon ci = new PwCustomIcon(m_uuidCustomIconID, + m_pbCustomIconData); + if (m_strCustomIconName != null) ci.Name = m_strCustomIconName; + ci.LastModificationTime = m_odtCustomIconLastMod; + m_pwDatabase.CustomIcons.Add(ci); + } + else { Debug.Assert(false); } + + m_uuidCustomIconID = PwUuid.Zero; + m_pbCustomIconData = null; + m_strCustomIconName = null; + m_odtCustomIconLastMod = null; + + return KdbContext.CustomIcons; + } + else if ((ctx == KdbContext.Binaries) && (xr.Name == ElemBinaries)) + return KdbContext.Meta; + else if ((ctx == KdbContext.CustomData) && (xr.Name == ElemCustomData)) + return KdbContext.Meta; + else if ((ctx == KdbContext.CustomDataItem) && (xr.Name == ElemStringDictExItem)) + { + if ((m_strCustomDataKey != null) && (m_strCustomDataValue != null)) + m_pwDatabase.CustomData.Set(m_strCustomDataKey, + m_strCustomDataValue, m_odtCustomDataLastMod); + else { Debug.Assert(false); } + + m_strCustomDataKey = null; + m_strCustomDataValue = null; + m_odtCustomDataLastMod = null; + + return KdbContext.CustomData; + } + else if ((ctx == KdbContext.Group) && (xr.Name == ElemGroup)) + { + if (PwUuid.Zero.Equals(m_ctxGroup.Uuid)) + m_ctxGroup.Uuid = new PwUuid(true); // No assert (import) + + m_ctxGroups.Pop(); + + if (m_ctxGroups.Count == 0) + { + m_ctxGroup = null; + return KdbContext.Root; + } + else + { + m_ctxGroup = m_ctxGroups.Peek(); + return KdbContext.Group; + } + } + else if ((ctx == KdbContext.GroupTimes) && (xr.Name == ElemTimes)) + return KdbContext.Group; + else if ((ctx == KdbContext.GroupCustomData) && (xr.Name == ElemCustomData)) + return KdbContext.Group; + else if ((ctx == KdbContext.GroupCustomDataItem) && (xr.Name == ElemStringDictExItem)) + { + if ((m_strGroupCustomDataKey != null) && (m_strGroupCustomDataValue != null)) + m_ctxGroup.CustomData.Set(m_strGroupCustomDataKey, m_strGroupCustomDataValue); + else { Debug.Assert(false); } + + m_strGroupCustomDataKey = null; + m_strGroupCustomDataValue = null; + + return KdbContext.GroupCustomData; + } + else if ((ctx == KdbContext.Entry) && (xr.Name == ElemEntry)) + { + // Create new UUID if absent + if (PwUuid.Zero.Equals(m_ctxEntry.Uuid)) + m_ctxEntry.Uuid = new PwUuid(true); // No assert (import) + + if (m_bEntryInHistory) + { + m_ctxEntry = m_ctxHistoryBase; + return KdbContext.EntryHistory; + } + + return KdbContext.Group; + } + else if ((ctx == KdbContext.EntryTimes) && (xr.Name == ElemTimes)) + return KdbContext.Entry; + else if ((ctx == KdbContext.EntryString) && (xr.Name == ElemString)) + { + m_ctxEntry.Strings.Set(m_ctxStringName, m_ctxStringValue); + m_ctxStringName = null; + m_ctxStringValue = null; + return KdbContext.Entry; + } + else if ((ctx == KdbContext.EntryBinary) && (xr.Name == ElemBinary)) + { + if (string.IsNullOrEmpty(m_strDetachBins)) + m_ctxEntry.Binaries.Set(m_ctxBinaryName, m_ctxBinaryValue); + else + { + SaveBinary(m_ctxBinaryName, m_ctxBinaryValue, m_strDetachBins); + + m_ctxBinaryValue = null; + GC.Collect(); + } + + m_ctxBinaryName = null; + m_ctxBinaryValue = null; + return KdbContext.Entry; + } + else if ((ctx == KdbContext.EntryAutoType) && (xr.Name == ElemAutoType)) + return KdbContext.Entry; + else if ((ctx == KdbContext.EntryAutoTypeItem) && (xr.Name == ElemAutoTypeItem)) + { + AutoTypeAssociation atAssoc = new AutoTypeAssociation(m_ctxATName, + m_ctxATSeq); + m_ctxEntry.AutoType.Add(atAssoc); + m_ctxATName = null; + m_ctxATSeq = null; + return KdbContext.EntryAutoType; + } + else if ((ctx == KdbContext.EntryCustomData) && (xr.Name == ElemCustomData)) + return KdbContext.Entry; + else if ((ctx == KdbContext.EntryCustomDataItem) && (xr.Name == ElemStringDictExItem)) + { + if ((m_strEntryCustomDataKey != null) && (m_strEntryCustomDataValue != null)) + m_ctxEntry.CustomData.Set(m_strEntryCustomDataKey, m_strEntryCustomDataValue); + else { Debug.Assert(false); } + + m_strEntryCustomDataKey = null; + m_strEntryCustomDataValue = null; + + return KdbContext.EntryCustomData; + } + else if ((ctx == KdbContext.EntryHistory) && (xr.Name == ElemHistory)) + { + m_bEntryInHistory = false; + return KdbContext.Entry; + } + else if ((ctx == KdbContext.RootDeletedObjects) && (xr.Name == ElemDeletedObjects)) + return KdbContext.Root; + else if ((ctx == KdbContext.DeletedObject) && (xr.Name == ElemDeletedObject)) + { + m_ctxDeletedObject = null; + return KdbContext.RootDeletedObjects; + } + else + { + Debug.Assert(false); + throw new FormatException(); + } + } + + private string ReadString(XmlReader xr) + { + XorredBuffer xb = ProcessNode(xr); + if (xb != null) + { + Debug.Assert(false); // Protected data is unexpected here + try + { + byte[] pb = xb.ReadPlainText(); + if (pb.Length == 0) return string.Empty; + try { return StrUtil.Utf8.GetString(pb, 0, pb.Length); } + finally { MemUtil.ZeroByteArray(pb); } + } + finally { xb.Dispose(); } + } + + m_bReadNextNode = false; // ReadElementString skips end tag + return xr.ReadElementString(); + } + + private string ReadStringRaw(XmlReader xr) + { + m_bReadNextNode = false; // ReadElementString skips end tag + return xr.ReadElementString(); + } + + private byte[] ReadBase64(XmlReader xr, bool bRaw) + { + // if(bRaw) return ReadBase64RawInChunks(xr); + + string str = (bRaw ? ReadStringRaw(xr) : ReadString(xr)); + if (string.IsNullOrEmpty(str)) return MemUtil.EmptyByteArray; + + return Convert.FromBase64String(str); + } + + /* private byte[] m_pbBase64ReadBuf = new byte[1024 * 1024 * 3]; + private byte[] ReadBase64RawInChunks(XmlReader xr) + { + xr.MoveToContent(); + + List lParts = new List(); + byte[] pbBuf = m_pbBase64ReadBuf; + while(true) + { + int cb = xr.ReadElementContentAsBase64(pbBuf, 0, pbBuf.Length); + if(cb == 0) break; + + byte[] pb = new byte[cb]; + Array.Copy(pbBuf, 0, pb, 0, cb); + lParts.Add(pb); + + // No break when cb < pbBuf.Length, because ReadElementContentAsBase64 + // moves to the next XML node only when returning 0 + } + m_bReadNextNode = false; + + if(lParts.Count == 0) return MemUtil.EmptyByteArray; + if(lParts.Count == 1) return lParts[0]; + + long cbRes = 0; + for(int i = 0; i < lParts.Count; ++i) + cbRes += lParts[i].Length; + + byte[] pbRes = new byte[cbRes]; + int cbCur = 0; + for(int i = 0; i < lParts.Count; ++i) + { + Array.Copy(lParts[i], 0, pbRes, cbCur, lParts[i].Length); + cbCur += lParts[i].Length; + } + + return pbRes; + } */ + + private bool ReadBool(XmlReader xr, bool bDefault) + { + string str = ReadString(xr); + if (str == ValTrue) return true; + else if (str == ValFalse) return false; + + Debug.Assert(false); + return bDefault; + } + + private PwUuid ReadUuid(XmlReader xr) + { + byte[] pb = ReadBase64(xr, false); + if (pb.Length == 0) return PwUuid.Zero; + return new PwUuid(pb); + } + + private int ReadInt(XmlReader xr, int nDefault) + { + string str = ReadString(xr); + + int n; + if (StrUtil.TryParseIntInvariant(str, out n)) return n; + + // Backward compatibility + if (StrUtil.TryParseInt(str, out n)) return n; + + Debug.Assert(false); + return nDefault; + } + + private uint ReadUInt(XmlReader xr, uint uDefault) + { + string str = ReadString(xr); + + uint u; + if (StrUtil.TryParseUIntInvariant(str, out u)) return u; + + // Backward compatibility + if (StrUtil.TryParseUInt(str, out u)) return u; + + Debug.Assert(false); + return uDefault; + } + + private long ReadLong(XmlReader xr, long lDefault) + { + string str = ReadString(xr); + + long l; + if (StrUtil.TryParseLongInvariant(str, out l)) return l; + + // Backward compatibility + if (StrUtil.TryParseLong(str, out l)) return l; + + Debug.Assert(false); + return lDefault; + } + + private ulong ReadULong(XmlReader xr, ulong uDefault) + { + string str = ReadString(xr); + + ulong u; + if (StrUtil.TryParseULongInvariant(str, out u)) return u; + + // Backward compatibility + if (StrUtil.TryParseULong(str, out u)) return u; + + Debug.Assert(false); + return uDefault; + } + + private DateTime ReadTime(XmlReader xr) + { + // Cf. WriteObject(string, DateTime) + if ((m_format == KdbxFormat.Default) && (m_uFileVersion >= FileVersion32_4)) + { + // long l = ReadLong(xr, -1); + // if(l != -1) return DateTime.FromBinary(l); + + byte[] pb = ReadBase64(xr, false); + if (pb.Length != 8) + { + Debug.Assert(false); + byte[] pb8 = new byte[8]; + Array.Copy(pb, pb8, Math.Min(pb.Length, 8)); // Little-endian + pb = pb8; + } + long lSec = MemUtil.BytesToInt64(pb); + try + { + return new DateTime(lSec * TimeSpan.TicksPerSecond, DateTimeKind.Utc); + } + catch (System.ArgumentOutOfRangeException e) + { + //files might contain bad data, e.g. see #868. Fall back to MinValue + Kp2aLog.Log("Failed to read date from file."); + return DateTime.MinValue; + } + } + else + { + string str = ReadString(xr); + + DateTime dt; + if (TimeUtil.TryDeserializeUtc(str, out dt)) return dt; + } + + Debug.Assert(false); + return m_dtNow; + } + + private PwIcon ReadIconId(XmlReader xr, PwIcon icDefault) + { + int i = ReadInt(xr, (int)icDefault); + if ((i >= 0) && (i < (int)PwIcon.Count)) return (PwIcon)i; + + Debug.Assert(false); + return icDefault; + } + + private ProtectedString ReadProtectedString(XmlReader xr) + { + XorredBuffer xb = ProcessNode(xr); + if (xb != null) + { + try { return new ProtectedString(true, xb); } + finally { xb.Dispose(); } + } + + bool bProtect = false; + if (m_format == KdbxFormat.PlainXml) + { + if (xr.MoveToAttribute(AttrProtectedInMemPlainXml)) + { + string strProtect = xr.Value; + bProtect = ((strProtect != null) && (strProtect == ValTrue)); + } + } + + return new ProtectedString(bProtect, ReadString(xr)); + } + + private ProtectedBinary ReadProtectedBinary(XmlReader xr) + { + if (xr.MoveToAttribute(AttrRef)) + { + string strRef = xr.Value; + if (!string.IsNullOrEmpty(strRef)) + { + int iRef; + if (StrUtil.TryParseIntInvariant(strRef, out iRef)) + { + ProtectedBinary pb = m_pbsBinaries.Get(iRef); + if (pb != null) + { + // https://sourceforge.net/p/keepass/feature-requests/2023/ + xr.MoveToElement(); +#if DEBUG + string strInner = ReadStringRaw(xr); + Debug.Assert(string.IsNullOrEmpty(strInner)); +#else + ReadStringRaw(xr); +#endif + + return pb; + } + else { Debug.Assert(false); } + } + else { Debug.Assert(false); } + } + else { Debug.Assert(false); } + } + + bool bCompressed = false; + if (xr.MoveToAttribute(AttrCompressed)) + bCompressed = (xr.Value == ValTrue); + + XorredBuffer xb = ProcessNode(xr); + if (xb != null) + { + Debug.Assert(!bCompressed); // See SubWriteValue(ProtectedBinary value) + try { return new ProtectedBinary(true, xb); } + finally { xb.Dispose(); } + } + + byte[] pbData = ReadBase64(xr, true); + if (pbData.Length == 0) return new ProtectedBinary(); + + if (bCompressed) pbData = MemUtil.Decompress(pbData); + return new ProtectedBinary(false, pbData); + } + + private void ReadUnknown(XmlReader xr) + { + Debug.Assert(false); // Unknown node! + Debug.Assert(xr.NodeType == XmlNodeType.Element); + + bool bRead = false; + int cOpen = 0; + + do + { + if (bRead) xr.Read(); + bRead = true; + + 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; } + + ++cOpen; + } + } + } + while (cOpen > 0); + + m_bReadNextNode = bRead; + } + + private XorredBuffer ProcessNode(XmlReader xr) + { + // Debug.Assert(xr.NodeType == XmlNodeType.Element); + + if (xr.HasAttributes) + { + if (xr.MoveToAttribute(AttrProtected)) + { + if (xr.Value == ValTrue) + { + xr.MoveToElement(); + + byte[] pbCT = ReadBase64(xr, true); + byte[] pbPad = m_randomStream.GetRandomBytes((uint)pbCT.Length); + + return new XorredBuffer(pbCT, pbPad); + } + } + } + + return null; + } + + private static KdbContext SwitchContext(KdbContext ctxCurrent, + KdbContext ctxNew, XmlReader xr) + { + if (xr.IsEmptyElement) return ctxCurrent; + return ctxNew; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Serialization/KdbxFile.Read.cs b/src/KeePassLib2AndroidSdkStyle/Serialization/KdbxFile.Read.cs new file mode 100644 index 00000000..930b3eaf --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Serialization/KdbxFile.Read.cs @@ -0,0 +1,575 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// #define KDBX_BENCHMARK + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Security; +using System.Text; +using System.Xml; + +#if !KeePassUAP +using System.Security.Cryptography; +#endif + +#if !KeePassLibSD +using System.IO.Compression; +#else +using KeePassLibSD; +#endif + +using KeePassLib.Collections; +using KeePassLib.Cryptography; +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.KeyDerivation; +using KeePassLib.Interfaces; +using KeePassLib.Keys; +using KeePassLib.Resources; +using KeePassLib.Security; +using KeePassLib.Utility; + +using keepass2android; + +namespace KeePassLib.Serialization +{ + /// + /// Serialization to KeePass KDBX files. + /// + public sealed partial class KdbxFile + { + /// + /// Load a KDBX file. + /// + /// File to load. + /// Format. + /// Status logger (optional). + public void Load(string strFilePath, KdbxFormat fmt, IStatusLogger slLogger) + { + IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFilePath); + Load(IOConnection.OpenRead(ioc), fmt, slLogger); + } + + /// + /// Load a KDBX file from a stream. + /// + /// Stream to read the data from. Must contain + /// a KDBX stream. + /// Format. + /// Status logger (optional). + public void Load(Stream sSource, KdbxFormat fmt, IStatusLogger slLogger) + { + Debug.Assert(sSource != null); + if (sSource == null) throw new ArgumentNullException("sSource"); + + if (m_bUsedOnce) + throw new InvalidOperationException("Do not reuse KdbxFile objects!"); + m_bUsedOnce = true; + +#if KDBX_BENCHMARK + Stopwatch swTime = Stopwatch.StartNew(); +#endif + + m_format = fmt; + m_slLogger = slLogger; + + // Other applications might not perform a deduplication + m_pbsBinaries = new ProtectedBinarySet(false); + + UTF8Encoding encNoBom = StrUtil.Utf8; + byte[] pbCipherKey = null; + byte[] pbHmacKey64 = null; + + List lStreams = new List(); + lStreams.Add(sSource); + + HashingStreamEx sHashing = new HashingStreamEx(sSource, false, null); + lStreams.Add(sHashing); + + try + { + Stream sXml; + if (fmt == KdbxFormat.Default) + { + BinaryReaderEx br = new BinaryReaderEx(sHashing, + encNoBom, KLRes.FileCorrupted); + byte[] pbHeader = LoadHeader(br); + m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader); + + int cbEncKey, cbEncIV; + ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV); + + if (m_slLogger != null) + m_slLogger.SetText("KP2AKEY_TransformingKey", LogStatusType.AdditionalInfo); + + ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64); + + string strIncomplete = KLRes.FileHeaderCorrupted + " " + + KLRes.FileIncomplete; + + Stream sPlain; + if (m_uFileVersion < FileVersion32_4) + { + Stream sDecrypted = EncryptStream(sHashing, iCipher, + pbCipherKey, cbEncIV, false); + if ((sDecrypted == null) || (sDecrypted == sHashing)) + throw new SecurityException(KLRes.CryptoStreamFailed); + + if (m_slLogger != null) + m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo); + + lStreams.Add(sDecrypted); + + BinaryReaderEx brDecrypted = new BinaryReaderEx(sDecrypted, + encNoBom, strIncomplete); + byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32); + + if ((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32)) + throw new EndOfStreamException(strIncomplete); + if (!MemUtil.ArraysEqual(pbStoredStartBytes, m_pbStreamStartBytes)) + throw new InvalidCompositeKeyException(); + + if (m_slLogger != null) + m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo); + + sPlain = new HashedBlockStream(sDecrypted, false, 0, !m_bRepairMode); + } + else // KDBX >= 4 + { + byte[] pbStoredHash = MemUtil.Read(sHashing, 32); + if ((pbStoredHash == null) || (pbStoredHash.Length != 32)) + throw new EndOfStreamException(strIncomplete); + if (!MemUtil.ArraysEqual(m_pbHashOfHeader, pbStoredHash)) + throw new InvalidDataException(KLRes.FileHeaderCorrupted); + + byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64); + byte[] pbStoredHmac = MemUtil.Read(sHashing, 32); + if ((pbStoredHmac == null) || (pbStoredHmac.Length != 32)) + throw new EndOfStreamException(strIncomplete); + if (!MemUtil.ArraysEqual(pbHeaderHmac, pbStoredHmac)) + throw new InvalidCompositeKeyException(); + + HmacBlockStream sBlocks = new HmacBlockStream(sHashing, + false, !m_bRepairMode, pbHmacKey64); + lStreams.Add(sBlocks); + + sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey, + cbEncIV, false); + if ((sPlain == null) || (sPlain == sBlocks)) + throw new SecurityException(KLRes.CryptoStreamFailed); + } + + lStreams.Add(sPlain); + + if (m_pwDatabase.Compression == PwCompressionAlgorithm.GZip) + { + sXml = new GZipStream(sPlain, CompressionMode.Decompress); + lStreams.Add(sXml); + } + else sXml = sPlain; + + if (m_uFileVersion >= FileVersion32_4) + LoadInnerHeader(sXml); // Binary header before XML + } + else if (fmt == KdbxFormat.PlainXml) + sXml = sHashing; + else + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("fmt"); + } + + if (fmt == KdbxFormat.Default) + { + if (m_pbInnerRandomStreamKey == null) + { + Debug.Assert(false); + throw new SecurityException("Invalid inner random stream key!"); + } + + m_randomStream = new CryptoRandomStream(m_craInnerRandomStream, + m_pbInnerRandomStreamKey); + } + + if (m_slLogger != null) + m_slLogger.SetText("KP2AKEY_ParsingDatabase", LogStatusType.AdditionalInfo); + +#if KeePassDebug_WriteXml +#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 + var stopWatch = Stopwatch.StartNew(); + + if (fmt == KdbxFormat.ProtocolBuffers) + { + throw new Exception("kdbp is deprecated and was removed to simplify the build process."); + } + else + { + + ReadXmlStreamed(sXml, sHashing); + + Kp2aLog.Log(String.Format("ReadXmlStreamed: {0}ms", stopWatch.ElapsedMilliseconds)); + } + } + // ReadXmlDom(sXml); + catch (CryptographicException) // Thrown on invalid padding + { + throw new CryptographicException(KLRes.FileCorrupted); + } + finally + { + if (pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey); + if (pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64); + + CommonCleanUpRead(lStreams, sHashing); + } + +#if KDBX_BENCHMARK + swTime.Stop(); + MessageService.ShowInfo("Loading KDBX took " + + swTime.ElapsedMilliseconds.ToString() + " ms."); +#endif + } + + private void CommonCleanUpRead(List lStreams, HashingStreamEx sHashing) + { + CloseStreams(lStreams); + + Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed + m_pbHashOfFileOnDisk = sHashing.Hash; + Debug.Assert(m_pbHashOfFileOnDisk != null); + + CleanUpInnerRandomStream(); + + // Reset memory protection settings (to always use reasonable + // defaults) + m_pwDatabase.MemoryProtection = new MemoryProtectionConfig(); + + // Remove old backups (this call is required here in order to apply + // the default history maintenance settings for people upgrading from + // KeePass <= 2.14 to >= 2.15; also it ensures history integrity in + // case a different application has created the KDBX file and ignored + // the history maintenance settings) + m_pwDatabase.MaintainBackups(); // Don't mark database as modified + + // Expand the root group, such that in case the user accidently + // collapses the root group he can simply reopen the database + PwGroup pgRoot = m_pwDatabase.RootGroup; + if (pgRoot != null) pgRoot.IsExpanded = true; + else { Debug.Assert(false); } + + m_pbHashOfHeader = null; + } + + private byte[] LoadHeader(BinaryReaderEx br) + { + string strPrevExcpText = br.ReadExceptionText; + br.ReadExceptionText = KLRes.FileHeaderCorrupted + " " + + KLRes.FileIncompleteExpc; + + MemoryStream msHeader = new MemoryStream(); + Debug.Assert(br.CopyDataTo == null); + br.CopyDataTo = msHeader; + + byte[] pbSig1 = br.ReadBytes(4); + uint uSig1 = MemUtil.BytesToUInt32(pbSig1); + byte[] pbSig2 = br.ReadBytes(4); + uint uSig2 = MemUtil.BytesToUInt32(pbSig2); + + if ((uSig1 == FileSignatureOld1) && (uSig2 == FileSignatureOld2)) + throw new OldFormatException(PwDefs.ShortProductName + @" 1.x", + OldFormatException.OldFormatType.KeePass1x); + + if ((uSig1 == FileSignature1) && (uSig2 == FileSignature2)) { } + else if ((uSig1 == FileSignaturePreRelease1) && (uSig2 == + FileSignaturePreRelease2)) { } + else throw new FormatException(KLRes.FileSigInvalid); + + byte[] pb = br.ReadBytes(4); + uint uVersion = MemUtil.BytesToUInt32(pb); + if ((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask)) + throw new FormatException(KLRes.FileVersionUnsupported + + MessageService.NewParagraph + KLRes.FileNewVerReq); + m_uFileVersion = uVersion; + + while (true) + { + if (!ReadHeaderField(br)) break; + } + + br.CopyDataTo = null; + byte[] pbHeader = msHeader.ToArray(); + msHeader.Close(); + + br.ReadExceptionText = strPrevExcpText; + return pbHeader; + } + + private bool ReadHeaderField(BinaryReaderEx brSource) + { + Debug.Assert(brSource != null); + if (brSource == null) throw new ArgumentNullException("brSource"); + + byte btFieldID = brSource.ReadByte(); + + int cbSize; + Debug.Assert(m_uFileVersion > 0); + if (m_uFileVersion < FileVersion32_4) + cbSize = (int)MemUtil.BytesToUInt16(brSource.ReadBytes(2)); + else cbSize = MemUtil.BytesToInt32(brSource.ReadBytes(4)); + if (cbSize < 0) throw new FormatException(KLRes.FileCorrupted); + + byte[] pbData = MemUtil.EmptyByteArray; + if (cbSize > 0) pbData = brSource.ReadBytes(cbSize); + + bool bResult = true; + KdbxHeaderFieldID kdbID = (KdbxHeaderFieldID)btFieldID; + switch (kdbID) + { + case KdbxHeaderFieldID.EndOfHeader: + bResult = false; // Returning false indicates end of header + break; + + case KdbxHeaderFieldID.CipherID: + SetCipher(pbData); + break; + + case KdbxHeaderFieldID.CompressionFlags: + SetCompressionFlags(pbData); + break; + + case KdbxHeaderFieldID.MasterSeed: + m_pbMasterSeed = pbData; + CryptoRandom.Instance.AddEntropy(pbData); + break; + + // Obsolete; for backward compatibility only + case KdbxHeaderFieldID.TransformSeed: + Debug.Assert(m_uFileVersion < FileVersion32_4); + + AesKdf kdfS = new AesKdf(); + if (!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfS.Uuid)) + m_pwDatabase.KdfParameters = kdfS.GetDefaultParameters(); + + // m_pbTransformSeed = pbData; + m_pwDatabase.KdfParameters.SetByteArray(AesKdf.ParamSeed, pbData); + + CryptoRandom.Instance.AddEntropy(pbData); + break; + + // Obsolete; for backward compatibility only + case KdbxHeaderFieldID.TransformRounds: + Debug.Assert(m_uFileVersion < FileVersion32_4); + + AesKdf kdfR = new AesKdf(); + if (!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfR.Uuid)) + m_pwDatabase.KdfParameters = kdfR.GetDefaultParameters(); + + // m_pwDatabase.KeyEncryptionRounds = MemUtil.BytesToUInt64(pbData); + m_pwDatabase.KdfParameters.SetUInt64(AesKdf.ParamRounds, + MemUtil.BytesToUInt64(pbData)); + break; + + case KdbxHeaderFieldID.EncryptionIV: + m_pbEncryptionIV = pbData; + break; + + case KdbxHeaderFieldID.InnerRandomStreamKey: + Debug.Assert(m_uFileVersion < FileVersion32_4); + Debug.Assert(m_pbInnerRandomStreamKey == null); + m_pbInnerRandomStreamKey = pbData; + CryptoRandom.Instance.AddEntropy(pbData); + break; + + case KdbxHeaderFieldID.StreamStartBytes: + Debug.Assert(m_uFileVersion < FileVersion32_4); + m_pbStreamStartBytes = pbData; + break; + + case KdbxHeaderFieldID.InnerRandomStreamID: + Debug.Assert(m_uFileVersion < FileVersion32_4); + SetInnerRandomStreamID(pbData); + break; + + case KdbxHeaderFieldID.KdfParameters: + m_pwDatabase.KdfParameters = KdfParameters.DeserializeExt(pbData); + break; + + case KdbxHeaderFieldID.PublicCustomData: + Debug.Assert(m_pwDatabase.PublicCustomData.Count == 0); + m_pwDatabase.PublicCustomData = VariantDictionary.Deserialize(pbData); + break; + + default: + Debug.Assert(false); + if (m_slLogger != null) + m_slLogger.SetText(KLRes.UnknownHeaderId + ": " + + kdbID.ToString() + "!", LogStatusType.Warning); + break; + } + + return bResult; + } + + private void LoadInnerHeader(Stream s) + { + BinaryReaderEx br = new BinaryReaderEx(s, StrUtil.Utf8, + KLRes.FileCorrupted + " " + KLRes.FileIncompleteExpc); + + while (true) + { + if (!ReadInnerHeaderField(br)) break; + } + } + + private bool ReadInnerHeaderField(BinaryReaderEx br) + { + Debug.Assert(br != null); + if (br == null) throw new ArgumentNullException("br"); + + byte btFieldID = br.ReadByte(); + + int cbSize = MemUtil.BytesToInt32(br.ReadBytes(4)); + if (cbSize < 0) throw new FormatException(KLRes.FileCorrupted); + + byte[] pbData = MemUtil.EmptyByteArray; + if (cbSize > 0) pbData = br.ReadBytes(cbSize); + + bool bResult = true; + KdbxInnerHeaderFieldID kdbID = (KdbxInnerHeaderFieldID)btFieldID; + switch (kdbID) + { + case KdbxInnerHeaderFieldID.EndOfHeader: + bResult = false; // Returning false indicates end of header + break; + + case KdbxInnerHeaderFieldID.InnerRandomStreamID: + SetInnerRandomStreamID(pbData); + break; + + case KdbxInnerHeaderFieldID.InnerRandomStreamKey: + Debug.Assert(m_pbInnerRandomStreamKey == null); + m_pbInnerRandomStreamKey = pbData; + CryptoRandom.Instance.AddEntropy(pbData); + break; + + case KdbxInnerHeaderFieldID.Binary: + if (pbData.Length < 1) throw new FormatException(); + KdbxBinaryFlags f = (KdbxBinaryFlags)pbData[0]; + bool bProt = ((f & KdbxBinaryFlags.Protected) != KdbxBinaryFlags.None); + + ProtectedBinary pb = new ProtectedBinary(bProt, pbData, + 1, pbData.Length - 1); + Debug.Assert(m_pbsBinaries.Find(pb) < 0); // No deduplication? + m_pbsBinaries.Add(pb); + + if (bProt) MemUtil.ZeroByteArray(pbData); + break; + + default: + Debug.Assert(false); + break; + } + + return bResult; + } + + private void SetCipher(byte[] pbID) + { + if ((pbID == null) || (pbID.Length != (int)PwUuid.UuidSize)) + throw new FormatException(KLRes.FileUnknownCipher); + + m_pwDatabase.DataCipherUuid = new PwUuid(pbID); + } + + private void SetCompressionFlags(byte[] pbFlags) + { + int nID = (int)MemUtil.BytesToUInt32(pbFlags); + if ((nID < 0) || (nID >= (int)PwCompressionAlgorithm.Count)) + throw new FormatException(KLRes.FileUnknownCompression); + + m_pwDatabase.Compression = (PwCompressionAlgorithm)nID; + } + + private void SetInnerRandomStreamID(byte[] pbID) + { + uint uID = MemUtil.BytesToUInt32(pbID); + if (uID >= (uint)CrsAlgorithm.Count) + throw new FormatException(KLRes.FileUnknownCipher); + + m_craInnerRandomStream = (CrsAlgorithm)uID; + } + + internal static PwGroup ReadGroup(Stream msData, PwDatabase pdContext, + bool bCopyIcons, bool bNewUuids, bool bSetCreatedNow) + { + PwDatabase pd = new PwDatabase(); + pd.New(new IOConnectionInfo(), new CompositeKey(), ""); + + KdbxFile f = new KdbxFile(pd); + f.Load(msData, KdbxFormat.PlainXml, null); + + if (bCopyIcons) + PwDatabase.CopyCustomIcons(pd, pdContext, pd.RootGroup, true); + + if (bNewUuids) + { + pd.RootGroup.Uuid = new PwUuid(true); + pd.RootGroup.CreateNewItemUuids(true, true, true); + } + + if (bSetCreatedNow) pd.RootGroup.SetCreatedNow(true); + + return pd.RootGroup; + } + + [Obsolete] + public static List ReadEntries(Stream msData) + { + return ReadEntries(msData, null, false); + } + + [Obsolete] + public static List ReadEntries(PwDatabase pdContext, Stream msData) + { + return ReadEntries(msData, pdContext, true); + } + + public static List ReadEntries(Stream msData, PwDatabase pdContext, + bool bCopyIcons) + { + if (msData == null) { Debug.Assert(false); return new List(); } + + PwGroup pg = ReadGroup(msData, pdContext, bCopyIcons, true, true); + return pg.GetEntries(true).CloneShallowToList(); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Serialization/KdbxFile.Write.cs b/src/KeePassLib2AndroidSdkStyle/Serialization/KdbxFile.Write.cs new file mode 100644 index 00000000..077831fd --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Serialization/KdbxFile.Write.cs @@ -0,0 +1,1089 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Security; +using System.Text; +using System.Xml; +using keepass2android; +#if !KeePassUAP +using System.Drawing; +using System.Security.Cryptography; +#endif + +#if KeePassLibSD +using KeePassLibSD; +#else +using System.IO.Compression; +#endif + +using KeePassLib.Collections; +using KeePassLib.Cryptography; +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.KeyDerivation; +using KeePassLib.Delegates; +using KeePassLib.Interfaces; +using KeePassLib.Keys; +using KeePassLib.Resources; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + /// + /// Serialization to KeePass KDBX files. + /// + public sealed partial class KdbxFile + { + // public void Save(string strFile, PwGroup pgDataSource, KdbxFormat fmt, + // IStatusLogger slLogger) + // { + // bool bMadeUnhidden = UrlUtil.UnhideFile(strFile); + // + // IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFile); + // this.Save(IOConnection.OpenWrite(ioc), pgDataSource, format, slLogger); + // + // if(bMadeUnhidden) UrlUtil.HideFile(strFile, true); // Hide again + // } + + /// + /// Save the contents of the current PwDatabase to a KDBX file. + /// + /// Stream to write the KDBX file into. + /// Group containing all groups and + /// entries to write. If null, the complete database will + /// be written. + /// Format of the file to create. + /// Logger that recieves status information. + public void Save(Stream sSaveTo, PwGroup pgDataSource, KdbxFormat fmt, + IStatusLogger slLogger) + { + Debug.Assert(sSaveTo != null); + if (sSaveTo == null) throw new ArgumentNullException("sSaveTo"); + + if (m_bUsedOnce) + throw new InvalidOperationException("Do not reuse KdbxFile objects!"); + m_bUsedOnce = true; + + m_format = fmt; + m_slLogger = slLogger; + m_xmlWriter = null; + + PwGroup pgRoot = (pgDataSource ?? m_pwDatabase.RootGroup); + UTF8Encoding encNoBom = StrUtil.Utf8; + CryptoRandom cr = CryptoRandom.Instance; + byte[] pbCipherKey = null; + byte[] pbHmacKey64 = null; + + m_pbsBinaries = new ProtectedBinarySet(true); + m_pbsBinaries.AddFrom(pgRoot); + + List lStreams = new List(); + lStreams.Add(sSaveTo); + + HashingStreamEx sHashing = new HashingStreamEx(sSaveTo, true, null); + lStreams.Add(sHashing); + + try + { + // Fix history entries (should not be necessary; just for safety, + // as e.g. XPath searches depend on correct history entry UUIDs) + if (m_pwDatabase.MaintainBackups()) { Debug.Assert(false); } + + m_uFileVersion = GetMinKdbxVersion(); + + int cbEncKey, cbEncIV; + ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV); + + m_pbMasterSeed = cr.GetRandomBytes(32); + m_pbEncryptionIV = cr.GetRandomBytes((uint)cbEncIV); + + // m_pbTransformSeed = cr.GetRandomBytes(32); + PwUuid puKdf = m_pwDatabase.KdfParameters.KdfUuid; + KdfEngine kdf = KdfPool.Get(puKdf); + if (kdf == null) + throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph + + // KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph + + "UUID: " + puKdf.ToHexString() + "."); + kdf.Randomize(m_pwDatabase.KdfParameters); + + if (m_format == KdbxFormat.Default) + { + if (m_uFileVersion < FileVersion32_4) + { + m_craInnerRandomStream = CrsAlgorithm.Salsa20; + m_pbInnerRandomStreamKey = cr.GetRandomBytes(32); + } + else // KDBX >= 4 + { + m_craInnerRandomStream = CrsAlgorithm.ChaCha20; + m_pbInnerRandomStreamKey = cr.GetRandomBytes(64); + } + + m_randomStream = new CryptoRandomStream(m_craInnerRandomStream, + m_pbInnerRandomStreamKey); + } + + if (m_uFileVersion < FileVersion32_4) + m_pbStreamStartBytes = cr.GetRandomBytes(32); + + Stream sXml; + if (m_format == KdbxFormat.Default || m_format == KdbxFormat.ProtocolBuffers) + { + byte[] pbHeader = GenerateHeader(); + m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader); + + MemUtil.Write(sHashing, pbHeader); + sHashing.Flush(); + + ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64); + + Stream sPlain; + if (m_uFileVersion < FileVersion32_4) + { + Stream sEncrypted = EncryptStream(sHashing, iCipher, + pbCipherKey, cbEncIV, true); + if ((sEncrypted == null) || (sEncrypted == sHashing)) + throw new SecurityException(KLRes.CryptoStreamFailed); + lStreams.Add(sEncrypted); + + MemUtil.Write(sEncrypted, m_pbStreamStartBytes); + + sPlain = new HashedBlockStream(sEncrypted, true); + } + else // KDBX >= 4 + { + // For integrity checking (without knowing the master key) + MemUtil.Write(sHashing, m_pbHashOfHeader); + + byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64); + MemUtil.Write(sHashing, pbHeaderHmac); + + Stream sBlocks = new HmacBlockStream(sHashing, true, + true, pbHmacKey64); + lStreams.Add(sBlocks); + + sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey, + cbEncIV, true); + if ((sPlain == null) || (sPlain == sBlocks)) + throw new SecurityException(KLRes.CryptoStreamFailed); + } + lStreams.Add(sPlain); + + if (m_pwDatabase.Compression == PwCompressionAlgorithm.GZip) + { + sXml = new GZipStream(sPlain, CompressionMode.Compress); + lStreams.Add(sXml); + } + else sXml = sPlain; + + if (m_uFileVersion >= FileVersion32_4) + WriteInnerHeader(sXml); // Binary header before XML + } + else if (m_format == KdbxFormat.PlainXml) + sXml = sHashing; + else + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("fmt"); + } + + var stopWatch = Stopwatch.StartNew(); + + +#if KeePassUAP + XmlWriterSettings xws = new XmlWriterSettings(); + xws.Encoding = encNoBom; + xws.Indent = true; + xws.IndentChars = "\t"; + xws.NewLineOnAttributes = false; + + XmlWriter xw = XmlWriter.Create(sXml, xws); +#else + XmlTextWriter xw = new XmlTextWriter(sXml, encNoBom); + + xw.Formatting = Formatting.Indented; + xw.IndentChar = '\t'; + xw.Indentation = 1; +#endif + m_xmlWriter = xw; + + WriteDocument(pgRoot); + + m_xmlWriter.Flush(); + m_xmlWriter.Close(); + + Kp2aLog.Log(String.Format("{1}: {0}ms", stopWatch.ElapsedMilliseconds, m_format == KdbxFormat.ProtocolBuffers ? "KdbpFile.WriteDocument" : "Xml WriteDocument")); + + } + finally + { + if(pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey); + if(pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64); + + CommonCleanUpWrite(lStreams, sHashing); + } + + } + + private void CommonCleanUpWrite(List lStreams, HashingStreamEx sHashing) + { + if (m_xmlWriter != null) { m_xmlWriter.Close(); m_xmlWriter = null; } + + CloseStreams(lStreams); + + Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed + m_pbHashOfFileOnDisk = sHashing.Hash; + Debug.Assert(m_pbHashOfFileOnDisk != null); + + CleanUpInnerRandomStream(); + + m_pbHashOfHeader = null; + } + + private byte[] GenerateHeader() + { + byte[] pbHeader; + using (MemoryStream ms = new MemoryStream()) + { + MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature1)); + MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2)); + MemUtil.Write(ms, MemUtil.UInt32ToBytes(m_uFileVersion)); + + WriteHeaderField(ms, KdbxHeaderFieldID.CipherID, + m_pwDatabase.DataCipherUuid.UuidBytes); + + int nCprID = (int)m_pwDatabase.Compression; + WriteHeaderField(ms, KdbxHeaderFieldID.CompressionFlags, + MemUtil.UInt32ToBytes((uint)nCprID)); + + WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed); + + if (m_uFileVersion < FileVersion32_4) + { + Debug.Assert(m_pwDatabase.KdfParameters.KdfUuid.Equals( + (new AesKdf()).Uuid)); + WriteHeaderField(ms, KdbxHeaderFieldID.TransformSeed, + m_pwDatabase.KdfParameters.GetByteArray(AesKdf.ParamSeed)); + WriteHeaderField(ms, KdbxHeaderFieldID.TransformRounds, + MemUtil.UInt64ToBytes(m_pwDatabase.KdfParameters.GetUInt64( + AesKdf.ParamRounds, PwDefs.DefaultKeyEncryptionRounds))); + } + else + WriteHeaderField(ms, KdbxHeaderFieldID.KdfParameters, + KdfParameters.SerializeExt(m_pwDatabase.KdfParameters)); + + if (m_pbEncryptionIV.Length > 0) + WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV); + + if (m_uFileVersion < FileVersion32_4) + { + WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamKey, + m_pbInnerRandomStreamKey); + + WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes, + m_pbStreamStartBytes); + + int nIrsID = (int)m_craInnerRandomStream; + WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID, + MemUtil.Int32ToBytes(nIrsID)); + } + + // Write public custom data only when there is at least one item, + // because KDBX 3.1 didn't support this field yet + if (m_pwDatabase.PublicCustomData.Count > 0) + WriteHeaderField(ms, KdbxHeaderFieldID.PublicCustomData, + VariantDictionary.Serialize(m_pwDatabase.PublicCustomData)); + + WriteHeaderField(ms, KdbxHeaderFieldID.EndOfHeader, new byte[] { + (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' }); + + pbHeader = ms.ToArray(); + } + + return pbHeader; + } + + private void WriteHeaderField(Stream s, KdbxHeaderFieldID kdbID, + byte[] pbData) + { + s.WriteByte((byte)kdbID); + + byte[] pb = (pbData ?? MemUtil.EmptyByteArray); + int cb = pb.Length; + if (cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); } + + Debug.Assert(m_uFileVersion > 0); + if (m_uFileVersion < FileVersion32_4) + { + if (cb > (int)ushort.MaxValue) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("pbData"); + } + + MemUtil.Write(s, MemUtil.UInt16ToBytes((ushort)cb)); + } + else MemUtil.Write(s, MemUtil.Int32ToBytes(cb)); + + MemUtil.Write(s, pb); + } + + private void WriteInnerHeader(Stream s) + { + int nIrsID = (int)m_craInnerRandomStream; + WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.InnerRandomStreamID, + MemUtil.Int32ToBytes(nIrsID), null); + + WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.InnerRandomStreamKey, + m_pbInnerRandomStreamKey, null); + + ProtectedBinary[] vBin = m_pbsBinaries.ToArray(); + for (int i = 0; i < vBin.Length; ++i) + { + ProtectedBinary pb = vBin[i]; + if (pb == null) throw new InvalidOperationException(); + + KdbxBinaryFlags f = KdbxBinaryFlags.None; + if (pb.IsProtected) f |= KdbxBinaryFlags.Protected; + + byte[] pbFlags = new byte[1] { (byte)f }; + byte[] pbData = pb.ReadData(); + + WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.Binary, + pbFlags, pbData); + + if (pb.IsProtected) MemUtil.ZeroByteArray(pbData); + } + + WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.EndOfHeader, + null, null); + } + + private void WriteInnerHeaderField(Stream s, KdbxInnerHeaderFieldID kdbID, + byte[] pbData1, byte[] pbData2) + { + s.WriteByte((byte)kdbID); + + byte[] pb1 = (pbData1 ?? MemUtil.EmptyByteArray); + byte[] pb2 = (pbData2 ?? MemUtil.EmptyByteArray); + + int cb = pb1.Length + pb2.Length; + if (cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); } + + MemUtil.Write(s, MemUtil.Int32ToBytes(cb)); + MemUtil.Write(s, pb1); + MemUtil.Write(s, pb2); + } + + private void WriteDocument(PwGroup pgRoot) + { + Debug.Assert(m_xmlWriter != null); + if (m_xmlWriter == null) throw new InvalidOperationException(); + + uint uNumGroups, uNumEntries, uCurEntry = 0; + pgRoot.GetCounts(true, out uNumGroups, out uNumEntries); + + m_xmlWriter.WriteStartDocument(true); + m_xmlWriter.WriteStartElement(ElemDocNode); + + WriteMeta(); + + m_xmlWriter.WriteStartElement(ElemRoot); + StartGroup(pgRoot); + + Stack groupStack = new Stack(); + groupStack.Push(pgRoot); + + GroupHandler gh = delegate (PwGroup pg) + { + Debug.Assert(pg != null); + if (pg == null) throw new ArgumentNullException("pg"); + + while (true) + { + if (pg.ParentGroup == groupStack.Peek()) + { + groupStack.Push(pg); + StartGroup(pg); + break; + } + else + { + groupStack.Pop(); + if (groupStack.Count <= 0) return false; + + EndGroup(); + } + } + + return true; + }; + + EntryHandler eh = delegate (PwEntry pe) + { + Debug.Assert(pe != null); + WriteEntry(pe, false); + + ++uCurEntry; + if (m_slLogger != null) + { + if (!m_slLogger.SetProgress((100 * uCurEntry) / uNumEntries)) + return false; + } + + return true; + }; + + if (!pgRoot.TraverseTree(TraversalMethod.PreOrder, gh, eh)) + throw new OperationCanceledException(); + + while (groupStack.Count > 1) + { + m_xmlWriter.WriteEndElement(); + groupStack.Pop(); + } + + EndGroup(); + + WriteList(ElemDeletedObjects, m_pwDatabase.DeletedObjects); + m_xmlWriter.WriteEndElement(); // Root + + m_xmlWriter.WriteEndElement(); // ElemDocNode + m_xmlWriter.WriteEndDocument(); + } + + private void WriteMeta() + { + m_xmlWriter.WriteStartElement(ElemMeta); + + WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false); + + if ((m_pbHashOfHeader != null) && (m_uFileVersion < FileVersion32_4)) + WriteObject(ElemHeaderHash, Convert.ToBase64String( + m_pbHashOfHeader), false); + + if (m_uFileVersion >= FileVersion32_4) + WriteObject(ElemSettingsChanged, m_pwDatabase.SettingsChanged); + + WriteObject(ElemDbName, m_pwDatabase.Name, true); + WriteObject(ElemDbNameChanged, m_pwDatabase.NameChanged); + WriteObject(ElemDbDesc, m_pwDatabase.Description, true); + WriteObject(ElemDbDescChanged, m_pwDatabase.DescriptionChanged); + WriteObject(ElemDbDefaultUser, m_pwDatabase.DefaultUserName, true); + WriteObject(ElemDbDefaultUserChanged, m_pwDatabase.DefaultUserNameChanged); + WriteObject(ElemDbMntncHistoryDays, m_pwDatabase.MaintenanceHistoryDays); + WriteObject(ElemDbColor, StrUtil.ColorToUnnamedHtml(m_pwDatabase.Color, true), false); + WriteObject(ElemDbKeyChanged, m_pwDatabase.MasterKeyChanged); + WriteObject(ElemDbKeyChangeRec, m_pwDatabase.MasterKeyChangeRec); + WriteObject(ElemDbKeyChangeForce, m_pwDatabase.MasterKeyChangeForce); + if (m_pwDatabase.MasterKeyChangeForceOnce) + WriteObject(ElemDbKeyChangeForceOnce, true); + + WriteList(ElemMemoryProt, m_pwDatabase.MemoryProtection); + + WriteCustomIconList(); + + WriteObject(ElemRecycleBinEnabled, m_pwDatabase.RecycleBinEnabled); + WriteObject(ElemRecycleBinUuid, m_pwDatabase.RecycleBinUuid); + WriteObject(ElemRecycleBinChanged, m_pwDatabase.RecycleBinChanged); + WriteObject(ElemEntryTemplatesGroup, m_pwDatabase.EntryTemplatesGroup); + WriteObject(ElemEntryTemplatesGroupChanged, m_pwDatabase.EntryTemplatesGroupChanged); + WriteObject(ElemHistoryMaxItems, m_pwDatabase.HistoryMaxItems); + WriteObject(ElemHistoryMaxSize, m_pwDatabase.HistoryMaxSize); + + WriteObject(ElemLastSelectedGroup, m_pwDatabase.LastSelectedGroup); + WriteObject(ElemLastTopVisibleGroup, m_pwDatabase.LastTopVisibleGroup); + + if ((m_format != KdbxFormat.Default) || (m_uFileVersion < FileVersion32_4)) + WriteBinPool(); + + WriteList(ElemCustomData, m_pwDatabase.CustomData); + + m_xmlWriter.WriteEndElement(); + } + + private void StartGroup(PwGroup pg) + { + m_xmlWriter.WriteStartElement(ElemGroup); + WriteObject(ElemUuid, pg.Uuid); + WriteObject(ElemName, pg.Name, true); + WriteObject(ElemNotes, pg.Notes, true); + WriteObject(ElemIcon, (int)pg.IconId); + + if (!pg.CustomIconUuid.Equals(PwUuid.Zero)) + WriteObject(ElemCustomIconID, pg.CustomIconUuid); + + WriteList(ElemTimes, pg); + WriteObject(ElemIsExpanded, pg.IsExpanded); + WriteObject(ElemGroupDefaultAutoTypeSeq, pg.DefaultAutoTypeSequence, true); + WriteObject(ElemEnableAutoType, StrUtil.BoolToStringEx(pg.EnableAutoType), false); + WriteObject(ElemEnableSearching, StrUtil.BoolToStringEx(pg.EnableSearching), false); + WriteObject(ElemLastTopVisibleEntry, pg.LastTopVisibleEntry); + + if (m_uFileVersion >= FileVersion32_4_1) + { + if (!pg.PreviousParentGroup.Equals(PwUuid.Zero)) + WriteObject(ElemPreviousParentGroup, pg.PreviousParentGroup); + + List lTags = pg.Tags; + if (lTags.Count != 0) + WriteObject(ElemTags, StrUtil.TagsToString(lTags, false), true); + } + + if (pg.CustomData.Count > 0) + WriteList(ElemCustomData, pg.CustomData); + } + + private void EndGroup() + { + m_xmlWriter.WriteEndElement(); // Close group element + } + + private void WriteEntry(PwEntry pe, bool bIsHistory) + { + Debug.Assert(pe != null); if (pe == null) throw new ArgumentNullException("pe"); + + m_xmlWriter.WriteStartElement(ElemEntry); + + WriteObject(ElemUuid, pe.Uuid); + WriteObject(ElemIcon, (int)pe.IconId); + + if (!pe.CustomIconUuid.Equals(PwUuid.Zero)) + WriteObject(ElemCustomIconID, pe.CustomIconUuid); + + WriteObject(ElemFgColor, StrUtil.ColorToUnnamedHtml(pe.ForegroundColor, true), false); + WriteObject(ElemBgColor, StrUtil.ColorToUnnamedHtml(pe.BackgroundColor, true), false); + WriteObject(ElemOverrideUrl, pe.OverrideUrl, true); + + if ((m_uFileVersion >= FileVersion32_4_1) && !pe.QualityCheck) + WriteObject(ElemQualityCheck, false); + + WriteObject(ElemTags, StrUtil.TagsToString(pe.Tags, false), true); + + if ((m_uFileVersion >= FileVersion32_4_1) && + !pe.PreviousParentGroup.Equals(PwUuid.Zero)) + WriteObject(ElemPreviousParentGroup, pe.PreviousParentGroup); + + WriteList(ElemTimes, pe); + + WriteList(pe.Strings, true); + WriteList(pe.Binaries); + WriteList(ElemAutoType, pe.AutoType); + + if (pe.CustomData.Count > 0) + WriteList(ElemCustomData, pe.CustomData); + + if (!bIsHistory) WriteList(ElemHistory, pe.History, true); + else { Debug.Assert(pe.History.UCount == 0); } + + m_xmlWriter.WriteEndElement(); + } + + private void WriteList(ProtectedStringDictionary dictStrings, bool bEntryStrings) + { + Debug.Assert(dictStrings != null); + if (dictStrings == null) throw new ArgumentNullException("dictStrings"); + + foreach (KeyValuePair kvp in dictStrings) + WriteObject(kvp.Key, kvp.Value, bEntryStrings); + } + + private void WriteList(ProtectedBinaryDictionary dictBinaries) + { + Debug.Assert(dictBinaries != null); + if (dictBinaries == null) throw new ArgumentNullException("dictBinaries"); + + foreach (KeyValuePair kvp in dictBinaries) + WriteObject(kvp.Key, kvp.Value, true); + } + + private void WriteList(string name, AutoTypeConfig cfgAutoType) + { + Debug.Assert(name != null); + Debug.Assert(cfgAutoType != null); + if (cfgAutoType == null) throw new ArgumentNullException("cfgAutoType"); + + m_xmlWriter.WriteStartElement(name); + + WriteObject(ElemAutoTypeEnabled, cfgAutoType.Enabled); + WriteObject(ElemAutoTypeObfuscation, (int)cfgAutoType.ObfuscationOptions); + + if (cfgAutoType.DefaultSequence.Length > 0) + WriteObject(ElemAutoTypeDefaultSeq, cfgAutoType.DefaultSequence, true); + + foreach (AutoTypeAssociation a in cfgAutoType.Associations) + WriteObject(ElemAutoTypeItem, ElemWindow, ElemKeystrokeSequence, + new KeyValuePair(a.WindowName, a.Sequence), null); + + m_xmlWriter.WriteEndElement(); + } + + private void WriteList(string name, ITimeLogger times) + { + Debug.Assert(name != null); + Debug.Assert(times != null); if (times == null) throw new ArgumentNullException("times"); + + m_xmlWriter.WriteStartElement(name); + + WriteObject(ElemCreationTime, times.CreationTime); + WriteObject(ElemLastModTime, times.LastModificationTime); + WriteObject(ElemLastAccessTime, times.LastAccessTime); + WriteObject(ElemExpiryTime, times.ExpiryTime); + WriteObject(ElemExpires, times.Expires); + WriteObject(ElemUsageCount, times.UsageCount); + WriteObject(ElemLocationChanged, times.LocationChanged); + + m_xmlWriter.WriteEndElement(); + } + + private void WriteList(string name, PwObjectList value, bool bIsHistory) + { + Debug.Assert(name != null); + Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value"); + + m_xmlWriter.WriteStartElement(name); + + foreach (PwEntry pe in value) + WriteEntry(pe, bIsHistory); + + m_xmlWriter.WriteEndElement(); + } + + private void WriteList(string name, PwObjectList value) + { + Debug.Assert(name != null); + Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value"); + + m_xmlWriter.WriteStartElement(name); + + foreach (PwDeletedObject pdo in value) + WriteObject(ElemDeletedObject, pdo); + + m_xmlWriter.WriteEndElement(); + } + + private void WriteList(string name, MemoryProtectionConfig value) + { + Debug.Assert(name != null); + Debug.Assert(value != null); + + m_xmlWriter.WriteStartElement(name); + + WriteObject(ElemProtTitle, value.ProtectTitle); + WriteObject(ElemProtUserName, value.ProtectUserName); + WriteObject(ElemProtPassword, value.ProtectPassword); + WriteObject(ElemProtUrl, value.ProtectUrl); + WriteObject(ElemProtNotes, value.ProtectNotes); + // WriteObject(ElemProtAutoHide, value.AutoEnableVisualHiding); + + m_xmlWriter.WriteEndElement(); + } + + private void WriteList(string name, StringDictionaryEx value) + { + Debug.Assert(name != null); + Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value"); + + m_xmlWriter.WriteStartElement(name); + + foreach (KeyValuePair kvp in value) + { + DateTime? odtLastMod = null; + if (m_uFileVersion >= FileVersion32_4_1) + odtLastMod = value.GetLastModificationTime(kvp.Key); + + WriteObject(ElemStringDictExItem, ElemKey, ElemValue, kvp, odtLastMod); + } + + m_xmlWriter.WriteEndElement(); + } + + private void WriteCustomIconList() + { + if (m_pwDatabase.CustomIcons.Count == 0) return; + + m_xmlWriter.WriteStartElement(ElemCustomIcons); + + foreach (PwCustomIcon ci in m_pwDatabase.CustomIcons) + { + m_xmlWriter.WriteStartElement(ElemCustomIconItem); + + WriteObject(ElemCustomIconItemID, ci.Uuid); + + string strData = Convert.ToBase64String(ci.ImageDataPng); + WriteObject(ElemCustomIconItemData, strData, false); + + if (m_uFileVersion >= FileVersion32_4_1) + { + if (ci.Name.Length != 0) + WriteObject(ElemName, ci.Name, true); + if (ci.LastModificationTime.HasValue) + WriteObject(ElemLastModTime, ci.LastModificationTime.Value); + } + + m_xmlWriter.WriteEndElement(); + } + + m_xmlWriter.WriteEndElement(); + } + + private void WriteObject(string name, string value, + bool bFilterValueXmlChars) + { + Debug.Assert(name != null); + Debug.Assert(value != null); + + m_xmlWriter.WriteStartElement(name); + + if (bFilterValueXmlChars) + m_xmlWriter.WriteString(StrUtil.SafeXmlString(value)); + else m_xmlWriter.WriteString(value); + + m_xmlWriter.WriteEndElement(); + } + + private void WriteObject(string name, bool value) + { + Debug.Assert(name != null); + + WriteObject(name, value ? ValTrue : ValFalse, false); + } + + private void WriteObject(string name, PwUuid value) + { + Debug.Assert(name != null); + Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value"); + + WriteObject(name, Convert.ToBase64String(value.UuidBytes), false); + } + + private void WriteObject(string name, int value) + { + Debug.Assert(name != null); + + m_xmlWriter.WriteStartElement(name); + m_xmlWriter.WriteString(value.ToString(NumberFormatInfo.InvariantInfo)); + m_xmlWriter.WriteEndElement(); + } + + private void WriteObject(string name, uint value) + { + Debug.Assert(name != null); + + m_xmlWriter.WriteStartElement(name); + m_xmlWriter.WriteString(value.ToString(NumberFormatInfo.InvariantInfo)); + m_xmlWriter.WriteEndElement(); + } + + private void WriteObject(string name, long value) + { + Debug.Assert(name != null); + + m_xmlWriter.WriteStartElement(name); + m_xmlWriter.WriteString(value.ToString(NumberFormatInfo.InvariantInfo)); + m_xmlWriter.WriteEndElement(); + } + + private void WriteObject(string name, ulong value) + { + Debug.Assert(name != null); + + m_xmlWriter.WriteStartElement(name); + m_xmlWriter.WriteString(value.ToString(NumberFormatInfo.InvariantInfo)); + m_xmlWriter.WriteEndElement(); + } + + private void WriteObject(string name, DateTime value) + { + Debug.Assert(name != null); + Debug.Assert(value.Kind == DateTimeKind.Utc); + + // Cf. ReadTime + if ((m_format == KdbxFormat.Default) && (m_uFileVersion >= FileVersion32_4)) + { + DateTime dt = TimeUtil.ToUtc(value, false); + + // DateTime dtBase = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); + // dt -= new TimeSpan(dtBase.Ticks); + + // WriteObject(name, dt.ToBinary()); + + // dt = TimeUtil.RoundToMultOf2PowLess1s(dt); + // long lBin = dt.ToBinary(); + + long lSec = dt.Ticks / TimeSpan.TicksPerSecond; + // WriteObject(name, lSec); + + byte[] pb = MemUtil.Int64ToBytes(lSec); + WriteObject(name, Convert.ToBase64String(pb), false); + } + else WriteObject(name, TimeUtil.SerializeUtc(value), false); + } + + private void WriteObject(string name, string strKeyName, string strValueName, + KeyValuePair kvp, DateTime? odtLastMod) + { + m_xmlWriter.WriteStartElement(name); + + m_xmlWriter.WriteStartElement(strKeyName); + m_xmlWriter.WriteString(StrUtil.SafeXmlString(kvp.Key)); + m_xmlWriter.WriteEndElement(); + + m_xmlWriter.WriteStartElement(strValueName); + m_xmlWriter.WriteString(StrUtil.SafeXmlString(kvp.Value)); + m_xmlWriter.WriteEndElement(); + + if (odtLastMod.HasValue) + WriteObject(ElemLastModTime, odtLastMod.Value); + + m_xmlWriter.WriteEndElement(); + } + + private void WriteObject(string name, ProtectedString value, bool bIsEntryString) + { + Debug.Assert(name != null); + Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value"); + + m_xmlWriter.WriteStartElement(ElemString); + m_xmlWriter.WriteStartElement(ElemKey); + m_xmlWriter.WriteString(StrUtil.SafeXmlString(name)); + m_xmlWriter.WriteEndElement(); + m_xmlWriter.WriteStartElement(ElemValue); + + bool bProtected = value.IsProtected; + if (bIsEntryString) + { + // Adjust memory protection setting (which might be different + // from the database default, e.g. due to an import which + // didn't specify the correct setting) + if (name == PwDefs.TitleField) + bProtected = m_pwDatabase.MemoryProtection.ProtectTitle; + else if (name == PwDefs.UserNameField) + bProtected = m_pwDatabase.MemoryProtection.ProtectUserName; + else if (name == PwDefs.PasswordField) + bProtected = m_pwDatabase.MemoryProtection.ProtectPassword; + else if (name == PwDefs.UrlField) + bProtected = m_pwDatabase.MemoryProtection.ProtectUrl; + else if (name == PwDefs.NotesField) + bProtected = m_pwDatabase.MemoryProtection.ProtectNotes; + } + + if (bProtected && (m_format == KdbxFormat.Default)) + { + m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue); + + byte[] pbEnc = value.ReadXorredString(m_randomStream); + if (pbEnc.Length > 0) + m_xmlWriter.WriteBase64(pbEnc, 0, pbEnc.Length); + } + else + { + string strValue = value.ReadString(); + + // If names should be localized, we need to apply the language-dependent + // string transformation here. By default, language-dependent conversions + // should be applied, otherwise characters could be rendered incorrectly + // (code page problems). + if (g_bLocalizedNames) + { + StringBuilder sb = new StringBuilder(); + foreach (char ch in strValue) + { + char chMapped = ch; + + // Symbols and surrogates must be moved into the correct code + // page area + if (char.IsSymbol(ch) || char.IsSurrogate(ch)) + { + UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory(ch); + // Map character to correct position in code page + chMapped = (char)((int)cat * 32 + ch); + } + else if (char.IsControl(ch)) + { + if (ch >= 256) // Control character in high ANSI code page + { + // Some of the control characters map to corresponding ones + // in the low ANSI range (up to 255) when calling + // ToLower on them with invariant culture (see + // http://lists.ximian.com/pipermail/mono-patches/2002-February/086106.html ) +#if !KeePassLibSD + chMapped = char.ToLowerInvariant(ch); +#else + chMapped = char.ToLower(ch); +#endif + } + } + + sb.Append(chMapped); + } + + strValue = sb.ToString(); // Correct string for current code page + } + + if ((m_format == KdbxFormat.PlainXml) && bProtected) + m_xmlWriter.WriteAttributeString(AttrProtectedInMemPlainXml, ValTrue); + + m_xmlWriter.WriteString(StrUtil.SafeXmlString(strValue)); + } + + m_xmlWriter.WriteEndElement(); // ElemValue + m_xmlWriter.WriteEndElement(); // ElemString + } + + private void WriteObject(string name, ProtectedBinary value, bool bAllowRef) + { + Debug.Assert(name != null); + Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value"); + + m_xmlWriter.WriteStartElement(ElemBinary); + m_xmlWriter.WriteStartElement(ElemKey); + m_xmlWriter.WriteString(StrUtil.SafeXmlString(name)); + m_xmlWriter.WriteEndElement(); + m_xmlWriter.WriteStartElement(ElemValue); + + string strRef = null; + if (bAllowRef) + { + int iRef = m_pbsBinaries.Find(value); + if (iRef >= 0) strRef = iRef.ToString(NumberFormatInfo.InvariantInfo); + else { Debug.Assert(false); } + } + if (strRef != null) + m_xmlWriter.WriteAttributeString(AttrRef, strRef); + else SubWriteValue(value); + + m_xmlWriter.WriteEndElement(); // ElemValue + m_xmlWriter.WriteEndElement(); // ElemBinary + } + + private void SubWriteValue(ProtectedBinary value) + { + if (value.IsProtected && (m_format == KdbxFormat.Default)) + { + m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue); + + byte[] pbEnc = value.ReadXorredData(m_randomStream); + if (pbEnc.Length > 0) + m_xmlWriter.WriteBase64(pbEnc, 0, pbEnc.Length); + } + else + { + if (m_pwDatabase.Compression != PwCompressionAlgorithm.None) + { + m_xmlWriter.WriteAttributeString(AttrCompressed, ValTrue); + + byte[] pbRaw = value.ReadData(); + byte[] pbCmp = MemUtil.Compress(pbRaw); + m_xmlWriter.WriteBase64(pbCmp, 0, pbCmp.Length); + + if (value.IsProtected) + { + MemUtil.ZeroByteArray(pbRaw); + MemUtil.ZeroByteArray(pbCmp); + } + } + else + { + byte[] pbRaw = value.ReadData(); + m_xmlWriter.WriteBase64(pbRaw, 0, pbRaw.Length); + + if (value.IsProtected) MemUtil.ZeroByteArray(pbRaw); + } + } + } + + private void WriteObject(string name, PwDeletedObject value) + { + Debug.Assert(name != null); + Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value"); + + m_xmlWriter.WriteStartElement(name); + WriteObject(ElemUuid, value.Uuid); + WriteObject(ElemDeletionTime, value.DeletionTime); + m_xmlWriter.WriteEndElement(); + } + + private void WriteBinPool() + { + m_xmlWriter.WriteStartElement(ElemBinaries); + + ProtectedBinary[] v = m_pbsBinaries.ToArray(); + for (int i = 0; i < v.Length; ++i) + { + m_xmlWriter.WriteStartElement(ElemBinary); + m_xmlWriter.WriteAttributeString(AttrId, + i.ToString(NumberFormatInfo.InvariantInfo)); + SubWriteValue(v[i]); + m_xmlWriter.WriteEndElement(); + } + + m_xmlWriter.WriteEndElement(); + } + + internal static void WriteGroup(Stream msOutput, PwDatabase pdContext, + PwGroup pg) + { + if (msOutput == null) throw new ArgumentNullException("msOutput"); + // pdContext may be null + if (pg == null) throw new ArgumentNullException("pg"); + + PwDatabase pd = new PwDatabase(); + pd.New(new IOConnectionInfo(), new CompositeKey(), pg.Name); + + pd.RootGroup = pg.CloneDeep(); + pd.RootGroup.ParentGroup = null; + + PwDatabase.CopyCustomIcons(pdContext, pd, pd.RootGroup, true); + + KdbxFile f = new KdbxFile(pd); + f.Save(msOutput, null, KdbxFormat.PlainXml, null); + } + + [Obsolete] + public static bool WriteEntries(Stream msOutput, PwEntry[] vEntries) + { + return WriteEntries(msOutput, null, vEntries); + } + + public static bool WriteEntries(Stream msOutput, PwDatabase pdContext, + PwEntry[] vEntries) + { + if (msOutput == null) { Debug.Assert(false); return false; } + // pdContext may be null + if (vEntries == null) { Debug.Assert(false); return false; } + + PwGroup pg = new PwGroup(true, true); + + foreach (PwEntry pe in vEntries) + { + PwEntry peCopy = pe.CloneDeep(); + pg.AddEntry(peCopy, true); + } + + WriteGroup(msOutput, pdContext, pg); + return true; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Serialization/KdbxFile.cs b/src/KeePassLib2AndroidSdkStyle/Serialization/KdbxFile.cs new file mode 100644 index 00000000..ab762af8 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Serialization/KdbxFile.cs @@ -0,0 +1,617 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Security; +using System.Text; +using System.Xml; + +#if !KeePassUAP +using System.Security.Cryptography; +#endif + +using KeePassLib.Collections; +using KeePassLib.Cryptography; +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.KeyDerivation; +using KeePassLib.Delegates; +using KeePassLib.Interfaces; +using KeePassLib.Resources; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + /// + /// The KdbxFile class supports saving the data to various + /// formats. + /// + public enum KdbxFormat + { + /// + /// The default, encrypted file format. + /// + Default = 0, + + /// + /// Use this flag when exporting data to a plain-text XML file. + /// + PlainXml, + + //Deprecated + ProtocolBuffers + } + + /// + /// Serialization to KeePass KDBX files. + /// + public sealed partial class KdbxFile + { + /// + /// System.Drawing.ColorTranslator is not supported on Android. Provide a custom implementation here for loading colors from file. + /// + private class ColorTranslator + { + public static Color FromHtml(String colorString) + { + Color color; + + if (colorString.StartsWith("#")) + { + colorString = colorString.Substring(1); + } + if (colorString.EndsWith(";")) + { + colorString = colorString.Substring(0, colorString.Length - 1); + } + + int red, green, blue; + switch (colorString.Length) + { + case 6: + red = int.Parse(colorString.Substring(0, 2), System.Globalization.NumberStyles.HexNumber); + green = int.Parse(colorString.Substring(2, 2), System.Globalization.NumberStyles.HexNumber); + blue = int.Parse(colorString.Substring(4, 2), System.Globalization.NumberStyles.HexNumber); + color = Color.FromArgb(red, green, blue); + break; + case 3: + red = int.Parse(colorString.Substring(0, 1), System.Globalization.NumberStyles.HexNumber); + green = int.Parse(colorString.Substring(1, 1), System.Globalization.NumberStyles.HexNumber); + blue = int.Parse(colorString.Substring(2, 1), System.Globalization.NumberStyles.HexNumber); + color = Color.FromArgb(red, green, blue); + break; + case 1: + red = green = blue = int.Parse(colorString.Substring(0, 1), System.Globalization.NumberStyles.HexNumber); + color = Color.FromArgb(red, green, blue); + break; + default: + throw new ArgumentException("Invalid color: " + colorString); + } + return color; + } + + } + + /// + /// File identifier, first 32-bit value. + /// + internal const uint FileSignature1 = 0x9AA2D903; + + /// + /// File identifier, second 32-bit value. + /// + internal const uint FileSignature2 = 0xB54BFB67; + + /// + /// Maximum supported version of database files. + /// KeePass 2.07 has version 1.01, 2.08 has 1.02, 2.09 has 2.00, + /// 2.10 has 2.02, 2.11 has 2.04, 2.15 has 3.00, 2.20 has 3.01. + /// The first 2 bytes are critical (i.e. loading will fail, if the + /// file version is too high), the last 2 bytes are informational. + /// + private const uint FileVersion32 = 0x00040001; + + public const uint FileVersion32_4_1 = 0x00040001; // 4.1 + public const uint FileVersion32_4 = 0x00040000; // 4.0 + public const uint FileVersion32_3_1 = 0x00030001; // 3.1 + + private const uint FileVersionCriticalMask = 0xFFFF0000; + + // KeePass 1.x signature + internal const uint FileSignatureOld1 = 0x9AA2D903; + internal const uint FileSignatureOld2 = 0xB54BFB65; + // KeePass 2.x pre-release (alpha and beta) signature + internal const uint FileSignaturePreRelease1 = 0x9AA2D903; + internal const uint FileSignaturePreRelease2 = 0xB54BFB66; + + private const string ElemDocNode = "KeePassFile"; + private const string ElemMeta = "Meta"; + private const string ElemRoot = "Root"; + private const string ElemGroup = "Group"; + internal const string ElemEntry = "Entry"; + + private const string ElemGenerator = "Generator"; + private const string ElemHeaderHash = "HeaderHash"; + private const string ElemSettingsChanged = "SettingsChanged"; + private const string ElemDbName = "DatabaseName"; + private const string ElemDbNameChanged = "DatabaseNameChanged"; + private const string ElemDbDesc = "DatabaseDescription"; + private const string ElemDbDescChanged = "DatabaseDescriptionChanged"; + private const string ElemDbDefaultUser = "DefaultUserName"; + private const string ElemDbDefaultUserChanged = "DefaultUserNameChanged"; + private const string ElemDbMntncHistoryDays = "MaintenanceHistoryDays"; + private const string ElemDbColor = "Color"; + private const string ElemDbKeyChanged = "MasterKeyChanged"; + private const string ElemDbKeyChangeRec = "MasterKeyChangeRec"; + private const string ElemDbKeyChangeForce = "MasterKeyChangeForce"; + private const string ElemDbKeyChangeForceOnce = "MasterKeyChangeForceOnce"; + private const string ElemRecycleBinEnabled = "RecycleBinEnabled"; + private const string ElemRecycleBinUuid = "RecycleBinUUID"; + private const string ElemRecycleBinChanged = "RecycleBinChanged"; + private const string ElemEntryTemplatesGroup = "EntryTemplatesGroup"; + private const string ElemEntryTemplatesGroupChanged = "EntryTemplatesGroupChanged"; + private const string ElemHistoryMaxItems = "HistoryMaxItems"; + private const string ElemHistoryMaxSize = "HistoryMaxSize"; + private const string ElemLastSelectedGroup = "LastSelectedGroup"; + private const string ElemLastTopVisibleGroup = "LastTopVisibleGroup"; + + private const string ElemMemoryProt = "MemoryProtection"; + private const string ElemProtTitle = "ProtectTitle"; + private const string ElemProtUserName = "ProtectUserName"; + private const string ElemProtPassword = "ProtectPassword"; + private const string ElemProtUrl = "ProtectURL"; + private const string ElemProtNotes = "ProtectNotes"; + // private const string ElemProtAutoHide = "AutoEnableVisualHiding"; + + private const string ElemCustomIcons = "CustomIcons"; + private const string ElemCustomIconItem = "Icon"; + private const string ElemCustomIconItemID = "UUID"; + private const string ElemCustomIconItemData = "Data"; + + private const string ElemAutoType = "AutoType"; + private const string ElemHistory = "History"; + + private const string ElemName = "Name"; + private const string ElemNotes = "Notes"; + internal const string ElemUuid = "UUID"; + private const string ElemIcon = "IconID"; + private const string ElemCustomIconID = "CustomIconUUID"; + private const string ElemFgColor = "ForegroundColor"; + private const string ElemBgColor = "BackgroundColor"; + private const string ElemOverrideUrl = "OverrideURL"; + private const string ElemQualityCheck = "QualityCheck"; + private const string ElemTimes = "Times"; + private const string ElemTags = "Tags"; + + private const string ElemCreationTime = "CreationTime"; + private const string ElemLastModTime = "LastModificationTime"; + private const string ElemLastAccessTime = "LastAccessTime"; + private const string ElemExpiryTime = "ExpiryTime"; + private const string ElemExpires = "Expires"; + private const string ElemUsageCount = "UsageCount"; + private const string ElemLocationChanged = "LocationChanged"; + + private const string ElemPreviousParentGroup = "PreviousParentGroup"; + + private const string ElemGroupDefaultAutoTypeSeq = "DefaultAutoTypeSequence"; + private const string ElemEnableAutoType = "EnableAutoType"; + private const string ElemEnableSearching = "EnableSearching"; + + private const string ElemString = "String"; + private const string ElemBinary = "Binary"; + private const string ElemKey = "Key"; + private const string ElemValue = "Value"; + + private const string ElemAutoTypeEnabled = "Enabled"; + private const string ElemAutoTypeObfuscation = "DataTransferObfuscation"; + private const string ElemAutoTypeDefaultSeq = "DefaultSequence"; + private const string ElemAutoTypeItem = "Association"; + private const string ElemWindow = "Window"; + private const string ElemKeystrokeSequence = "KeystrokeSequence"; + + private const string ElemBinaries = "Binaries"; + + private const string AttrId = "ID"; + private const string AttrRef = "Ref"; + private const string AttrProtected = "Protected"; + private const string AttrProtectedInMemPlainXml = "ProtectInMemory"; + private const string AttrCompressed = "Compressed"; + + private const string ElemIsExpanded = "IsExpanded"; + private const string ElemLastTopVisibleEntry = "LastTopVisibleEntry"; + + private const string ElemDeletedObjects = "DeletedObjects"; + private const string ElemDeletedObject = "DeletedObject"; + private const string ElemDeletionTime = "DeletionTime"; + + private const string ValFalse = "False"; + private const string ValTrue = "True"; + + private const string ElemCustomData = "CustomData"; + private const string ElemStringDictExItem = "Item"; + + private PwDatabase m_pwDatabase; // Not null, see constructor + private bool m_bUsedOnce = false; + + private XmlWriter m_xmlWriter = null; + private CryptoRandomStream m_randomStream = null; + private KdbxFormat m_format = KdbxFormat.Default; + private IStatusLogger m_slLogger = null; + + private uint m_uFileVersion = 0; + private byte[] m_pbMasterSeed = null; + // private byte[] m_pbTransformSeed = null; + private byte[] m_pbEncryptionIV = null; + private byte[] m_pbStreamStartBytes = null; + + // ArcFourVariant only for backward compatibility; KeePass defaults + // to a more secure algorithm when *writing* databases + private CrsAlgorithm m_craInnerRandomStream = CrsAlgorithm.ArcFourVariant; + private byte[] m_pbInnerRandomStreamKey = null; + + private ProtectedBinarySet m_pbsBinaries = null; + + private byte[] m_pbHashOfHeader = null; + private byte[] m_pbHashOfFileOnDisk = null; + + private readonly DateTime m_dtNow = DateTime.UtcNow; // Cache current time + + private const uint NeutralLanguageOffset = 0x100000; // 2^20, see 32-bit Unicode specs + private const uint NeutralLanguageIDSec = 0x7DC5C; // See 32-bit Unicode specs + private const uint NeutralLanguageID = NeutralLanguageOffset + NeutralLanguageIDSec; + private static bool g_bLocalizedNames = false; + + private enum KdbxHeaderFieldID : byte + { + EndOfHeader = 0, + Comment = 1, + CipherID = 2, + CompressionFlags = 3, + MasterSeed = 4, + TransformSeed = 5, // KDBX 3.1, for backward compatibility only + TransformRounds = 6, // KDBX 3.1, for backward compatibility only + EncryptionIV = 7, + InnerRandomStreamKey = 8, // KDBX 3.1, for backward compatibility only + StreamStartBytes = 9, // KDBX 3.1, for backward compatibility only + InnerRandomStreamID = 10, // KDBX 3.1, for backward compatibility only + KdfParameters = 11, // KDBX 4, superseding Transform* + PublicCustomData = 12 // KDBX 4 + } + + // Inner header in KDBX >= 4 files + private enum KdbxInnerHeaderFieldID : byte + { + EndOfHeader = 0, + InnerRandomStreamID = 1, // Supersedes KdbxHeaderFieldID.InnerRandomStreamID + InnerRandomStreamKey = 2, // Supersedes KdbxHeaderFieldID.InnerRandomStreamKey + Binary = 3 + } + + [Flags] + private enum KdbxBinaryFlags : byte + { + None = 0, + Protected = 1 + } + + public byte[] HashOfFileOnDisk + { + get { return m_pbHashOfFileOnDisk; } + } + + private bool m_bRepairMode = false; + public bool RepairMode + { + get { return m_bRepairMode; } + set { m_bRepairMode = value; } + } + + private uint m_uForceVersion = 0; + internal uint ForceVersion + { + get { return m_uForceVersion; } + set { m_uForceVersion = value; } + } + + private string m_strDetachBins = null; + /// + /// Detach binaries when opening a file. If this isn't null, + /// all binaries are saved to the specified path and are removed + /// from the database. + /// + public string DetachBinaries + { + get { return m_strDetachBins; } + set { m_strDetachBins = value; } + } + + /// + /// Default constructor. + /// + /// The PwDatabase instance that the + /// class will load file data into or use to create a KDBX file. + public KdbxFile(PwDatabase pwDataStore) + { + Debug.Assert(pwDataStore != null); + if (pwDataStore == null) throw new ArgumentNullException("pwDataStore"); + + m_pwDatabase = pwDataStore; + } + + /// + /// Call this once to determine the current localization settings. + /// + public static void DetermineLanguageId() + { + // Test if localized names should be used. If localized names are used, + // the g_bLocalizedNames value must be set to true. By default, localized + // names should be used (otherwise characters could be corrupted + // because of different code pages). + unchecked + { + uint uTest = 0; + foreach (char ch in PwDatabase.LocalizedAppName) + uTest = uTest * 5 + ch; + + g_bLocalizedNames = (uTest != NeutralLanguageID); + } + } + + private uint GetMinKdbxVersion() + { + if (m_uForceVersion != 0) return m_uForceVersion; + + uint minVersionForKeys = m_pwDatabase.MasterKey.UserKeys.Select(key => key.GetMinKdbxVersion()).Max(); + + uint minRequiredVersion = Math.Max(minVersionForKeys, m_uFileVersion); //don't save a version lower than what we read + + return Math.Max(minRequiredVersion, GetMinKdbxVersionOrig()); + } + + private uint GetMinKdbxVersionOrig() + { + if (m_uForceVersion != 0) return m_uForceVersion; + + // See also KeePassKdb2x3.Export (KDBX 3.1 export module) + + uint uMin = 0; + + GroupHandler gh = delegate (PwGroup pg) + { + if (pg == null) { Debug.Assert(false); return true; } + + if (pg.Tags.Count != 0) + uMin = Math.Max(uMin, FileVersion32_4_1); + if (pg.CustomData.Count != 0) + uMin = Math.Max(uMin, FileVersion32_4); + + return true; + }; + + EntryHandler eh = delegate (PwEntry pe) + { + if (pe == null) { Debug.Assert(false); return true; } + + if (!pe.QualityCheck) + uMin = Math.Max(uMin, FileVersion32_4_1); + if (pe.CustomData.Count != 0) + uMin = Math.Max(uMin, FileVersion32_4); + + return true; + }; + + gh(m_pwDatabase.RootGroup); + m_pwDatabase.RootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); + + if (uMin >= FileVersion32_4_1) return uMin; // All below is <= 4.1 + + foreach (PwCustomIcon ci in m_pwDatabase.CustomIcons) + { + if ((ci.Name.Length != 0) || ci.LastModificationTime.HasValue) + return FileVersion32_4_1; + } + + foreach (KeyValuePair kvp in m_pwDatabase.CustomData) + { + DateTime? odt = m_pwDatabase.CustomData.GetLastModificationTime(kvp.Key); + if (odt.HasValue) return FileVersion32_4_1; + } + + if (uMin >= FileVersion32_4) return uMin; // All below is <= 4 + + if (m_pwDatabase.DataCipherUuid.Equals(ChaCha20Engine.ChaCha20Uuid)) + return FileVersion32_4; + + AesKdf kdfAes = new AesKdf(); + if (!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfAes.Uuid)) + return FileVersion32_4; + + if (m_pwDatabase.PublicCustomData.Count != 0) + return FileVersion32_4; + + return FileVersion32_3_1; // KDBX 3.1 is sufficient + } + + private void ComputeKeys(out byte[] pbCipherKey, int cbCipherKey, + out byte[] pbHmacKey64) + { + byte[] pbCmp = new byte[32 + 32 + 1]; + try + { + Debug.Assert(m_pbMasterSeed != null); + if (m_pbMasterSeed == null) + throw new ArgumentNullException("m_pbMasterSeed"); + Debug.Assert(m_pbMasterSeed.Length == 32); + if (m_pbMasterSeed.Length != 32) + throw new FormatException(KLRes.MasterSeedLengthInvalid); + Array.Copy(m_pbMasterSeed, 0, pbCmp, 0, 32); + + Debug.Assert(m_pwDatabase != null); + Debug.Assert(m_pwDatabase.MasterKey != null); + ProtectedBinary pbinUser = m_pwDatabase.MasterKey.GenerateKey32(m_pwDatabase.KdfParameters, + m_pbMasterSeed); + + Array.Copy(m_pbMasterSeed, 0, pbCmp, 0, 32); + + Debug.Assert(pbinUser != null); + if (pbinUser == null) + throw new SecurityException(KLRes.InvalidCompositeKey); + byte[] pUserKey32 = pbinUser.ReadData(); + if ((pUserKey32 == null) || (pUserKey32.Length != 32)) + throw new SecurityException(KLRes.InvalidCompositeKey); + Array.Copy(pUserKey32, 0, pbCmp, 32, 32); + MemUtil.ZeroByteArray(pUserKey32); + + pbCipherKey = CryptoUtil.ResizeKey(pbCmp, 0, 64, cbCipherKey); + + pbCmp[64] = 1; + using (SHA512Managed h = new SHA512Managed()) + { + pbHmacKey64 = h.ComputeHash(pbCmp); + } + } + finally { MemUtil.ZeroByteArray(pbCmp); } + } + + private ICipherEngine GetCipher(out int cbEncKey, out int cbEncIV) + { + PwUuid pu = m_pwDatabase.DataCipherUuid; + ICipherEngine iCipher = CipherPool.GlobalPool.GetCipher(pu); + if (iCipher == null) // CryptographicExceptions are translated to "file corrupted" + throw new Exception(KLRes.FileUnknownCipher + + MessageService.NewParagraph + KLRes.FileNewVerOrPlgReq + + MessageService.NewParagraph + "UUID: " + pu.ToHexString() + "."); + + ICipherEngine2 iCipher2 = (iCipher as ICipherEngine2); + if (iCipher2 != null) + { + cbEncKey = iCipher2.KeyLength; + if (cbEncKey < 0) throw new InvalidOperationException("EncKey.Length"); + + cbEncIV = iCipher2.IVLength; + if (cbEncIV < 0) throw new InvalidOperationException("EncIV.Length"); + } + else + { + cbEncKey = 32; + cbEncIV = 16; + } + + return iCipher; + } + + private Stream EncryptStream(Stream s, ICipherEngine iCipher, + byte[] pbKey, int cbIV, bool bEncrypt) + { + byte[] pbIV = (m_pbEncryptionIV ?? MemUtil.EmptyByteArray); + if (pbIV.Length != cbIV) + { + Debug.Assert(false); + throw new Exception(KLRes.FileCorrupted); + } + + if (bEncrypt) + return iCipher.EncryptStream(s, pbKey, pbIV); + return iCipher.DecryptStream(s, pbKey, pbIV); + } + + private byte[] ComputeHeaderHmac(byte[] pbHeader, byte[] pbKey) + { + byte[] pbHeaderHmac; + byte[] pbBlockKey = HmacBlockStream.GetHmacKey64( + pbKey, ulong.MaxValue); + using (HMACSHA256 h = new HMACSHA256(pbBlockKey)) + { + pbHeaderHmac = h.ComputeHash(pbHeader); + } + MemUtil.ZeroByteArray(pbBlockKey); + + return pbHeaderHmac; + } + + private void CloseStreams(List lStreams) + { + if (lStreams == null) { Debug.Assert(false); return; } + + // Typically, closing a stream also closes its base + // stream; however, there may be streams that do not + // do this (e.g. some cipher plugin), thus for safety + // we close all streams manually, from the innermost + // to the outermost + + for (int i = lStreams.Count - 1; i >= 0; --i) + { + // Check for duplicates + Debug.Assert((lStreams.IndexOf(lStreams[i]) == i) && + (lStreams.LastIndexOf(lStreams[i]) == i)); + + try { lStreams[i].Close(); } + catch (Exception) { Debug.Assert(false); } + } + + // Do not clear the list + } + + private void CleanUpInnerRandomStream() + { + if (m_randomStream != null) m_randomStream.Dispose(); + + if (m_pbInnerRandomStreamKey != null) + MemUtil.ZeroByteArray(m_pbInnerRandomStreamKey); + } + + private static void SaveBinary(string strName, ProtectedBinary pb, + string strSaveDir) + { + if (pb == null) { Debug.Assert(false); return; } + + strName = UrlUtil.GetSafeFileName(strName); + + string strPath; + int iTry = 1; + do + { + strPath = UrlUtil.EnsureTerminatingSeparator(strSaveDir, false); + + string strDesc = UrlUtil.StripExtension(strName); + string strExt = UrlUtil.GetExtension(strName); + + strPath += strDesc; + if (iTry > 1) + strPath += " (" + iTry.ToString(NumberFormatInfo.InvariantInfo) + + ")"; + + if (!string.IsNullOrEmpty(strExt)) strPath += "." + strExt; + + ++iTry; + } + while (File.Exists(strPath)); + + byte[] pbData = pb.ReadData(); + try { File.WriteAllBytes(strPath, pbData); } + finally { if (pb.IsProtected) MemUtil.ZeroByteArray(pbData); } + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Serialization/OldFormatException.cs b/src/KeePassLib2AndroidSdkStyle/Serialization/OldFormatException.cs new file mode 100644 index 00000000..2dd6b20c --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Serialization/OldFormatException.cs @@ -0,0 +1,66 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public sealed class OldFormatException : Exception + { + private string m_strFormat = string.Empty; + private OldFormatType m_type = OldFormatType.Unknown; + + public enum OldFormatType + { + Unknown = 0, + KeePass1x = 1 + } + + public override string Message + { + get + { + string str = KLRes.OldFormat + ((m_strFormat.Length > 0) ? + (@" (" + m_strFormat + @")") : string.Empty) + "."; + + if(m_type == OldFormatType.KeePass1x) + str += MessageService.NewParagraph + KLRes.KeePass1xHint; + + return str; + } + } + + public OldFormatException(string strFormatName) + { + if(strFormatName != null) m_strFormat = strFormatName; + } + + public OldFormatException(string strFormatName, OldFormatType t) + { + if(strFormatName != null) m_strFormat = strFormatName; + + m_type = t; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Translation/KPControlCustomization.cs b/src/KeePassLib2AndroidSdkStyle/Translation/KPControlCustomization.cs new file mode 100644 index 00000000..3372fcac --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Translation/KPControlCustomization.cs @@ -0,0 +1,405 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.ComponentModel; +using System.Diagnostics; +using System.Xml.Serialization; +using System.Globalization; +using System.IO; +using System.Security.Cryptography; +using System.Drawing; + +using KeePassLib.Utility; + +namespace KeePassLib.Translation +{ + public class Control + {} + + public sealed class KpccLayout + { + + public enum LayoutParameterEx + { + X, Y, Width, Height + } + + private const string m_strControlRelative = @"%c"; + + internal const NumberStyles m_nsParser = (NumberStyles.AllowLeadingSign | + NumberStyles.AllowDecimalPoint); + internal static readonly CultureInfo m_lclInv = CultureInfo.InvariantCulture; + + private string m_strPosX = string.Empty; + [XmlAttribute] + [DefaultValue("")] + public string X + { + get { return m_strPosX; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strPosX = value; + } + } + + private string m_strPosY = string.Empty; + [XmlAttribute] + [DefaultValue("")] + public string Y + { + get { return m_strPosY; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strPosY = value; + } + } + + private string m_strSizeW = string.Empty; + [XmlAttribute] + [DefaultValue("")] + public string Width + { + get { return m_strSizeW; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strSizeW = value; + } + } + + private string m_strSizeH = string.Empty; + [XmlAttribute] + [DefaultValue("")] + public string Height + { + get { return m_strSizeH; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strSizeH = value; + } + } + + public void SetControlRelativeValue(LayoutParameterEx lp, string strValue) + { + Debug.Assert(strValue != null); + if(strValue == null) throw new ArgumentNullException("strValue"); + + if(strValue.Length > 0) strValue += m_strControlRelative; + + if(lp == LayoutParameterEx.X) m_strPosX = strValue; + else if(lp == LayoutParameterEx.Y) m_strPosY = strValue; + else if(lp == LayoutParameterEx.Width) m_strSizeW = strValue; + else if(lp == LayoutParameterEx.Height) m_strSizeH = strValue; + else { Debug.Assert(false); } + } + +#if (!KeePassLibSD && !KeePassRT) + /*internal void ApplyTo(Control c) + { + Debug.Assert(c != null); if(c == null) return; + + int? v; + v = GetModControlParameter(c, LayoutParameterEx.X, m_strPosX); + if(v.HasValue) c.Left = v.Value; + v = GetModControlParameter(c, LayoutParameterEx.Y, m_strPosY); + if(v.HasValue) c.Top = v.Value; + v = GetModControlParameter(c, LayoutParameterEx.Width, m_strSizeW); + if(v.HasValue) c.Width = v.Value; + v = GetModControlParameter(c, LayoutParameterEx.Height, m_strSizeH); + if(v.HasValue) c.Height = v.Value; + } + + private static int? GetModControlParameter(Control c, LayoutParameterEx p, + string strModParam) + { + if(strModParam.Length == 0) return null; + + Debug.Assert(c.Left == c.Location.X); + Debug.Assert(c.Top == c.Location.Y); + Debug.Assert(c.Width == c.Size.Width); + Debug.Assert(c.Height == c.Size.Height); + + int iPrev; + if(p == LayoutParameterEx.X) iPrev = c.Left; + else if(p == LayoutParameterEx.Y) iPrev = c.Top; + else if(p == LayoutParameterEx.Width) iPrev = c.Width; + else if(p == LayoutParameterEx.Height) iPrev = c.Height; + else { Debug.Assert(false); return null; } + + double? dRel = ToControlRelativePercent(strModParam); + if(dRel.HasValue) + return (iPrev + (int)((dRel.Value * (double)iPrev) / 100.0)); + + Debug.Assert(false); + return null; + } + + public static double? ToControlRelativePercent(string strEncoded) + { + Debug.Assert(strEncoded != null); + if(strEncoded == null) throw new ArgumentNullException("strEncoded"); + + if(strEncoded.Length == 0) return null; + + if(strEncoded.EndsWith(m_strControlRelative)) + { + string strValue = strEncoded.Substring(0, strEncoded.Length - + m_strControlRelative.Length); + if((strValue.Length == 1) && (strValue == "-")) + strValue = "0"; + + double dRel; + if(double.TryParse(strValue, m_nsParser, m_lclInv, out dRel)) + { + return dRel; + } + else + { + Debug.Assert(false); + return null; + } + } + + Debug.Assert(false); + return null; + }*/ +#endif + + public static string ToControlRelativeString(string strEncoded) + { + Debug.Assert(strEncoded != null); + if(strEncoded == null) throw new ArgumentNullException("strEncoded"); + + if(strEncoded.Length == 0) return string.Empty; + + if(strEncoded.EndsWith(m_strControlRelative)) + return strEncoded.Substring(0, strEncoded.Length - + m_strControlRelative.Length); + + Debug.Assert(false); + return string.Empty; + } + } + + public sealed class KPControlCustomization : IComparable + { + private string m_strMemberName = string.Empty; + /// + /// Member variable name of the control to be translated. + /// + [XmlAttribute] + public string Name + { + get { return m_strMemberName; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strMemberName = value; + } + } + + private string m_strHash = string.Empty; + [XmlAttribute] + public string BaseHash + { + get { return m_strHash; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strHash = value; + } + } + + private string m_strText = string.Empty; + [DefaultValue("")] + public string Text + { + get { return m_strText; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strText = value; + } + } + + private string m_strEngText = string.Empty; + [XmlIgnore] + public string TextEnglish + { + get { return m_strEngText; } + set { m_strEngText = value; } + } + + private KpccLayout m_layout = new KpccLayout(); + public KpccLayout Layout + { + get { return m_layout; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_layout = value; + } + } + + public int CompareTo(KPControlCustomization kpOther) + { + if(kpOther == null) { Debug.Assert(false); return 1; } + + return m_strMemberName.CompareTo(kpOther.Name); + } + +#if (!KeePassLibSD && !KeePassRT) + /*private static readonly Type[] m_vTextControls = new Type[] { + typeof(MenuStrip), typeof(PictureBox), typeof(ListView), + typeof(TreeView), typeof(ToolStrip), typeof(WebBrowser), + typeof(Panel), typeof(StatusStrip), typeof(ProgressBar), + typeof(NumericUpDown), typeof(TabControl) + }; + + public static bool ControlSupportsText(object oControl) + { + if(oControl == null) return false; + + Type t = oControl.GetType(); + for(int i = 0; i < m_vTextControls.Length; ++i) + { + if(t == m_vTextControls[i]) return false; + } + + return true; + } + + // Name-unchecked (!) property application method + internal void ApplyTo(Control c) + { + if((m_strText.Length > 0) && ControlSupportsText(c) && + (c.Text.Length > 0)) + { + c.Text = m_strText; + } + + m_layout.ApplyTo(c); + } + + public static string HashControl(Control c) + { + if(c == null) { Debug.Assert(false); return string.Empty; } + + StringBuilder sb = new StringBuilder(); + WriteCpiParam(sb, c.Text); + + if(c is Form) + { + WriteCpiParam(sb, c.ClientSize.Width.ToString(KpccLayout.m_lclInv)); + WriteCpiParam(sb, c.ClientSize.Height.ToString(KpccLayout.m_lclInv)); + } + else // Normal control + { + WriteCpiParam(sb, c.Left.ToString(KpccLayout.m_lclInv)); + WriteCpiParam(sb, c.Top.ToString(KpccLayout.m_lclInv)); + WriteCpiParam(sb, c.Width.ToString(KpccLayout.m_lclInv)); + WriteCpiParam(sb, c.Height.ToString(KpccLayout.m_lclInv)); + WriteCpiParam(sb, c.Dock.ToString()); + } + + WriteCpiParam(sb, c.Font.Name); + WriteCpiParam(sb, c.Font.SizeInPoints.ToString(KpccLayout.m_lclInv)); + WriteCpiParam(sb, c.Font.Bold ? "B" : "N"); + WriteCpiParam(sb, c.Font.Italic ? "I" : "N"); + WriteCpiParam(sb, c.Font.Underline ? "U" : "N"); + WriteCpiParam(sb, c.Font.Strikeout ? "S" : "N"); + + WriteControlDependentParams(sb, c); + + byte[] pb = StrUtil.Utf8.GetBytes(sb.ToString()); + + SHA256Managed sha256 = new SHA256Managed(); + byte[] pbSha = sha256.ComputeHash(pb); + + // Also see MatchHash + return "v1:" + Convert.ToBase64String(pbSha, 0, 3, + Base64FormattingOptions.None); + } + + private static void WriteControlDependentParams(StringBuilder sb, Control c) + { + CheckBox cb = (c as CheckBox); + RadioButton rb = (c as RadioButton); + Button btn = (c as Button); + Label l = (c as Label); + LinkLabel ll = (c as LinkLabel); + + if(cb != null) + { + WriteCpiParam(sb, cb.AutoSize ? "A" : "F"); + WriteCpiParam(sb, cb.TextAlign.ToString()); + WriteCpiParam(sb, cb.TextImageRelation.ToString()); + WriteCpiParam(sb, cb.Appearance.ToString()); + WriteCpiParam(sb, cb.CheckAlign.ToString()); + } + else if(rb != null) + { + WriteCpiParam(sb, rb.AutoSize ? "A" : "F"); + WriteCpiParam(sb, rb.TextAlign.ToString()); + WriteCpiParam(sb, rb.TextImageRelation.ToString()); + WriteCpiParam(sb, rb.Appearance.ToString()); + WriteCpiParam(sb, rb.CheckAlign.ToString()); + } + else if(btn != null) + { + WriteCpiParam(sb, btn.AutoSize ? "A" : "F"); + WriteCpiParam(sb, btn.TextAlign.ToString()); + WriteCpiParam(sb, btn.TextImageRelation.ToString()); + } + else if(l != null) + { + WriteCpiParam(sb, l.AutoSize ? "A" : "F"); + WriteCpiParam(sb, l.TextAlign.ToString()); + } + else if(ll != null) + { + WriteCpiParam(sb, ll.AutoSize ? "A" : "F"); + WriteCpiParam(sb, ll.TextAlign.ToString()); + } + } + + private static void WriteCpiParam(StringBuilder sb, string strProp) + { + sb.Append('/'); + sb.Append(strProp); + } + + public bool MatchHash(string strHash) + { + if(strHash == null) throw new ArgumentNullException("strHash"); + + // Currently only v1: is supported, see HashControl + return (m_strHash == strHash); + }*/ +#endif + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Translation/KPStringTable.cs b/src/KeePassLib2AndroidSdkStyle/Translation/KPStringTable.cs new file mode 100644 index 00000000..62ea37da --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Translation/KPStringTable.cs @@ -0,0 +1,99 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Xml.Serialization; + + +namespace KeePassLib.Translation +{ + public sealed class KPStringTable + { + private string m_strName = string.Empty; + [XmlAttribute] + public string Name + { + get { return m_strName; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strName = value; + } + } + + private List m_vItems = new List(); + + [XmlArrayItem("Data")] + public List Strings + { + get { return m_vItems; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_vItems = value; + } + } + + public Dictionary ToDictionary() + { + Dictionary dict = new Dictionary(); + + foreach(KPStringTableItem kpstItem in m_vItems) + { + if(kpstItem.Value.Length > 0) + dict[kpstItem.Name] = kpstItem.Value; + } + + return dict; + } + /* +#if (!KeePassLibSD && !KeePassUAP) + public void ApplyTo(ToolStripItemCollection tsic) + { + if(tsic == null) throw new ArgumentNullException("tsic"); + + Dictionary dict = this.ToDictionary(); + if(dict.Count == 0) return; + + this.ApplyTo(tsic, dict); + } + + private void ApplyTo(ToolStripItemCollection tsic, Dictionary dict) + { + if(tsic == null) return; + + foreach(ToolStripItem tsi in tsic) + { + if(tsi.Text.Length == 0) continue; + + string strTrl; + if(dict.TryGetValue(tsi.Name, out strTrl)) + tsi.Text = strTrl; + + ToolStripMenuItem tsmi = tsi as ToolStripMenuItem; + if((tsmi != null) && (tsmi.DropDownItems != null)) + this.ApplyTo(tsmi.DropDownItems); + } + } +#endif*/ + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Translation/KPStringTableItem.cs b/src/KeePassLib2AndroidSdkStyle/Translation/KPStringTableItem.cs new file mode 100644 index 00000000..5a879a90 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Translation/KPStringTableItem.cs @@ -0,0 +1,51 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml.Serialization; + +namespace KeePassLib.Translation +{ + public sealed class KPStringTableItem + { + private string m_strName = string.Empty; + public string Name + { + get { return m_strName; } + set { m_strName = value; } + } + + private string m_strValue = string.Empty; + public string Value + { + get { return m_strValue; } + set { m_strValue = value; } + } + + private string m_strEnglish = string.Empty; + [XmlIgnore] + public string ValueEnglish + { + get { return m_strEnglish; } + set { m_strEnglish = value; } + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Translation/KPTranslation.cs b/src/KeePassLib2AndroidSdkStyle/Translation/KPTranslation.cs new file mode 100644 index 00000000..6314f74c --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Translation/KPTranslation.cs @@ -0,0 +1,266 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Xml; +using System.Xml.Serialization; + +#if KeePassLibSD +using ICSharpCode.SharpZipLib.GZip; +#else +using System.IO.Compression; +#endif + +using KeePassLib.Interfaces; +using KeePassLib.Utility; + +namespace KeePassLib.Translation +{ + [XmlRoot("Translation")] + public sealed class KPTranslation + { + public const string FileExtension = "lngx"; + + private KPTranslationProperties m_props = new KPTranslationProperties(); + public KPTranslationProperties Properties + { + get { return m_props; } + set { m_props = value; } + } + + private List m_vStringTables = new List(); + + [XmlArrayItem("StringTable")] + public List StringTables + { + get { return m_vStringTables; } + set + { + if(value == null) throw new ArgumentNullException("value"); + + m_vStringTables = value; + } + } + /* + private List m_vForms = new List(); + + [XmlArrayItem("Form")] + public List Forms + { + get { return m_vForms; } + set + { + if(value == null) throw new ArgumentNullException("value"); + + m_vForms = value; + } + } + */ + private string m_strUnusedText = string.Empty; + [DefaultValue("")] + public string UnusedText + { + get { return m_strUnusedText; } + set + { + if(value == null) throw new ArgumentNullException("value"); + + m_strUnusedText = value; + } + } + + public static void Save(KPTranslation kpTrl, string strFileName, + IXmlSerializerEx xs) + { + using(FileStream fs = new FileStream(strFileName, FileMode.Create, + FileAccess.Write, FileShare.None)) + { + Save(kpTrl, fs, xs); + } + } + + public static void Save(KPTranslation kpTrl, Stream sOut, + IXmlSerializerEx xs) + { + if(xs == null) throw new ArgumentNullException("xs"); + +#if !KeePassLibSD + GZipStream gz = new GZipStream(sOut, CompressionMode.Compress); +#else + GZipOutputStream gz = new GZipOutputStream(sOut); +#endif + + XmlWriterSettings xws = new XmlWriterSettings(); + xws.CheckCharacters = true; + xws.Encoding = StrUtil.Utf8; + xws.Indent = true; + xws.IndentChars = "\t"; + + XmlWriter xw = XmlWriter.Create(gz, xws); + + xs.Serialize(xw, kpTrl); + + xw.Close(); + gz.Close(); + sOut.Close(); + } + + public static KPTranslation Load(string strFile, IXmlSerializerEx xs) + { + KPTranslation kpTrl = null; + + using(FileStream fs = new FileStream(strFile, FileMode.Open, + FileAccess.Read, FileShare.Read)) + { + kpTrl = Load(fs, xs); + } + + return kpTrl; + } + + public static KPTranslation Load(Stream s, IXmlSerializerEx xs) + { + if(xs == null) throw new ArgumentNullException("xs"); + +#if !KeePassLibSD + GZipStream gz = new GZipStream(s, CompressionMode.Decompress); +#else + GZipInputStream gz = new GZipInputStream(s); +#endif + + KPTranslation kpTrl = (xs.Deserialize(gz) as KPTranslation); + + gz.Close(); + s.Close(); + return kpTrl; + } + + public Dictionary SafeGetStringTableDictionary( + string strTableName) + { + foreach(KPStringTable kpst in m_vStringTables) + { + if(kpst.Name == strTableName) return kpst.ToDictionary(); + } + + return new Dictionary(); + } + /* +#if (!KeePassLibSD && !KeePassUAP) + public void ApplyTo(Form form) + { + if(form == null) throw new ArgumentNullException("form"); + + if(m_props.RightToLeft) + { + try + { + form.RightToLeft = RightToLeft.Yes; + form.RightToLeftLayout = true; + } + catch(Exception) { Debug.Assert(false); } + } + + string strTypeName = form.GetType().FullName; + foreach(KPFormCustomization kpfc in m_vForms) + { + if(kpfc.FullName == strTypeName) + { + kpfc.ApplyTo(form); + break; + } + } + + if(m_props.RightToLeft) + { + try { RtlApplyToControls(form.Controls); } + catch(Exception) { Debug.Assert(false); } + } + } + + private static void RtlApplyToControls(Control.ControlCollection cc) + { + foreach(Control c in cc) + { + if(c.Controls.Count > 0) RtlApplyToControls(c.Controls); + + if(c is DateTimePicker) + ((DateTimePicker)c).RightToLeftLayout = true; + else if(c is ListView) + ((ListView)c).RightToLeftLayout = true; + else if(c is MonthCalendar) + ((MonthCalendar)c).RightToLeftLayout = true; + else if(c is ProgressBar) + ((ProgressBar)c).RightToLeftLayout = true; + else if(c is TabControl) + ((TabControl)c).RightToLeftLayout = true; + else if(c is TrackBar) + ((TrackBar)c).RightToLeftLayout = true; + else if(c is TreeView) + ((TreeView)c).RightToLeftLayout = true; + else if(c is ToolStrip) + RtlApplyToToolStripItems(((ToolStrip)c).Items); + + if((c is GroupBox) || (c is Panel)) RtlMoveChildControls(c); + } + } + + private static void RtlMoveChildControls(Control cParent) + { + int nParentWidth = cParent.Size.Width; + + foreach(Control c in cParent.Controls) + { + Point ptCur = c.Location; + c.Location = new Point(nParentWidth - c.Size.Width - ptCur.X, ptCur.Y); + } + } + + private static void RtlApplyToToolStripItems(ToolStripItemCollection tsic) + { + foreach(ToolStripItem tsi in tsic) + { + tsi.RightToLeftAutoMirrorImage = true; + } + } + + public void ApplyTo(string strTableName, ToolStripItemCollection tsic) + { + if(tsic == null) throw new ArgumentNullException("tsic"); + + KPStringTable kpst = null; + foreach(KPStringTable kpstEnum in m_vStringTables) + { + if(kpstEnum.Name == strTableName) + { + kpst = kpstEnum; + break; + } + } + + if(kpst != null) kpst.ApplyTo(tsic); + } +#endif*/ + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Translation/KPTranslationProperties.cs b/src/KeePassLib2AndroidSdkStyle/Translation/KPTranslationProperties.cs new file mode 100644 index 00000000..01e511bf --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Translation/KPTranslationProperties.cs @@ -0,0 +1,105 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace KeePassLib.Translation +{ + public sealed class KPTranslationProperties + { + private string m_strApp = string.Empty; + public string Application + { + get { return m_strApp; } + set { m_strApp = value; } + } + + private string m_strForVersion = PwDefs.VersionString; + public string ApplicationVersion + { + get { return m_strForVersion; } + set { m_strForVersion = value; } + } + + private string m_strNameEnglish = string.Empty; + public string NameEnglish + { + get { return m_strNameEnglish; } + set { m_strNameEnglish = value; } + } + + private string m_strNameNative = string.Empty; + public string NameNative + { + get { return m_strNameNative; } + set { m_strNameNative = value; } + } + + private string m_strIso6391Code = string.Empty; + public string Iso6391Code + { + get { return m_strIso6391Code; } + set { m_strIso6391Code = value; } + } + + private bool m_bRtl = false; + public bool RightToLeft + { + get { return m_bRtl; } + set { m_bRtl = value; } + } + + private string m_strAuthorName = string.Empty; + public string AuthorName + { + get { return m_strAuthorName; } + set { m_strAuthorName = value; } + } + + private string m_strAuthorContact = string.Empty; + public string AuthorContact + { + get { return m_strAuthorContact; } + set { m_strAuthorContact = value; } + } + + private string m_strGen = string.Empty; + public string Generator + { + get { return m_strGen; } + set { m_strGen = value; } + } + + private string m_strUuid = string.Empty; + public string FileUuid + { + get { return m_strUuid; } + set { m_strUuid = value; } + } + + private string m_strLastModified = string.Empty; + public string LastModified + { + get { return m_strLastModified; } + set { m_strLastModified = value; } + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Utility/AppLogEx.cs b/src/KeePassLib2AndroidSdkStyle/Utility/AppLogEx.cs new file mode 100644 index 00000000..53dcf09f --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Utility/AppLogEx.cs @@ -0,0 +1,103 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +#if !KeePassLibSD +using System.IO.Compression; +#endif + +namespace KeePassLib.Utility +{ + /// + /// Application-wide logging services. + /// + public static class AppLogEx + { + private static StreamWriter m_swOut = null; + + public static void Open(string strPrefix) + { + // Logging is not enabled in normal builds of KeePass! + /* + AppLogEx.Close(); + + Debug.Assert(strPrefix != null); + if(strPrefix == null) strPrefix = "Log"; + + try + { + string strDirSep = string.Empty; + strDirSep += UrlUtil.LocalDirSepChar; + + string strTemp = UrlUtil.GetTempPath(); + if(!strTemp.EndsWith(strDirSep)) + strTemp += strDirSep; + + string strPath = strTemp + strPrefix + "-"; + Debug.Assert(strPath.IndexOf('/') < 0); + + DateTime dtNow = DateTime.UtcNow; + string strTime = dtNow.ToString("s"); + strTime = strTime.Replace('T', '-'); + strTime = strTime.Replace(':', '-'); + + strPath += strTime + "-" + Environment.TickCount.ToString( + NumberFormatInfo.InvariantInfo) + ".log.gz"; + + FileStream fsOut = new FileStream(strPath, FileMode.Create, + FileAccess.Write, FileShare.None); + GZipStream gz = new GZipStream(fsOut, CompressionMode.Compress); + m_swOut = new StreamWriter(gz); + + AppLogEx.Log("Started logging on " + dtNow.ToString("s") + "."); + } + catch(Exception) { Debug.Assert(false); } + */ + } + + public static void Close() + { + if(m_swOut == null) return; + + m_swOut.Close(); + m_swOut = null; + } + + public static void Log(string strText) + { + if(m_swOut == null) return; + + if(strText == null) m_swOut.WriteLine(); + else m_swOut.WriteLine(strText); + } + + public static void Log(Exception ex) + { + if(m_swOut == null) return; + + if(ex == null) m_swOut.WriteLine(); + else m_swOut.WriteLine(ex.ToString()); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Utility/GfxUtil.cs b/src/KeePassLib2AndroidSdkStyle/Utility/GfxUtil.cs new file mode 100644 index 00000000..fd0343bf --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Utility/GfxUtil.cs @@ -0,0 +1,82 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2013 Dominik Reichl + + Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Drawing; + +using System.Diagnostics; +using Android.Graphics; + +namespace KeePassLib.Utility +{ + public static class GfxUtil + { + public static Android.Graphics.Bitmap LoadImage(byte[] pb) + { + if(pb == null) throw new ArgumentNullException("pb"); + + MemoryStream ms = new MemoryStream(pb, false); + try { return LoadImagePriv(ms); } + catch(Exception) + { + Android.Graphics.Bitmap imgIco = TryLoadIco(pb); + if(imgIco != null) return imgIco; + throw; + } + finally { ms.Close(); } + } + + private static Android.Graphics.Bitmap LoadImagePriv(Stream s) + { + Android.Graphics.Bitmap img = null; + +#if !KeePassLibSD + + img = BitmapFactory.DecodeStream(s); + +#else + imgSrc = new Bitmap(s); + Bitmap bmp = new Bitmap(imgSrc.Width, imgSrc.Height); +#endif + + + return img; + + } + + private static Android.Graphics.Bitmap TryLoadIco(byte[] pb) + { +#if !KeePassLibSD + throw new NotImplementedException(); + /* + MemoryStream ms = new MemoryStream(pb, false); + try { return (new Icon(ms)).ToBitmap(); } + catch(Exception) { } + finally { ms.Close(); }*/ +#endif + + //return null; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Utility/MemUtil.cs b/src/KeePassLib2AndroidSdkStyle/Utility/MemUtil.cs new file mode 100644 index 00000000..0fe591ff --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Utility/MemUtil.cs @@ -0,0 +1,813 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text; + +#if KeePassLibSD +using KeePassLibSD; +#else +using System.IO.Compression; +#endif + +namespace KeePassLib.Utility +{ + /// + /// Contains static buffer manipulation and string conversion routines. + /// + public static class MemUtil + { + public static readonly byte[] EmptyByteArray = new byte[0]; + + private static readonly uint[] m_vSBox = new uint[256] { + 0xCD2FACB3, 0xE78A7F5C, 0x6F0803FC, 0xBCF6E230, + 0x3A321712, 0x06403DB1, 0xD2F84B95, 0xDF22A6E4, + 0x07CE9E5B, 0x31788A0C, 0xF683F6F4, 0xEA061F49, + 0xFA5C2ACA, 0x4B9E494E, 0xB0AB25BA, 0x767731FC, + 0x261893A7, 0x2B09F2CE, 0x046261E4, 0x41367B4B, + 0x18A7F225, 0x8F923C0E, 0x5EF3A325, 0x28D0435E, + 0x84C22919, 0xED66873C, 0x8CEDE444, 0x7FC47C24, + 0xFCFC6BA3, 0x676F928D, 0xB4147187, 0xD8FB126E, + 0x7D798D17, 0xFF82E424, 0x1712FA5B, 0xABB09DD5, + 0x8156BA63, 0x84E4D969, 0xC937FB9A, 0x2F1E5BFC, + 0x178ECA11, 0x0E71CD5F, 0x52AAC6F4, 0x71EEFC8F, + 0x7090D749, 0x21CACA31, 0x92996378, 0x0939A8A8, + 0xE9EE1934, 0xD2718616, 0xF2500543, 0xB911873C, + 0xD3CB3EEC, 0x2BA0DBEB, 0xB42D0A27, 0xECE67C0F, + 0x302925F0, 0x6114F839, 0xD39E6307, 0xE28970D6, + 0xEB982F99, 0x941B4CDF, 0xC540E550, 0x8124FC45, + 0x98B025C7, 0xE2BF90EA, 0x4F57C976, 0xCF546FE4, + 0x59566DC8, 0xE3F4360D, 0xF5F9D231, 0xD6180B22, + 0xB54E088A, 0xB5DFE6A6, 0x3637A36F, 0x056E9284, + 0xAFF8FBC5, 0x19E01648, 0x8611F043, 0xDAE44337, + 0xF61B6A1C, 0x257ACD9E, 0xDD35F507, 0xEF05CAFA, + 0x05EB4A83, 0xFC25CA92, 0x0A4728E6, 0x9CF150EF, + 0xAEEF67DE, 0xA9472337, 0x57C81EFE, 0x3E5E009F, + 0x02CB03BB, 0x2BA85674, 0xF21DC251, 0x78C34A34, + 0xABB1F5BF, 0xB95A2FBD, 0x1FB47777, 0x9A96E8AC, + 0x5D2D2838, 0x55AAC92A, 0x99EE324E, 0x10F6214B, + 0x58ABDFB1, 0x2008794D, 0xBEC880F0, 0xE75E5341, + 0x88015C34, 0x352D8FBF, 0x622B7F6C, 0xF5C59EA2, + 0x1F759D8E, 0xADE56159, 0xCC7B4C25, 0x5B8BC48C, + 0xB6BD15AF, 0x3C5B5110, 0xE74A7C3D, 0xEE613161, + 0x156A1C67, 0x72C06817, 0xEA0A6F69, 0x4CECF993, + 0xCA9D554C, 0x8E20361F, 0x42D396B9, 0x595DE578, + 0x749D7955, 0xFD1BA5FD, 0x81FC160E, 0xDB97E28C, + 0x7CF148F7, 0x0B0B3CF5, 0x534DE605, 0x46421066, + 0xD4B68DD1, 0x9E479CE6, 0xAE667A9D, 0xBC082082, + 0xB06DD6EF, 0x20F0F23F, 0xB99E1551, 0xF47A2E3A, + 0x71DA50C6, 0x67B65779, 0x2A8CB376, 0x1EA71EEE, + 0x29ABCD50, 0xB6EB0C6B, 0x23C10511, 0x6F3F2144, + 0x6AF23012, 0xF696BD9E, 0xB94099D8, 0xAD5A9C81, + 0x7A0794FA, 0x7EDF59D6, 0x1E72E574, 0x8561913C, + 0x4E4D568F, 0xEECB9928, 0x9C124D2E, 0x0848B82C, + 0xF1CA395F, 0x9DAF43DC, 0xF77EC323, 0x394E9B59, + 0x7E200946, 0x8B811D68, 0x16DA3305, 0xAB8DE2C3, + 0xE6C53B64, 0x98C2D321, 0x88A97D81, 0xA7106419, + 0x8E52F7BF, 0x8ED262AF, 0x7CCA974E, 0xF0933241, + 0x040DD437, 0xE143B3D4, 0x3019F56F, 0xB741521D, + 0xF1745362, 0x4C435F9F, 0xB4214D0D, 0x0B0C348B, + 0x5051D189, 0x4C30447E, 0x7393D722, 0x95CEDD0B, + 0xDD994E80, 0xC3D22ED9, 0x739CD900, 0x131EB9C4, + 0xEF1062B2, 0x4F0DE436, 0x52920073, 0x9A7F3D80, + 0x896E7B1B, 0x2C8BBE5A, 0xBD304F8A, 0xA993E22C, + 0x134C41A0, 0xFA989E00, 0x39CE9726, 0xFB89FCCF, + 0xE8FBAC97, 0xD4063FFC, 0x935A2B5A, 0x44C8EE83, + 0xCB2BC7B6, 0x02989E92, 0x75478BEA, 0x144378D0, + 0xD853C087, 0x8897A34E, 0xDD23629D, 0xBDE2A2A2, + 0x581D8ECC, 0x5DA8AEE8, 0xFF8AAFD0, 0xBA2BCF6E, + 0x4BD98DAC, 0xF2EDB9E4, 0xFA2DC868, 0x47E84661, + 0xECEB1C7D, 0x41705CA4, 0x5982E4D4, 0xEB5204A1, + 0xD196CAFB, 0x6414804D, 0x3ABD4B46, 0x8B494C26, + 0xB432D52B, 0x39C5356B, 0x6EC80BF7, 0x71BE5483, + 0xCEC4A509, 0xE9411D61, 0x52F341E5, 0xD2E6197B, + 0x4F02826C, 0xA9E48838, 0xD1F8F247, 0xE4957FB3, + 0x586CCA99, 0x9A8B6A5B, 0x4998FBEA, 0xF762BE4C, + 0x90DFE33C, 0x9731511E, 0x88C6A82F, 0xDD65A4D4 + }; + + /// + /// Convert a hexadecimal string to a byte array. The input string must be + /// even (i.e. its length is a multiple of 2). + /// + /// String containing hexadecimal characters. + /// Returns a byte array. Returns null if the string parameter + /// was null or is an uneven string (i.e. if its length isn't a + /// multiple of 2). + /// Thrown if + /// is null. + public static byte[] HexStringToByteArray(string strHex) + { + if(strHex == null) { Debug.Assert(false); throw new ArgumentNullException("strHex"); } + + int nStrLen = strHex.Length; + if((nStrLen & 1) != 0) { Debug.Assert(false); return null; } + + byte[] pb = new byte[nStrLen / 2]; + byte bt; + char ch; + + for(int i = 0; i < nStrLen; i += 2) + { + ch = strHex[i]; + + if((ch >= '0') && (ch <= '9')) + bt = (byte)(ch - '0'); + else if((ch >= 'a') && (ch <= 'f')) + bt = (byte)(ch - 'a' + 10); + else if((ch >= 'A') && (ch <= 'F')) + bt = (byte)(ch - 'A' + 10); + else { Debug.Assert(false); bt = 0; } + + bt <<= 4; + + ch = strHex[i + 1]; + if((ch >= '0') && (ch <= '9')) + bt += (byte)(ch - '0'); + else if((ch >= 'a') && (ch <= 'f')) + bt += (byte)(ch - 'a' + 10); + else if((ch >= 'A') && (ch <= 'F')) + bt += (byte)(ch - 'A' + 10); + else { Debug.Assert(false); } + + pb[i >> 1] = bt; + } + + return pb; + } + + /// + /// Convert a byte array to a hexadecimal string. + /// + /// Input byte array. + /// Returns the hexadecimal string representing the byte + /// array. Returns null, if the input byte array was null. Returns + /// an empty string, if the input byte array has length 0. + public static string ByteArrayToHexString(byte[] pbArray) + { + if(pbArray == null) return null; + + int nLen = pbArray.Length; + if(nLen == 0) return string.Empty; + + StringBuilder sb = new StringBuilder(); + + byte bt, btHigh, btLow; + for(int i = 0; i < nLen; ++i) + { + bt = pbArray[i]; + btHigh = bt; btHigh >>= 4; + btLow = (byte)(bt & 0x0F); + + if(btHigh >= 10) sb.Append((char)('A' + btHigh - 10)); + else sb.Append((char)('0' + btHigh)); + + if(btLow >= 10) sb.Append((char)('A' + btLow - 10)); + else sb.Append((char)('0' + btLow)); + } + + return sb.ToString(); + } + + /// + /// Decode Base32 strings according to RFC 4648. + /// + public static byte[] ParseBase32(string str) + { + if((str == null) || ((str.Length % 8) != 0)) + { + Debug.Assert(false); + return null; + } + + ulong uMaxBits = (ulong)str.Length * 5UL; + List l = new List((int)(uMaxBits / 8UL) + 1); + Debug.Assert(l.Count == 0); + + for(int i = 0; i < str.Length; i += 8) + { + ulong u = 0; + int nBits = 0; + + for(int j = 0; j < 8; ++j) + { + char ch = str[i + j]; + if(ch == '=') break; + + ulong uValue; + if((ch >= 'A') && (ch <= 'Z')) + uValue = (ulong)(ch - 'A'); + else if((ch >= 'a') && (ch <= 'z')) + uValue = (ulong)(ch - 'a'); + else if((ch >= '2') && (ch <= '7')) + uValue = (ulong)(ch - '2') + 26UL; + else { Debug.Assert(false); return null; } + + u <<= 5; + u += uValue; + nBits += 5; + } + + int nBitsTooMany = (nBits % 8); + u >>= nBitsTooMany; + nBits -= nBitsTooMany; + Debug.Assert((nBits % 8) == 0); + + int idxNewBytes = l.Count; + while(nBits > 0) + { + l.Add((byte)(u & 0xFF)); + u >>= 8; + nBits -= 8; + } + l.Reverse(idxNewBytes, l.Count - idxNewBytes); + } + + return l.ToArray(); + } + + /// + /// Set all bytes in a byte array to zero. + /// + /// Input array. All bytes of this array + /// will be set to zero. +#if KeePassLibSD + [MethodImpl(MethodImplOptions.NoInlining)] +#else + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] +#endif + public static void ZeroByteArray(byte[] pbArray) + { + Debug.Assert(pbArray != null); + if(pbArray == null) throw new ArgumentNullException("pbArray"); + + Array.Clear(pbArray, 0, pbArray.Length); + } + + /// + /// Set all elements of an array to the default value. + /// + /// Input array. +#if KeePassLibSD + [MethodImpl(MethodImplOptions.NoInlining)] +#else + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] +#endif + public static void ZeroArray(T[] v) + { + if(v == null) { Debug.Assert(false); throw new ArgumentNullException("v"); } + + Array.Clear(v, 0, v.Length); + } + + /// + /// Convert 2 bytes to a 16-bit unsigned integer (little-endian). + /// + public static ushort BytesToUInt16(byte[] pb) + { + Debug.Assert((pb != null) && (pb.Length == 2)); + if(pb == null) throw new ArgumentNullException("pb"); + if(pb.Length != 2) throw new ArgumentOutOfRangeException("pb"); + + return (ushort)((ushort)pb[0] | ((ushort)pb[1] << 8)); + } + + /// + /// Convert 2 bytes to a 16-bit unsigned integer (little-endian). + /// + public static ushort BytesToUInt16(byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 1) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + return (ushort)((ushort)pb[iOffset] | ((ushort)pb[iOffset + 1] << 8)); + } + + /// + /// Convert 4 bytes to a 32-bit unsigned integer (little-endian). + /// + public static uint BytesToUInt32(byte[] pb) + { + Debug.Assert((pb != null) && (pb.Length == 4)); + if(pb == null) throw new ArgumentNullException("pb"); + if(pb.Length != 4) throw new ArgumentOutOfRangeException("pb"); + + return ((uint)pb[0] | ((uint)pb[1] << 8) | ((uint)pb[2] << 16) | + ((uint)pb[3] << 24)); + } + + /// + /// Convert 4 bytes to a 32-bit unsigned integer (little-endian). + /// + public static uint BytesToUInt32(byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 3) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + return ((uint)pb[iOffset] | ((uint)pb[iOffset + 1] << 8) | + ((uint)pb[iOffset + 2] << 16) | ((uint)pb[iOffset + 3] << 24)); + } + + /// + /// Convert 8 bytes to a 64-bit unsigned integer (little-endian). + /// + public static ulong BytesToUInt64(byte[] pb) + { + Debug.Assert((pb != null) && (pb.Length == 8)); + if(pb == null) throw new ArgumentNullException("pb"); + if(pb.Length != 8) throw new ArgumentOutOfRangeException("pb"); + + return ((ulong)pb[0] | ((ulong)pb[1] << 8) | ((ulong)pb[2] << 16) | + ((ulong)pb[3] << 24) | ((ulong)pb[4] << 32) | ((ulong)pb[5] << 40) | + ((ulong)pb[6] << 48) | ((ulong)pb[7] << 56)); + } + + /// + /// Convert 8 bytes to a 64-bit unsigned integer (little-endian). + /// + public static ulong BytesToUInt64(byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 7) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + // if(BitConverter.IsLittleEndian) + // return BitConverter.ToUInt64(pb, iOffset); + + return ((ulong)pb[iOffset] | ((ulong)pb[iOffset + 1] << 8) | + ((ulong)pb[iOffset + 2] << 16) | ((ulong)pb[iOffset + 3] << 24) | + ((ulong)pb[iOffset + 4] << 32) | ((ulong)pb[iOffset + 5] << 40) | + ((ulong)pb[iOffset + 6] << 48) | ((ulong)pb[iOffset + 7] << 56)); + } + + public static int BytesToInt32(byte[] pb) + { + return (int)BytesToUInt32(pb); + } + + public static int BytesToInt32(byte[] pb, int iOffset) + { + return (int)BytesToUInt32(pb, iOffset); + } + + public static long BytesToInt64(byte[] pb) + { + return (long)BytesToUInt64(pb); + } + + public static long BytesToInt64(byte[] pb, int iOffset) + { + return (long)BytesToUInt64(pb, iOffset); + } + + /// + /// Convert a 16-bit unsigned integer to 2 bytes (little-endian). + /// + public static byte[] UInt16ToBytes(ushort uValue) + { + byte[] pb = new byte[2]; + + unchecked + { + pb[0] = (byte)uValue; + pb[1] = (byte)(uValue >> 8); + } + + return pb; + } + + /// + /// Convert a 32-bit unsigned integer to 4 bytes (little-endian). + /// + public static byte[] UInt32ToBytes(uint uValue) + { + byte[] pb = new byte[4]; + + unchecked + { + pb[0] = (byte)uValue; + pb[1] = (byte)(uValue >> 8); + pb[2] = (byte)(uValue >> 16); + pb[3] = (byte)(uValue >> 24); + } + + return pb; + } + + /// + /// Convert a 32-bit unsigned integer to 4 bytes (little-endian). + /// + public static void UInt32ToBytesEx(uint uValue, byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 3) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + unchecked + { + pb[iOffset] = (byte)uValue; + pb[iOffset + 1] = (byte)(uValue >> 8); + pb[iOffset + 2] = (byte)(uValue >> 16); + pb[iOffset + 3] = (byte)(uValue >> 24); + } + } + + /// + /// Convert a 64-bit unsigned integer to 8 bytes (little-endian). + /// + public static byte[] UInt64ToBytes(ulong uValue) + { + byte[] pb = new byte[8]; + + unchecked + { + pb[0] = (byte)uValue; + pb[1] = (byte)(uValue >> 8); + pb[2] = (byte)(uValue >> 16); + pb[3] = (byte)(uValue >> 24); + pb[4] = (byte)(uValue >> 32); + pb[5] = (byte)(uValue >> 40); + pb[6] = (byte)(uValue >> 48); + pb[7] = (byte)(uValue >> 56); + } + + return pb; + } + + /// + /// Convert a 64-bit unsigned integer to 8 bytes (little-endian). + /// + public static void UInt64ToBytesEx(ulong uValue, byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 7) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + unchecked + { + pb[iOffset] = (byte)uValue; + pb[iOffset + 1] = (byte)(uValue >> 8); + pb[iOffset + 2] = (byte)(uValue >> 16); + pb[iOffset + 3] = (byte)(uValue >> 24); + pb[iOffset + 4] = (byte)(uValue >> 32); + pb[iOffset + 5] = (byte)(uValue >> 40); + pb[iOffset + 6] = (byte)(uValue >> 48); + pb[iOffset + 7] = (byte)(uValue >> 56); + } + } + + public static byte[] Int32ToBytes(int iValue) + { + return UInt32ToBytes((uint)iValue); + } + + public static byte[] Int64ToBytes(long lValue) + { + return UInt64ToBytes((ulong)lValue); + } + + public static uint RotateLeft32(uint u, int nBits) + { + return ((u << nBits) | (u >> (32 - nBits))); + } + + public static uint RotateRight32(uint u, int nBits) + { + return ((u >> nBits) | (u << (32 - nBits))); + } + + public static ulong RotateLeft64(ulong u, int nBits) + { + return ((u << nBits) | (u >> (64 - nBits))); + } + + public static ulong RotateRight64(ulong u, int nBits) + { + return ((u >> nBits) | (u << (64 - nBits))); + } + + public static bool ArraysEqual(byte[] x, byte[] y) + { + // Return false if one of them is null (not comparable)! + if((x == null) || (y == null)) { Debug.Assert(false); return false; } + + if(x.Length != y.Length) return false; + + for(int i = 0; i < x.Length; ++i) + { + if(x[i] != y[i]) return false; + } + + return true; + } + + public static void XorArray(byte[] pbSource, int iSourceOffset, + byte[] pbBuffer, int iBufferOffset, int cb) + { + if(pbSource == null) throw new ArgumentNullException("pbSource"); + if(iSourceOffset < 0) throw new ArgumentOutOfRangeException("iSourceOffset"); + if(pbBuffer == null) throw new ArgumentNullException("pbBuffer"); + if(iBufferOffset < 0) throw new ArgumentOutOfRangeException("iBufferOffset"); + if(cb < 0) throw new ArgumentOutOfRangeException("cb"); + if(iSourceOffset > (pbSource.Length - cb)) + throw new ArgumentOutOfRangeException("cb"); + if(iBufferOffset > (pbBuffer.Length - cb)) + throw new ArgumentOutOfRangeException("cb"); + + for(int i = 0; i < cb; ++i) + pbBuffer[iBufferOffset + i] ^= pbSource[iSourceOffset + i]; + } + + /// + /// Fast hash that can be used e.g. for hash tables. + /// The algorithm might change in the future; do not store + /// the hashes for later use. + /// + public static uint Hash32(byte[] v, int iStart, int iLength) + { + uint u = 0x326F637B; + + if(v == null) { Debug.Assert(false); return u; } + if(iStart < 0) { Debug.Assert(false); return u; } + if(iLength < 0) { Debug.Assert(false); return u; } + + int m = iStart + iLength; + if(m > v.Length) { Debug.Assert(false); return u; } + + for(int i = iStart; i < m; ++i) + { + u ^= m_vSBox[v[i]]; + u *= 3; + } + + return u; + } + + public static void CopyStream(Stream sSource, Stream sTarget) + { + Debug.Assert((sSource != null) && (sTarget != null)); + if(sSource == null) throw new ArgumentNullException("sSource"); + if(sTarget == null) throw new ArgumentNullException("sTarget"); + + const int nBufSize = 4096; + byte[] pbBuf = new byte[nBufSize]; + + while(true) + { + int nRead = sSource.Read(pbBuf, 0, nBufSize); + if(nRead == 0) break; + + sTarget.Write(pbBuf, 0, nRead); + } + + // Do not close any of the streams + } + + public static byte[] Read(Stream s, int nCount) + { + if(s == null) throw new ArgumentNullException("s"); + if(nCount < 0) throw new ArgumentOutOfRangeException("nCount"); + + byte[] pb = new byte[nCount]; + int iOffset = 0; + while(nCount > 0) + { + int iRead = s.Read(pb, iOffset, nCount); + if(iRead == 0) break; + + iOffset += iRead; + nCount -= iRead; + } + + if(iOffset != pb.Length) + { + byte[] pbPart = new byte[iOffset]; + Array.Copy(pb, pbPart, iOffset); + return pbPart; + } + + return pb; + } + + public static void Write(Stream s, byte[] pbData) + { + if(s == null) { Debug.Assert(false); return; } + if(pbData == null) { Debug.Assert(false); return; } + + Debug.Assert(pbData.Length >= 0); + if(pbData.Length > 0) s.Write(pbData, 0, pbData.Length); + } + + public static byte[] Compress(byte[] pbData) + { + if(pbData == null) throw new ArgumentNullException("pbData"); + if(pbData.Length == 0) return pbData; + + byte[] pbCompressed; + using(MemoryStream msSource = new MemoryStream(pbData, false)) + { + using(MemoryStream msCompressed = new MemoryStream()) + { + using(GZipStream gz = new GZipStream(msCompressed, + CompressionMode.Compress)) + { + MemUtil.CopyStream(msSource, gz); + } + + pbCompressed = msCompressed.ToArray(); + } + } + + return pbCompressed; + } + + public static byte[] Decompress(byte[] pbCompressed) + { + if(pbCompressed == null) throw new ArgumentNullException("pbCompressed"); + if(pbCompressed.Length == 0) return pbCompressed; + + byte[] pbData; + using(MemoryStream msData = new MemoryStream()) + { + using(MemoryStream msCompressed = new MemoryStream(pbCompressed, false)) + { + using(GZipStream gz = new GZipStream(msCompressed, + CompressionMode.Decompress)) + { + MemUtil.CopyStream(gz, msData); + } + } + + pbData = msData.ToArray(); + } + + return pbData; + } + + public static int IndexOf(T[] vHaystack, T[] vNeedle) + where T : IEquatable + { + if(vHaystack == null) throw new ArgumentNullException("vHaystack"); + if(vNeedle == null) throw new ArgumentNullException("vNeedle"); + if(vNeedle.Length == 0) return 0; + + for(int i = 0; i <= (vHaystack.Length - vNeedle.Length); ++i) + { + bool bFound = true; + for(int m = 0; m < vNeedle.Length; ++m) + { + if(!vHaystack[i + m].Equals(vNeedle[m])) + { + bFound = false; + break; + } + } + if(bFound) return i; + } + + return -1; + } + + public static T[] Mid(T[] v, int iOffset, int iLength) + { + if(v == null) throw new ArgumentNullException("v"); + if(iOffset < 0) throw new ArgumentOutOfRangeException("iOffset"); + if(iLength < 0) throw new ArgumentOutOfRangeException("iLength"); + if((iOffset + iLength) > v.Length) throw new ArgumentException(); + + T[] r = new T[iLength]; + Array.Copy(v, iOffset, r, 0, iLength); + return r; + } + + public static IEnumerable Union(IEnumerable a, IEnumerable b, + IEqualityComparer cmp) + { + if(a == null) throw new ArgumentNullException("a"); + if(b == null) throw new ArgumentNullException("b"); + + Dictionary d = ((cmp != null) ? + (new Dictionary(cmp)) : (new Dictionary())); + + foreach(T ta in a) + { + if(d.ContainsKey(ta)) continue; // Prevent duplicates + + d[ta] = true; + yield return ta; + } + + foreach(T tb in b) + { + if(d.ContainsKey(tb)) continue; // Prevent duplicates + + d[tb] = true; + yield return tb; + } + + yield break; + } + + public static IEnumerable Intersect(IEnumerable a, IEnumerable b, + IEqualityComparer cmp) + { + if(a == null) throw new ArgumentNullException("a"); + if(b == null) throw new ArgumentNullException("b"); + + Dictionary d = ((cmp != null) ? + (new Dictionary(cmp)) : (new Dictionary())); + + foreach(T tb in b) { d[tb] = true; } + + foreach(T ta in a) + { + if(d.Remove(ta)) // Prevent duplicates + yield return ta; + } + + yield break; + } + + public static IEnumerable Except(IEnumerable a, IEnumerable b, + IEqualityComparer cmp) + { + if(a == null) throw new ArgumentNullException("a"); + if(b == null) throw new ArgumentNullException("b"); + + Dictionary d = ((cmp != null) ? + (new Dictionary(cmp)) : (new Dictionary())); + + foreach(T tb in b) { d[tb] = true; } + + foreach(T ta in a) + { + if(d.ContainsKey(ta)) continue; + + d[ta] = true; // Prevent duplicates + yield return ta; + } + + yield break; + } + internal static bool ListsEqual(List a, List b) + where T : class, IEquatable + { + if (object.ReferenceEquals(a, b)) return true; + if ((a == null) || (b == null)) return false; + + int n = a.Count; + if (n != b.Count) return false; + + for (int i = 0; i < n; ++i) + { + T tA = a[i], tB = b[i]; + + if (tA == null) + { + if (tB != null) return false; + } + else if (tB == null) return false; + else if (!tA.Equals(tB)) return false; + } + + return true; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Utility/MessageService.cs b/src/KeePassLib2AndroidSdkStyle/Utility/MessageService.cs new file mode 100644 index 00000000..fd9cd033 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Utility/MessageService.cs @@ -0,0 +1,462 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2013 Dominik Reichl + + Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Text; + +using System.Diagnostics; + +using KeePassLib.Resources; +using KeePassLib.Serialization; +using Android.Webkit; + +namespace KeePassLib.Utility +{ + public enum MessageBoxButtons + { + OK, OKCancel, AbortRetryIgnore, YesNoCancel, YesNo, RetryCancel + } + public enum MessageBoxIcon + { + Information, Warning, Error, Question + } + public enum MessageBoxDefaultButton + { + Button1, Button2, Button3 + } + + public enum DialogResult + { + Yes, No, Cancel, Retry, Abort + } + + + public sealed class MessageServiceEventArgs : EventArgs + { + private string m_strTitle = string.Empty; + private string m_strText = string.Empty; + //private MessageBoxButtons m_msgButtons = MessageBoxButtons.OK; + //private MessageBoxIcon m_msgIcon = MessageBoxIcon.None; + + public string Title { get { return m_strTitle; } } + public string Text { get { return m_strText; } } + //public MessageBoxButtons Buttons { get { return m_msgButtons; } } + //public MessageBoxIcon Icon { get { return m_msgIcon; } } + + public MessageServiceEventArgs() { } + + public MessageServiceEventArgs(string strTitle, string strText, + MessageBoxButtons msgButtons, MessageBoxIcon msgIcon) + { + m_strTitle = (strTitle ?? string.Empty); + m_strText = (strText ?? string.Empty); + + } + } + + public static class MessageService + { + private static volatile uint m_uCurrentMessageCount = 0; + +#if !KeePassLibSD + private const MessageBoxIcon m_mbiInfo = MessageBoxIcon.Information; + private const MessageBoxIcon m_mbiWarning = MessageBoxIcon.Warning; + private const MessageBoxIcon m_mbiFatal = MessageBoxIcon.Error; + +#else + private const MessageBoxIcon m_mbiInfo = MessageBoxIcon.Asterisk; + private const MessageBoxIcon m_mbiWarning = MessageBoxIcon.Exclamation; + private const MessageBoxIcon m_mbiFatal = MessageBoxIcon.Hand; +#endif + //private const MessageBoxIcon m_mbiQuestion = MessageBoxIcon.Question; + + public static string NewLine + { +#if !KeePassLibSD + get { return Environment.NewLine; } +#else + get { return "\r\n"; } +#endif + } + + public static string NewParagraph + { +#if !KeePassLibSD + get { return Environment.NewLine + Environment.NewLine; } +#else + get { return "\r\n\r\n"; } +#endif + } + + public static uint CurrentMessageCount + { + get { return m_uCurrentMessageCount; } + } + + public static event EventHandler MessageShowing; + + private static string ObjectsToMessage(object[] vLines) + { + return ObjectsToMessage(vLines, false); + } + + private static string ObjectsToMessage(object[] vLines, bool bFullExceptions) + { + if (vLines == null) return string.Empty; + + string strNewPara = MessageService.NewParagraph; + + StringBuilder sbText = new StringBuilder(); + bool bSeparator = false; + + foreach (object obj in vLines) + { + if (obj == null) continue; + + string strAppend = null; + + Exception exObj = (obj as Exception); + string strObj = (obj as string); +#if (!KeePassLibSD && !KeePassRT) + StringCollection scObj = (obj as StringCollection); +#endif + + if (exObj != null) + { + if (bFullExceptions) + strAppend = StrUtil.FormatException(exObj); + else if ((exObj.Message != null) && (exObj.Message.Length > 0)) + strAppend = exObj.Message; + } +#if (!KeePassLibSD && !KeePassRT) + else if (scObj != null) + { + StringBuilder sb = new StringBuilder(); + foreach (string strCollLine in scObj) + { + if (sb.Length > 0) sb.AppendLine(); + sb.Append(strCollLine.TrimEnd()); + } + strAppend = sb.ToString(); + } +#endif + else if (strObj != null) + strAppend = strObj; + else + strAppend = obj.ToString(); + + if (!string.IsNullOrEmpty(strAppend)) + { + if (bSeparator) sbText.Append(strNewPara); + else bSeparator = true; + + sbText.Append(strAppend); + } + } + + return sbText.ToString(); + } + +#if (!KeePassLibSD && !KeePassRT) + /*internal static Form GetTopForm() + { + FormCollection fc = Application.OpenForms; + if((fc == null) || (fc.Count == 0)) return null; + + return fc[fc.Count - 1]; + }*/ +#endif + + private static DialogResult SafeShowMessageBox(string strText, string strTitle, + MessageBoxButtons mb, MessageBoxIcon mi, MessageBoxDefaultButton mdb) + { +#if (KeePassLibSD || KeePassRT) + return MessageBox.Show(strText, strTitle, mb, mi, mdb); +#else + + if (mb == MessageBoxButtons.OK) + { + //Android.Widget.Toast toast = .. + } + //this might help: http://www.gregshackles.com/2011/04/using-background-threads-in-mono-for-android-applications/ + throw new NotImplementedException(); + /*IWin32Window wnd = null; + try + { + Form f = GetTopForm(); + if((f != null) && f.InvokeRequired) + return (DialogResult)f.Invoke(new SafeShowMessageBoxInternalDelegate( + SafeShowMessageBoxInternal), f, strText, strTitle, mb, mi, mdb); + else wnd = f; + } + catch(Exception) { Debug.Assert(false); } + + if(wnd == null) + { + if(StrUtil.RightToLeft) + return MessageBox.Show(strText, strTitle, mb, mi, mdb, m_mboRtl); + return MessageBox.Show(strText, strTitle, mb, mi, mdb); + } + + try + { + if(StrUtil.RightToLeft) + return MessageBox.Show(wnd, strText, strTitle, mb, mi, mdb, m_mboRtl); + return MessageBox.Show(wnd, strText, strTitle, mb, mi, mdb); + } + catch(Exception) { Debug.Assert(false); } + + if(StrUtil.RightToLeft) + return MessageBox.Show(strText, strTitle, mb, mi, mdb, m_mboRtl); + return MessageBox.Show(strText, strTitle, mb, mi, mdb); + */ +#endif + } + +#if (!KeePassLibSD && !KeePassRT) + /* internal delegate DialogResult SafeShowMessageBoxInternalDelegate(IWin32Window iParent, + string strText, string strTitle, MessageBoxButtons mb, MessageBoxIcon mi, + MessageBoxDefaultButton mdb); + + internal static DialogResult SafeShowMessageBoxInternal(IWin32Window iParent, + string strText, string strTitle, MessageBoxButtons mb, MessageBoxIcon mi, + MessageBoxDefaultButton mdb) + { + if(StrUtil.RightToLeft) + return MessageBox.Show(iParent, strText, strTitle, mb, mi, mdb, m_mboRtl); + return MessageBox.Show(iParent, strText, strTitle, mb, mi, mdb); + }*/ +#endif + + public static void ShowInfo(params object[] vLines) + { + ShowInfoEx(null, vLines); + } + + public static void ShowInfoEx(string strTitle, params object[] vLines) + { + ++m_uCurrentMessageCount; + + strTitle = (strTitle ?? PwDefs.ShortProductName); + string strText = ObjectsToMessage(vLines); + + if (MessageService.MessageShowing != null) + MessageService.MessageShowing(null, new MessageServiceEventArgs( + strTitle, strText, MessageBoxButtons.OK, m_mbiInfo)); + + SafeShowMessageBox(strText, strTitle, MessageBoxButtons.OK, m_mbiInfo, + MessageBoxDefaultButton.Button1); + + --m_uCurrentMessageCount; + } + + public static void ShowWarning(params object[] vLines) + { + ShowWarningPriv(vLines, false); + } + + internal static void ShowWarningExcp(params object[] vLines) + { + ShowWarningPriv(vLines, true); + } + + private static void ShowWarningPriv(object[] vLines, bool bFullExceptions) + { + ++m_uCurrentMessageCount; + + string strTitle = PwDefs.ShortProductName; + string strText = ObjectsToMessage(vLines, bFullExceptions); + + if (MessageService.MessageShowing != null) + MessageService.MessageShowing(null, new MessageServiceEventArgs( + strTitle, strText, MessageBoxButtons.OK, m_mbiWarning)); + + SafeShowMessageBox(strText, strTitle, MessageBoxButtons.OK, m_mbiWarning, + MessageBoxDefaultButton.Button1); + + --m_uCurrentMessageCount; + } + + public static void ShowFatal(params object[] vLines) + { + ++m_uCurrentMessageCount; + + string strTitle = PwDefs.ShortProductName + @" - " + KLRes.FatalError; + string strText = KLRes.FatalErrorText + MessageService.NewParagraph + + KLRes.ErrorInClipboard + MessageService.NewParagraph + + // Please send it to the KeePass developers. + // KLRes.ErrorFeedbackRequest + MessageService.NewParagraph + + ObjectsToMessage(vLines); + + try + { +#if !KeePassLibSD + /* nicht benoetigt - hoffentlich :-) +Clipboard.Clear(); +Clipboard.SetText(ObjectsToMessage(vLines, true));*/ +#else + Clipboard.SetDataObject(ObjectsToMessage(vLines, true)); +#endif + } + catch (Exception) { Debug.Assert(false); } + + if (MessageService.MessageShowing != null) + MessageService.MessageShowing(null, new MessageServiceEventArgs( + strTitle, strText, MessageBoxButtons.OK, m_mbiFatal)); + + SafeShowMessageBox(strText, strTitle, MessageBoxButtons.OK, m_mbiFatal, + MessageBoxDefaultButton.Button1); + + --m_uCurrentMessageCount; + } + + public static DialogResult Ask(string strText, string strTitle, + MessageBoxButtons mbb) + { + ++m_uCurrentMessageCount; + + string strTextEx = (strText ?? string.Empty); + string strTitleEx = (strTitle ?? PwDefs.ShortProductName); + + if (MessageService.MessageShowing != null) + MessageService.MessageShowing(null, new MessageServiceEventArgs( + strTitleEx, strTextEx, mbb, MessageBoxIcon.Question)); + + DialogResult dr = SafeShowMessageBox(strTextEx, strTitleEx, mbb, + MessageBoxIcon.Question, MessageBoxDefaultButton.Button1); + + --m_uCurrentMessageCount; + return dr; + } + + public static bool AskYesNo(string strText, string strTitle, bool bDefaultToYes, + MessageBoxIcon mbi) + { + ++m_uCurrentMessageCount; + + string strTextEx = (strText ?? string.Empty); + string strTitleEx = (strTitle ?? PwDefs.ShortProductName); + + if (MessageService.MessageShowing != null) + MessageService.MessageShowing(null, new MessageServiceEventArgs( + strTitleEx, strTextEx, MessageBoxButtons.YesNo, MessageBoxIcon.Question)); + + DialogResult dr = SafeShowMessageBox(strTextEx, strTitleEx, + MessageBoxButtons.YesNo, MessageBoxIcon.Question, bDefaultToYes ? + MessageBoxDefaultButton.Button1 : MessageBoxDefaultButton.Button2); + + --m_uCurrentMessageCount; + return (dr == DialogResult.Yes); + } + + public static bool AskYesNo(string strText, string strTitle, bool bDefaultToYes) + { + return AskYesNo(strText, strTitle, bDefaultToYes, MessageBoxIcon.Question); + } + + public static bool AskYesNo(string strText, string strTitle) + { + return AskYesNo(strText, strTitle, true, MessageBoxIcon.Question); + } + + public static bool AskYesNo(string strText) + { + return AskYesNo(strText, null, true, MessageBoxIcon.Question); + } + + public static void ShowLoadWarning(string strFilePath, Exception ex) + { + ShowLoadWarning(strFilePath, ex, false); + } + + public static void ShowLoadWarning(string strFilePath, Exception ex, + bool bFullException) + { + string str = string.Empty; + + if ((strFilePath != null) && (strFilePath.Length > 0)) + str += strFilePath + MessageService.NewParagraph; + + str += KLRes.FileLoadFailed; + + if ((ex != null) && (ex.Message != null) && (ex.Message.Length > 0)) + { + str += MessageService.NewParagraph; + if (!bFullException) str += ex.Message; + else str += ObjectsToMessage(new object[] { ex }, true); + } + + ShowWarning(str); + } + + public static void ShowLoadWarning(IOConnectionInfo ioConnection, Exception ex) + { + if (ioConnection != null) + ShowLoadWarning(UrlUtil.GetFileName(ioConnection.Path), ex, false); + else ShowWarning(ex); + } + + public static void ShowSaveWarning(string strFilePath, Exception ex, + bool bCorruptionWarning) + { + FileLockException fl = (ex as FileLockException); + if (fl != null) + { + ShowWarning(fl.Message); + return; + } + + string str = string.Empty; + if ((strFilePath != null) && (strFilePath.Length > 0)) + str += strFilePath + MessageService.NewParagraph; + + str += KLRes.FileSaveFailed2; + + if ((ex != null) && (ex.Message != null) && (ex.Message.Length > 0)) + str += MessageService.NewParagraph + ex.Message; + + if (bCorruptionWarning) + str += MessageService.NewParagraph + KLRes.FileSaveCorruptionWarning; + + ShowWarning(str); + } + + public static void ShowSaveWarning(IOConnectionInfo ioConnection, Exception ex, + bool bCorruptionWarning) + { + if (ioConnection != null) + ShowSaveWarning(UrlUtil.GetFileName(ioConnection.Path), ex, bCorruptionWarning); + else ShowWarning(ex); + } + + public static void ExternalIncrementMessageCount() + { + ++m_uCurrentMessageCount; + } + + public static void ExternalDecrementMessageCount() + { + --m_uCurrentMessageCount; + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Utility/MonoWorkarounds.cs b/src/KeePassLib2AndroidSdkStyle/Utility/MonoWorkarounds.cs new file mode 100644 index 00000000..10c6ed29 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Utility/MonoWorkarounds.cs @@ -0,0 +1,217 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Xml; + + +using KeePassLib.Native; + +namespace KeePassLib.Utility +{ + public static class MonoWorkarounds + { + private static Dictionary m_dForceReq = new Dictionary(); + private static Thread m_thFixClip = null; + + + private static bool? m_bReq = null; + public static bool IsRequired() + { + if(!m_bReq.HasValue) m_bReq = NativeLib.IsUnix(); + return m_bReq.Value; + } + + // 1219: + // Mono prepends byte order mark (BOM) to StdIn. + // https://sourceforge.net/p/keepass/bugs/1219/ + // 1245: + // Key events not raised while Alt is down, and nav keys out of order. + // https://sourceforge.net/p/keepass/bugs/1245/ + // 1254: + // NumericUpDown bug: text is drawn below up/down buttons. + // https://sourceforge.net/p/keepass/bugs/1254/ + // 1354: + // Finalizer of NotifyIcon throws on Unity. + // https://sourceforge.net/p/keepass/bugs/1354/ + // 1358: + // FileDialog crashes when ~/.recently-used is invalid. + // https://sourceforge.net/p/keepass/bugs/1358/ + // 1366: + // Drawing bug when scrolling a RichTextBox. + // https://sourceforge.net/p/keepass/bugs/1366/ + // 1378: + // Mono doesn't implement Microsoft.Win32.SystemEvents events. + // https://sourceforge.net/p/keepass/bugs/1378/ + // https://github.com/mono/mono/blob/master/mcs/class/System/Microsoft.Win32/SystemEvents.cs + // 1418: + // Minimizing a form while loading it doesn't work. + // https://sourceforge.net/p/keepass/bugs/1418/ + // 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/ + // 5795: + // Text in input field is incomplete. + // https://bugzilla.xamarin.com/show_bug.cgi?id=5795 + // https://sourceforge.net/p/keepass/discussion/329220/thread/d23dc88b/ + // 10163: + // WebRequest GetResponse call missing, breaks WebDAV due to no PUT. + // https://bugzilla.xamarin.com/show_bug.cgi?id=10163 + // https://sourceforge.net/p/keepass/bugs/1117/ + // https://sourceforge.net/p/keepass/discussion/329221/thread/9422258c/ + // https://github.com/mono/mono/commit/8e67b8c2fc7cb66bff7816ebf7c1039fb8cfc43b + // https://bugzilla.xamarin.com/show_bug.cgi?id=1512 + // https://sourceforge.net/p/keepass/patches/89/ + // 12525: + // PictureBox not rendered when bitmap height >= control height. + // https://bugzilla.xamarin.com/show_bug.cgi?id=12525 + // https://sourceforge.net/p/keepass/discussion/329220/thread/54f61e9a/ + // 586901: + // RichTextBox doesn't handle Unicode string correctly. + // https://bugzilla.novell.com/show_bug.cgi?id=586901 + // 620618: + // ListView column headers not drawn. + // https://bugzilla.novell.com/show_bug.cgi?id=620618 + // 649266: + // Calling Control.Hide doesn't remove the application from taskbar. + // https://bugzilla.novell.com/show_bug.cgi?id=649266 + // 686017: + // Minimum sizes must be enforced. + // http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=686017 + // 801414: + // Mono recreates the main window incorrectly. + // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/801414 + // 891029: + // Increase tab control height, otherwise Mono throws exceptions. + // https://sourceforge.net/projects/keepass/forums/forum/329221/topic/4519750 + // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/891029 + // 836428016: + // ListView group header selection unsupported. + // https://sourceforge.net/p/keepass/discussion/329221/thread/31dae0f0/ + // 3574233558: + // Problems with minimizing windows, no content rendered. + // https://sourceforge.net/p/keepass/discussion/329220/thread/d50a79d6/ + // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/801414 + // 891029: + // Increase tab control height, otherwise Mono throws exceptions. + // https://sourceforge.net/projects/keepass/forums/forum/329221/topic/4519750 + // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/891029 + // 836428016: + // ListView group header selection unsupported. + // https://sourceforge.net/p/keepass/discussion/329221/thread/31dae0f0/ + // 3574233558: + // Problems with minimizing windows, no content rendered. + // https://sourceforge.net/p/keepass/discussion/329220/thread/d50a79d6/ + public static bool IsRequired(uint uBugID) + { + if(!MonoWorkarounds.IsRequired()) return false; + + bool bForce; + if(m_dForceReq.TryGetValue(uBugID, out bForce)) return bForce; + + ulong v = NativeLib.MonoVersion; + if(v != 0) + { + if(uBugID == 10163) + return (v >= 0x0002000B00000000UL); // >= 2.11 + } + + return true; + } + + internal static void SetEnabled(string strIDs, bool bEnabled) + { + if(string.IsNullOrEmpty(strIDs)) return; + + string[] vIDs = strIDs.Split(new char[] { ',' }); + foreach(string strID in vIDs) + { + if(string.IsNullOrEmpty(strID)) continue; + + uint uID; + if(StrUtil.TryParseUInt(strID.Trim(), out uID)) + m_dForceReq[uID] = bEnabled; + } + } + + internal static void Initialize() + { + Terminate(); + + // m_fOwnWindow = fOwnWindow; + + + } + + internal static void Terminate() + { + if(m_thFixClip != null) + { + try { m_thFixClip.Abort(); } + catch(Exception) { Debug.Assert(false); } + + m_thFixClip = null; + } + } + + /// + /// Ensure that the file ~/.recently-used is valid (in order to + /// prevent Mono's FileDialog from crashing). + /// + internal static void EnsureRecentlyUsedValid() + { + if(!MonoWorkarounds.IsRequired(1358)) return; + + try + { + string strFile = Environment.GetFolderPath( + Environment.SpecialFolder.Personal); + strFile = UrlUtil.EnsureTerminatingSeparator(strFile, false); + strFile += ".recently-used"; + + if(File.Exists(strFile)) + { + try + { + // Mono's WriteRecentlyUsedFiles method also loads the + // XML file using XmlDocument + XmlDocument xd = new XmlDocument(); + xd.Load(strFile); + } + catch(Exception) // The XML file is invalid + { + File.Delete(strFile); + } + } + } + catch(Exception) { Debug.Assert(false); } + } + + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Utility/StrUtil.cs b/src/KeePassLib2AndroidSdkStyle/Utility/StrUtil.cs new file mode 100644 index 00000000..47094082 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Utility/StrUtil.cs @@ -0,0 +1,1959 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Text; + +#if !KeePassUAP +using System.Drawing; +using System.Security.Cryptography; +#endif + +using KeePassLib.Collections; +using KeePassLib.Cryptography; +using KeePassLib.Cryptography.PasswordGenerator; +using KeePassLib.Native; +using KeePassLib.Security; + +namespace KeePassLib.Utility +{ + /// + /// Character stream class. + /// + public sealed class CharStream + { + 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) + { + if (str == null) { Debug.Assert(false); throw new ArgumentNullException("str"); } + + m_str = str; + } + + public char ReadChar() + { + if (m_iPos >= m_str.Length) return char.MinValue; + + return m_str[m_iPos++]; + } + + public char ReadChar(bool bSkipWhiteSpace) + { + if (!bSkipWhiteSpace) return ReadChar(); + + while (true) + { + char ch = ReadChar(); + if ((ch != ' ') && (ch != '\t') && (ch != '\r') && (ch != '\n')) + return ch; + } + } + + public char PeekChar() + { + if (m_iPos >= m_str.Length) return char.MinValue; + + return m_str[m_iPos]; + } + + public char PeekChar(bool bSkipWhiteSpace) + { + if (!bSkipWhiteSpace) return PeekChar(); + + int i = m_iPos; + while (i < m_str.Length) + { + char ch = m_str[i]; + if ((ch != ' ') && (ch != '\t') && (ch != '\r') && (ch != '\n')) + return ch; + ++i; + } + + return char.MinValue; + } + } + + public enum StrEncodingType + { + Unknown = 0, + Default, + Ascii, + Utf7, + Utf8, + Utf16LE, + Utf16BE, + Utf32LE, + Utf32BE + } + + public sealed class StrEncodingInfo + { + private readonly StrEncodingType m_type; + public StrEncodingType Type + { + get { return m_type; } + } + + private readonly string m_strName; + public string Name + { + get { return m_strName; } + } + + private readonly Encoding m_enc; + public Encoding Encoding + { + get { return m_enc; } + } + + private readonly uint m_cbCodePoint; + /// + /// Size of a character in bytes. + /// + public uint CodePointSize + { + get { return m_cbCodePoint; } + } + + private readonly byte[] m_vSig; + /// + /// Start signature of the text (byte order mark). + /// May be null or empty, if no signature is known. + /// + public byte[] StartSignature + { + get { return m_vSig; } + } + + public StrEncodingInfo(StrEncodingType t, string strName, Encoding enc, + uint cbCodePoint, byte[] vStartSig) + { + if (strName == null) throw new ArgumentNullException("strName"); + if (enc == null) throw new ArgumentNullException("enc"); + if (cbCodePoint <= 0) throw new ArgumentOutOfRangeException("cbCodePoint"); + + m_type = t; + m_strName = strName; + m_enc = enc; + m_cbCodePoint = cbCodePoint; + m_vSig = vStartSig; + } + } + + /// + /// A class containing various string helper methods. + /// + public static class StrUtil + { + public static readonly StringComparison CaseIgnoreCmp = StringComparison.OrdinalIgnoreCase; + + public static StringComparer CaseIgnoreComparer + { + get { return StringComparer.OrdinalIgnoreCase; } + } + + private static bool m_bRtl = false; + public static bool RightToLeft + { + get { return m_bRtl; } + set { m_bRtl = value; } + } + + private static UTF8Encoding m_encUtf8 = null; + public static UTF8Encoding Utf8 + { + get + { + if (m_encUtf8 == null) m_encUtf8 = new UTF8Encoding(false, false); + return m_encUtf8; + } + } + + private static List m_lEncs = null; + public static IEnumerable Encodings + { + get + { + if (m_lEncs != null) return m_lEncs; + + List l = new List(); + + l.Add(new StrEncodingInfo(StrEncodingType.Default, +#if KeePassUAP + "Unicode (UTF-8)", StrUtil.Utf8, 1, new byte[] { 0xEF, 0xBB, 0xBF })); +#else +#if !KeePassLibSD + Encoding.Default.EncodingName, +#else + Encoding.Default.WebName, +#endif + Encoding.Default, + (uint)Encoding.Default.GetBytes("a").Length, null)); +#endif + + l.Add(new StrEncodingInfo(StrEncodingType.Ascii, + "ASCII", Encoding.ASCII, 1, null)); + l.Add(new StrEncodingInfo(StrEncodingType.Utf7, + "Unicode (UTF-7)", Encoding.UTF7, 1, null)); + l.Add(new StrEncodingInfo(StrEncodingType.Utf8, + "Unicode (UTF-8)", StrUtil.Utf8, 1, new byte[] { 0xEF, 0xBB, 0xBF })); + l.Add(new StrEncodingInfo(StrEncodingType.Utf16LE, + "Unicode (UTF-16 LE)", new UnicodeEncoding(false, false), + 2, new byte[] { 0xFF, 0xFE })); + l.Add(new StrEncodingInfo(StrEncodingType.Utf16BE, + "Unicode (UTF-16 BE)", new UnicodeEncoding(true, false), + 2, new byte[] { 0xFE, 0xFF })); + +#if !KeePassLibSD + l.Add(new StrEncodingInfo(StrEncodingType.Utf32LE, + "Unicode (UTF-32 LE)", new UTF32Encoding(false, false), + 4, new byte[] { 0xFF, 0xFE, 0x0, 0x0 })); + l.Add(new StrEncodingInfo(StrEncodingType.Utf32BE, + "Unicode (UTF-32 BE)", new UTF32Encoding(true, false), + 4, new byte[] { 0x0, 0x0, 0xFE, 0xFF })); +#endif + + m_lEncs = l; + return l; + } + } + + // public static string RtfPar + // { + // // get { return (m_bRtl ? "\\rtlpar " : "\\par "); } + // get { return "\\par "; } + // } + + // /// + // /// Convert a string into a valid RTF string. + // /// + // /// Any string. + // /// RTF-encoded string. + // public static string MakeRtfString(string str) + // { + // Debug.Assert(str != null); if(str == null) throw new ArgumentNullException("str"); + // str = str.Replace("\\", "\\\\"); + // str = str.Replace("\r", string.Empty); + // str = str.Replace("{", "\\{"); + // str = str.Replace("}", "\\}"); + // str = str.Replace("\n", StrUtil.RtfPar); + // StringBuilder sbEncoded = new StringBuilder(); + // for(int i = 0; i < str.Length; ++i) + // { + // char ch = str[i]; + // if((int)ch >= 256) + // sbEncoded.Append(StrUtil.RtfEncodeChar(ch)); + // else sbEncoded.Append(ch); + // } + // return sbEncoded.ToString(); + // } + + public static string RtfEncodeChar(char ch) + { + // Unicode character values must be encoded using + // 16-bit numbers (decimal); Unicode values greater + // than 32767 must be expressed as negative numbers + short sh = (short)ch; + return ("\\u" + sh.ToString(NumberFormatInfo.InvariantInfo) + "?"); + } + + internal static bool RtfIsURtf(string str) + { + if (str == null) { Debug.Assert(false); return false; } + + const string p = "{\\urtf"; // Typically "{\\urtf1\\ansi\\ansicpg65001" + return (str.StartsWith(p) && (str.Length > p.Length) && + char.IsDigit(str[p.Length])); + } + + public static string RtfFix(string strRtf) + { + if (strRtf == null) { Debug.Assert(false); return string.Empty; } + + string str = strRtf; + + // Workaround for .NET bug: the Rtf property of a RichTextBox + // can return an RTF text starting with "{\\urtf", but + // setting such an RTF text throws an exception (the setter + // checks for the RTF text to start with "{\\rtf"); + // https://sourceforge.net/p/keepass/discussion/329221/thread/7788872f/ + // https://www.microsoft.com/en-us/download/details.aspx?id=10725 + // https://msdn.microsoft.com/en-us/library/windows/desktop/bb774284.aspx + // https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/RichTextBox.cs + if (RtfIsURtf(str)) str = str.Remove(2, 1); // Remove the 'u' + + return str; + } + + internal static string RtfFilterText(string strText) + { + if (strText == null) { Debug.Assert(false); return string.Empty; } + + // A U+FFFC character causes the rest of the text to be lost. + // With '?', the string length, substring indices and + // character visibility remain the same. + // More special characters (unproblematic) in rich text boxes: + // https://docs.microsoft.com/en-us/windows/win32/api/richedit/ns-richedit-gettextex + return strText.Replace('\uFFFC', '?'); + } + + internal static bool ContainsHighChar(string str) + { + if (str == null) { Debug.Assert(false); return false; } + + for (int i = 0; i < str.Length; ++i) + { + if (str[i] > '\u00FF') return true; + } + + return false; + } + + /// + /// Convert a string to a HTML sequence representing that string. + /// + /// String to convert. + /// String, HTML-encoded. + public static string StringToHtml(string str) + { + return StringToHtml(str, false); + } + + internal static string StringToHtml(string str, bool bNbsp) + { + Debug.Assert(str != null); if (str == null) throw new ArgumentNullException("str"); + + str = str.Replace(@"&", @"&"); // Must be first + str = str.Replace(@"<", @"<"); + str = str.Replace(@">", @">"); + str = str.Replace("\"", @"""); + str = str.Replace("\'", @"'"); + + if (bNbsp) str = str.Replace(" ", @" "); // Before
+ + str = NormalizeNewLines(str, false); + str = str.Replace("\n", @"
" + MessageService.NewLine); + + return str; + } + + public static string XmlToString(string str) + { + Debug.Assert(str != null); if (str == null) throw new ArgumentNullException("str"); + + str = str.Replace(@"&", @"&"); + str = str.Replace(@"<", @"<"); + str = str.Replace(@">", @">"); + str = str.Replace(@""", "\""); + str = str.Replace(@"'", "\'"); + + return str; + } + + public static string ReplaceCaseInsensitive(string strString, string strFind, + string strNew) + { + Debug.Assert(strString != null); if (strString == null) return strString; + Debug.Assert(strFind != null); if (strFind == null) return strString; + Debug.Assert(strNew != null); if (strNew == null) return strString; + + string str = strString; + + int nPos = 0; + while (nPos < str.Length) + { + nPos = str.IndexOf(strFind, nPos, StringComparison.OrdinalIgnoreCase); + if (nPos < 0) break; + + str = str.Remove(nPos, strFind.Length); + str = str.Insert(nPos, strNew); + + nPos += strNew.Length; + } + + return str; + } + + // /// + // /// Initialize an RTF document based on given font face and size. + // /// + // /// StringBuilder to put the generated RTF into. + // /// Face name of the font to use. + // /// Size of the font to use. + // public static void InitRtf(StringBuilder sb, string strFontFace, float fFontSize) + // { + // Debug.Assert(sb != null); if(sb == null) throw new ArgumentNullException("sb"); + // Debug.Assert(strFontFace != null); if(strFontFace == null) throw new ArgumentNullException("strFontFace"); + // sb.Append("{\\rtf1"); + // if(m_bRtl) sb.Append("\\fbidis"); + // sb.Append("\\ansi\\ansicpg"); + // sb.Append(Encoding.Default.CodePage); + // sb.Append("\\deff0{\\fonttbl{\\f0\\fswiss MS Sans Serif;}{\\f1\\froman\\fcharset2 Symbol;}{\\f2\\fswiss "); + // sb.Append(strFontFace); + // sb.Append(";}{\\f3\\fswiss Arial;}}"); + // sb.Append("{\\colortbl\\red0\\green0\\blue0;}"); + // if(m_bRtl) sb.Append("\\rtldoc"); + // sb.Append("\\deflang1031\\pard\\plain\\f2\\cf0 "); + // sb.Append("\\fs"); + // sb.Append((int)(fFontSize * 2)); + // if(m_bRtl) sb.Append("\\rtlpar\\qr\\rtlch "); + // } + + // /// + // /// Convert a simple HTML string to an RTF string. + // /// + // /// Input HTML string. + // /// RTF string representing the HTML input string. + // public static string SimpleHtmlToRtf(string strHtmlString) + // { + // StringBuilder sb = new StringBuilder(); + // StrUtil.InitRtf(sb, "Microsoft Sans Serif", 8.25f); + // sb.Append(" "); + // string str = MakeRtfString(strHtmlString); + // str = str.Replace("", "\\b "); + // str = str.Replace("", "\\b0 "); + // str = str.Replace("", "\\i "); + // str = str.Replace("", "\\i0 "); + // str = str.Replace("", "\\ul "); + // str = str.Replace("", "\\ul0 "); + // str = str.Replace("
", StrUtil.RtfPar); + // sb.Append(str); + // return sb.ToString(); + // } + + /// + /// Convert a Color to a HTML color identifier string. + /// + /// Color to convert. + /// If this is true, an empty string + /// is returned if the color is transparent. + /// HTML color identifier string. + public static string ColorToUnnamedHtml(Color color, bool bEmptyIfTransparent) + { + if (bEmptyIfTransparent && (color.A != 255)) + return string.Empty; + + StringBuilder sb = new StringBuilder(); + byte bt; + + sb.Append('#'); + + bt = (byte)(color.R >> 4); + if (bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); + bt = (byte)(color.R & 0x0F); + if (bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); + + bt = (byte)(color.G >> 4); + if (bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); + bt = (byte)(color.G & 0x0F); + if (bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); + + bt = (byte)(color.B >> 4); + if (bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); + bt = (byte)(color.B & 0x0F); + if (bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); + + return sb.ToString(); + } + + /// + /// Format an exception and convert it to a string. + /// + /// Exception to convert/format. + /// String representing the exception. + public static string FormatException(Exception excp) + { + string strText = string.Empty; + + if (!string.IsNullOrEmpty(excp.Message)) + strText += excp.Message + MessageService.NewLine; +#if !KeePassLibSD + if (!string.IsNullOrEmpty(excp.Source)) + strText += excp.Source + MessageService.NewLine; +#endif + if (!string.IsNullOrEmpty(excp.StackTrace)) + strText += excp.StackTrace + MessageService.NewLine; +#if !KeePassLibSD +#if !KeePassUAP + if (excp.TargetSite != null) + strText += excp.TargetSite.ToString() + MessageService.NewLine; +#endif + + if (excp.Data != null) + { + strText += MessageService.NewLine; + foreach (DictionaryEntry de in excp.Data) + strText += @"'" + de.Key + @"' -> '" + de.Value + @"'" + + MessageService.NewLine; + } +#endif + + if (excp.InnerException != null) + { + strText += MessageService.NewLine + "Inner:" + MessageService.NewLine; + if (!string.IsNullOrEmpty(excp.InnerException.Message)) + strText += excp.InnerException.Message + MessageService.NewLine; +#if !KeePassLibSD + if (!string.IsNullOrEmpty(excp.InnerException.Source)) + strText += excp.InnerException.Source + MessageService.NewLine; +#endif + if (!string.IsNullOrEmpty(excp.InnerException.StackTrace)) + strText += excp.InnerException.StackTrace + MessageService.NewLine; +#if !KeePassLibSD +#if !KeePassUAP + if (excp.InnerException.TargetSite != null) + strText += excp.InnerException.TargetSite.ToString(); +#endif + + if (excp.InnerException.Data != null) + { + strText += MessageService.NewLine; + foreach (DictionaryEntry de in excp.InnerException.Data) + strText += @"'" + de.Key + @"' -> '" + de.Value + @"'" + + MessageService.NewLine; + } +#endif + } + + return strText; + } + + public static bool TryParseUShort(string str, out ushort u) + { +#if !KeePassLibSD + return ushort.TryParse(str, out u); +#else + try { u = ushort.Parse(str); return true; } + catch(Exception) { u = 0; return false; } +#endif + } + + public static bool TryParseInt(string str, out int n) + { +#if !KeePassLibSD + return int.TryParse(str, out n); +#else + try { n = int.Parse(str); return true; } + catch(Exception) { n = 0; } + return false; +#endif + } + + public static bool TryParseIntInvariant(string str, out int n) + { +#if !KeePassLibSD + return int.TryParse(str, NumberStyles.Integer, + NumberFormatInfo.InvariantInfo, out n); +#else + try + { + n = int.Parse(str, NumberStyles.Integer, + NumberFormatInfo.InvariantInfo); + return true; + } + catch(Exception) { n = 0; } + return false; +#endif + } + + public static bool TryParseUInt(string str, out uint u) + { +#if !KeePassLibSD + return uint.TryParse(str, out u); +#else + try { u = uint.Parse(str); return true; } + catch(Exception) { u = 0; } + return false; +#endif + } + + public static bool TryParseUIntInvariant(string str, out uint u) + { +#if !KeePassLibSD + return uint.TryParse(str, NumberStyles.Integer, + NumberFormatInfo.InvariantInfo, out u); +#else + try + { + u = uint.Parse(str, NumberStyles.Integer, + NumberFormatInfo.InvariantInfo); + return true; + } + catch(Exception) { u = 0; } + return false; +#endif + } + + public static bool TryParseLong(string str, out long n) + { +#if !KeePassLibSD + return long.TryParse(str, out n); +#else + try { n = long.Parse(str); return true; } + catch(Exception) { n = 0; } + return false; +#endif + } + + public static bool TryParseLongInvariant(string str, out long n) + { +#if !KeePassLibSD + return long.TryParse(str, NumberStyles.Integer, + NumberFormatInfo.InvariantInfo, out n); +#else + try + { + n = long.Parse(str, NumberStyles.Integer, + NumberFormatInfo.InvariantInfo); + return true; + } + catch(Exception) { n = 0; } + return false; +#endif + } + + public static bool TryParseULong(string str, out ulong u) + { +#if !KeePassLibSD + return ulong.TryParse(str, out u); +#else + try { u = ulong.Parse(str); return true; } + catch(Exception) { u = 0; } + return false; +#endif + } + + public static bool TryParseULongInvariant(string str, out ulong u) + { +#if !KeePassLibSD + return ulong.TryParse(str, NumberStyles.Integer, + NumberFormatInfo.InvariantInfo, out u); +#else + try + { + u = ulong.Parse(str, NumberStyles.Integer, + NumberFormatInfo.InvariantInfo); + return true; + } + catch(Exception) { u = 0; } + return false; +#endif + } + + public static bool TryParseDateTime(string str, out DateTime dt) + { +#if !KeePassLibSD + return DateTime.TryParse(str, out dt); +#else + try { dt = DateTime.Parse(str); return true; } + catch(Exception) { dt = DateTime.UtcNow; } + return false; +#endif + } + + public static string CompactString3Dots(string strText, int cchMax) + { + Debug.Assert(strText != null); + if (strText == null) throw new ArgumentNullException("strText"); + Debug.Assert(cchMax >= 0); + if (cchMax < 0) throw new ArgumentOutOfRangeException("cchMax"); + + if (strText.Length <= cchMax) return strText; + + if (cchMax == 0) return string.Empty; + if (cchMax <= 3) return new string('.', cchMax); + + return (strText.Substring(0, cchMax - 3) + "..."); + } + + private static readonly char[] g_vDots = new char[] { '.', '\u2026' }; + private static readonly char[] g_vDotsWS = new char[] { '.', '\u2026', + ' ', '\t', '\r', '\n' }; + internal static string TrimDots(string strText, bool bTrimWhiteSpace) + { + if (strText == null) { Debug.Assert(false); return string.Empty; } + + return strText.Trim(bTrimWhiteSpace ? g_vDotsWS : g_vDots); + } + + public static string GetStringBetween(string strText, int nStartIndex, + string strStart, string strEnd) + { + int nTemp; + return GetStringBetween(strText, nStartIndex, strStart, strEnd, out nTemp); + } + + public static string GetStringBetween(string strText, int nStartIndex, + string strStart, string strEnd, out int nInnerStartIndex) + { + if (strText == null) throw new ArgumentNullException("strText"); + if (strStart == null) throw new ArgumentNullException("strStart"); + if (strEnd == null) throw new ArgumentNullException("strEnd"); + + nInnerStartIndex = -1; + + int nIndex = strText.IndexOf(strStart, nStartIndex); + if (nIndex < 0) return string.Empty; + + nIndex += strStart.Length; + + int nEndIndex = strText.IndexOf(strEnd, nIndex); + if (nEndIndex < 0) return string.Empty; + + nInnerStartIndex = nIndex; + return strText.Substring(nIndex, nEndIndex - nIndex); + } + + /// + /// Removes all characters that are not valid XML characters, + /// according to https://www.w3.org/TR/xml/#charsets . + /// + /// Source text. + /// Text containing only valid XML characters. + public static string SafeXmlString(string strText) + { + Debug.Assert(strText != null); // No throw + if (string.IsNullOrEmpty(strText)) return strText; + + int nLength = strText.Length; + StringBuilder sb = new StringBuilder(nLength); + + for (int i = 0; i < nLength; ++i) + { + char ch = strText[i]; + + if (((ch >= '\u0020') && (ch <= '\uD7FF')) || + (ch == '\u0009') || (ch == '\u000A') || (ch == '\u000D') || + ((ch >= '\uE000') && (ch <= '\uFFFD'))) + sb.Append(ch); + else if ((ch >= '\uD800') && (ch <= '\uDBFF')) // High surrogate + { + if ((i + 1) < nLength) + { + char chLow = strText[i + 1]; + if ((chLow >= '\uDC00') && (chLow <= '\uDFFF')) // Low sur. + { + sb.Append(ch); + sb.Append(chLow); + ++i; + } + else { Debug.Assert(false); } // Low sur. invalid + } + else { Debug.Assert(false); } // Low sur. missing + } + + Debug.Assert((ch < '\uDC00') || (ch > '\uDFFF')); // Lonely low sur. + } + + return sb.ToString(); + } + + /* private static Regex g_rxNaturalSplit = null; + public static int CompareNaturally(string strX, string strY) + { + Debug.Assert(strX != null); + if(strX == null) throw new ArgumentNullException("strX"); + Debug.Assert(strY != null); + if(strY == null) throw new ArgumentNullException("strY"); + + if(NativeMethods.SupportsStrCmpNaturally) + return NativeMethods.StrCmpNaturally(strX, strY); + + if(g_rxNaturalSplit == null) + g_rxNaturalSplit = new Regex(@"([0-9]+)", RegexOptions.Compiled); + + string[] vPartsX = g_rxNaturalSplit.Split(strX); + string[] vPartsY = g_rxNaturalSplit.Split(strY); + + int n = Math.Min(vPartsX.Length, vPartsY.Length); + for(int i = 0; i < n; ++i) + { + string strPartX = vPartsX[i], strPartY = vPartsY[i]; + int iPartCompare; + +#if KeePassLibSD + try + { + ulong uX = ulong.Parse(strPartX); + ulong uY = ulong.Parse(strPartY); + iPartCompare = uX.CompareTo(uY); + } + catch(Exception) { iPartCompare = string.Compare(strPartX, strPartY, true); } +#else + ulong uX, uY; + if(ulong.TryParse(strPartX, out uX) && ulong.TryParse(strPartY, out uY)) + iPartCompare = uX.CompareTo(uY); + else iPartCompare = string.Compare(strPartX, strPartY, true); +#endif + + if(iPartCompare != 0) return iPartCompare; + } + + if(vPartsX.Length == vPartsY.Length) return 0; + if(vPartsX.Length < vPartsY.Length) return -1; + return 1; + } */ + + public static int CompareNaturally(string strX, string strY) + { + Debug.Assert(strX != null); + if (strX == null) throw new ArgumentNullException("strX"); + Debug.Assert(strY != null); + if (strY == null) throw new ArgumentNullException("strY"); + + if (NativeMethods.SupportsStrCmpNaturally) + return NativeMethods.StrCmpNaturally(strX, strY); + + int cX = strX.Length; + int cY = strY.Length; + if (cX == 0) return ((cY == 0) ? 0 : -1); + if (cY == 0) return 1; + + char chFirstX = strX[0]; + char chFirstY = strY[0]; + bool bExpNum = ((chFirstX >= '0') && (chFirstX <= '9')); + bool bExpNumY = ((chFirstY >= '0') && (chFirstY <= '9')); + if (bExpNum != bExpNumY) return string.Compare(strX, strY, true); + + int pX = 0; + int pY = 0; + while ((pX < cX) && (pY < cY)) + { + Debug.Assert(((strX[pX] >= '0') && (strX[pX] <= '9')) == bExpNum); + Debug.Assert(((strY[pY] >= '0') && (strY[pY] <= '9')) == bExpNum); + + int pExclX = pX + 1; + while (pExclX < cX) + { + char ch = strX[pExclX]; + bool bChNum = ((ch >= '0') && (ch <= '9')); + if (bChNum != bExpNum) break; + ++pExclX; + } + + int pExclY = pY + 1; + while (pExclY < cY) + { + char ch = strY[pExclY]; + bool bChNum = ((ch >= '0') && (ch <= '9')); + if (bChNum != bExpNum) break; + ++pExclY; + } + + string strPartX = strX.Substring(pX, pExclX - pX); + string strPartY = strY.Substring(pY, pExclY - pY); + + bool bStrCmp = true; + if (bExpNum) + { + // 2^64 - 1 = 18446744073709551615 has length 20 + if ((strPartX.Length <= 19) && (strPartY.Length <= 19)) + { + ulong uX, uY; + if (ulong.TryParse(strPartX, out uX) && ulong.TryParse(strPartY, out uY)) + { + if (uX < uY) return -1; + if (uX > uY) return 1; + + bStrCmp = false; + } + else { Debug.Assert(false); } + } + else + { + double dX, dY; + if (double.TryParse(strPartX, out dX) && double.TryParse(strPartY, out dY)) + { + if (dX < dY) return -1; + if (dX > dY) return 1; + + bStrCmp = false; + } + else { Debug.Assert(false); } + } + } + if (bStrCmp) + { + int c = string.Compare(strPartX, strPartY, true); + if (c != 0) return c; + } + + bExpNum = !bExpNum; + pX = pExclX; + pY = pExclY; + } + + if (pX >= cX) + { + Debug.Assert(pX == cX); + if (pY >= cY) { Debug.Assert(pY == cY); return 0; } + return -1; + } + + Debug.Assert(pY == cY); + return 1; + } + + public static string RemoveAccelerator(string strMenuText) + { + if (strMenuText == null) throw new ArgumentNullException("strMenuText"); + + string str = strMenuText; + + for (char ch = 'A'; ch <= 'Z'; ++ch) + { + string strEnhAcc = @"(&" + ch.ToString() + ")"; + if (str.IndexOf(strEnhAcc) >= 0) + { + str = str.Replace(" " + strEnhAcc, string.Empty); + str = str.Replace(strEnhAcc, string.Empty); + } + } + + str = str.Replace(@"&", string.Empty); + + return str; + } + + public static string AddAccelerator(string strMenuText, + List lAvailKeys) + { + if (strMenuText == null) { Debug.Assert(false); return string.Empty; } + if (lAvailKeys == null) { Debug.Assert(false); return strMenuText; } + + for (int i = 0; i < strMenuText.Length; ++i) + { + char ch = char.ToLowerInvariant(strMenuText[i]); + + for (int j = 0; j < lAvailKeys.Count; ++j) + { + if (char.ToLowerInvariant(lAvailKeys[j]) == ch) + { + lAvailKeys.RemoveAt(j); + return strMenuText.Insert(i, @"&"); + } + } + } + + return strMenuText; + } + + public static string EncodeMenuText(string strText) + { + if (strText == null) throw new ArgumentNullException("strText"); + + return strText.Replace(@"&", @"&&"); + } + + public static string EncodeToolTipText(string strText) + { + if (strText == null) throw new ArgumentNullException("strText"); + + return strText.Replace(@"&", @"&&&"); + } + + public static bool IsHexString(string str, bool bStrict) + { + if (str == null) throw new ArgumentNullException("str"); + + foreach (char ch in str) + { + if ((ch >= '0') && (ch <= '9')) continue; + if ((ch >= 'a') && (ch <= 'f')) continue; + if ((ch >= 'A') && (ch <= 'F')) continue; + + if (bStrict) return false; + + if ((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n')) + continue; + + return false; + } + + return true; + } + + public static bool IsHexString(byte[] pbUtf8, bool bStrict) + { + if (pbUtf8 == null) throw new ArgumentNullException("pbUtf8"); + + for (int i = 0; i < pbUtf8.Length; ++i) + { + byte bt = pbUtf8[i]; + if ((bt >= (byte)'0') && (bt <= (byte)'9')) continue; + if ((bt >= (byte)'a') && (bt <= (byte)'f')) continue; + if ((bt >= (byte)'A') && (bt <= (byte)'F')) continue; + + if (bStrict) return false; + + if ((bt == (byte)' ') || (bt == (byte)'\t') || + (bt == (byte)'\r') || (bt == (byte)'\n')) + continue; + + return false; + } + + return true; + } + +#if !KeePassLibSD + private static readonly char[] g_vPatternPartsSep = new char[] { '*' }; + public static bool SimplePatternMatch(string strPattern, string strText, + StringComparison sc) + { + if (strPattern == null) throw new ArgumentNullException("strPattern"); + if (strText == null) throw new ArgumentNullException("strText"); + + if (strPattern.IndexOf('*') < 0) return strText.Equals(strPattern, sc); + + string[] vPatternParts = strPattern.Split(g_vPatternPartsSep, + StringSplitOptions.RemoveEmptyEntries); + if (vPatternParts == null) { Debug.Assert(false); return true; } + if (vPatternParts.Length == 0) return true; + + if (strText.Length == 0) return false; + + if ((strPattern[0] != '*') && !strText.StartsWith(vPatternParts[0], sc)) + return false; + if ((strPattern[strPattern.Length - 1] != '*') && !strText.EndsWith( + vPatternParts[vPatternParts.Length - 1], sc)) + return false; + + int iOffset = 0; + for (int i = 0; i < vPatternParts.Length; ++i) + { + string strPart = vPatternParts[i]; + + int iFound = strText.IndexOf(strPart, iOffset, sc); + if (iFound < iOffset) return false; + + iOffset = iFound + strPart.Length; + if (iOffset == strText.Length) return (i == (vPatternParts.Length - 1)); + } + + return true; + } +#endif // !KeePassLibSD + + public static bool StringToBool(string str) + { + if (string.IsNullOrEmpty(str)) return false; // No assert + + string s = str.Trim().ToLower(); + if (s == "true") return true; + if (s == "yes") return true; + if (s == "1") return true; + if (s == "enabled") return true; + if (s == "checked") return true; + + return false; + } + + public static bool? StringToBoolEx(string str) + { + if (string.IsNullOrEmpty(str)) return null; + + string s = str.Trim().ToLower(); + if (s == "true") return true; + if (s == "false") return false; + + return null; + } + + public static string BoolToString(bool bValue) + { + return (bValue ? "true" : "false"); + } + + public static string BoolToStringEx(bool? bValue) + { + if (bValue.HasValue) return BoolToString(bValue.Value); + return "null"; + } + + /// + /// Normalize new line characters in a string. Input strings may + /// contain mixed new line character sequences from all commonly + /// used operating systems (i.e. \r\n from Windows, \n from Unix + /// and \r from Mac OS. + /// + /// String with mixed new line characters. + /// If true, new line characters + /// are normalized for Windows (\r\n); if false, new line + /// characters are normalized for Unix (\n). + /// String with normalized new line characters. + public static string NormalizeNewLines(string str, bool bWindows) + { + if (string.IsNullOrEmpty(str)) return str; + + str = str.Replace("\r\n", "\n"); + str = str.Replace("\r", "\n"); + + if (bWindows) str = str.Replace("\n", "\r\n"); + + return str; + } + + public static void NormalizeNewLines(ProtectedStringDictionary dict, + bool bWindows) + { + if (dict == null) { Debug.Assert(false); return; } + + List lKeys = dict.GetKeys(); + foreach (string strKey in lKeys) + { + ProtectedString ps = dict.Get(strKey); + if (ps == null) { Debug.Assert(false); continue; } + + char[] v = ps.ReadChars(); + if (!IsNewLineNormalized(v, bWindows)) + dict.Set(strKey, new ProtectedString(ps.IsProtected, + NormalizeNewLines(ps.ReadString(), bWindows))); + MemUtil.ZeroArray(v); + } + } + + internal static bool IsNewLineNormalized(char[] v, bool bWindows) + { + if (v == null) { Debug.Assert(false); return true; } + + if (bWindows) + { + int iFreeCr = -2; // Must be < -1 (for test "!= (i - 1)") + + for (int i = 0; i < v.Length; ++i) + { + char ch = v[i]; + + if (ch == '\r') + { + if (iFreeCr >= 0) return false; + iFreeCr = i; + } + else if (ch == '\n') + { + if (iFreeCr != (i - 1)) return false; + iFreeCr = -2; // Consume \r + } + } + + return (iFreeCr < 0); // Ensure no \r at end + } + + return (Array.IndexOf(v, '\r') < 0); + } + + public static string GetNewLineSeq(string str) + { + if (str == null) { Debug.Assert(false); return MessageService.NewLine; } + + int n = str.Length, nLf = 0, nCr = 0, nCrLf = 0; + char chLast = char.MinValue; + for (int i = 0; i < n; ++i) + { + char ch = str[i]; + + if (ch == '\r') ++nCr; + else if (ch == '\n') + { + ++nLf; + if (chLast == '\r') ++nCrLf; + } + + chLast = ch; + } + + nCr -= nCrLf; + nLf -= nCrLf; + + int nMax = Math.Max(nCrLf, Math.Max(nCr, nLf)); + if (nMax == 0) return MessageService.NewLine; + + if (nCrLf == nMax) return "\r\n"; + return ((nLf == nMax) ? "\n" : "\r"); + } + + public static string AlphaNumericOnly(string str) + { + if (string.IsNullOrEmpty(str)) return str; + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < str.Length; ++i) + { + char ch = str[i]; + if (((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || + ((ch >= '0') && (ch <= '9'))) + sb.Append(ch); + } + + return sb.ToString(); + } + + public static string FormatDataSize(ulong uBytes) + { + const ulong uKB = 1024; + const ulong uMB = uKB * uKB; + const ulong uGB = uMB * uKB; + const ulong uTB = uGB * uKB; + + if (uBytes == 0) return "0 KB"; + if (uBytes <= uKB) return "1 KB"; + if (uBytes <= uMB) return (((uBytes - 1UL) / uKB) + 1UL).ToString() + " KB"; + 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"; + } + + public static string FormatDataSizeKB(ulong uBytes) + { + const ulong uKB = 1024; + + if (uBytes == 0) return "0 KB"; + if (uBytes <= uKB) return "1 KB"; + + return (((uBytes - 1UL) / uKB) + 1UL).ToString() + " KB"; + } + + private static readonly char[] m_vVersionSep = new char[] { '.', ',' }; + public static ulong ParseVersion(string strVersion) + { + if (strVersion == null) { Debug.Assert(false); return 0; } + + string[] vVer = strVersion.Split(m_vVersionSep); + if ((vVer == null) || (vVer.Length == 0)) { Debug.Assert(false); return 0; } + + ushort uPart; + StrUtil.TryParseUShort(vVer[0].Trim(), out uPart); + ulong uVer = ((ulong)uPart << 48); + + if (vVer.Length >= 2) + { + StrUtil.TryParseUShort(vVer[1].Trim(), out uPart); + uVer |= ((ulong)uPart << 32); + } + + if (vVer.Length >= 3) + { + StrUtil.TryParseUShort(vVer[2].Trim(), out uPart); + uVer |= ((ulong)uPart << 16); + } + + if (vVer.Length >= 4) + { + StrUtil.TryParseUShort(vVer[3].Trim(), out uPart); + uVer |= (ulong)uPart; + } + + return uVer; + } + + public static string VersionToString(ulong uVersion) + { + return VersionToString(uVersion, 1U); + } + + [Obsolete] + public static string VersionToString(ulong uVersion, + bool bEnsureAtLeastTwoComp) + { + return VersionToString(uVersion, (bEnsureAtLeastTwoComp ? 2U : 1U)); + } + + public static string VersionToString(ulong uVersion, uint uMinComp) + { + StringBuilder sb = new StringBuilder(); + uint uComp = 0; + + for (int i = 0; i < 4; ++i) + { + if (uVersion == 0UL) break; + + ushort us = (ushort)(uVersion >> 48); + + if (sb.Length > 0) sb.Append('.'); + + sb.Append(us.ToString(NumberFormatInfo.InvariantInfo)); + ++uComp; + + uVersion <<= 16; + } + + while (uComp < uMinComp) + { + if (sb.Length > 0) sb.Append('.'); + + sb.Append('0'); + ++uComp; + } + + return sb.ToString(); + } + + private static readonly byte[] m_pbOptEnt = { 0xA5, 0x74, 0x2E, 0xEC }; + + public static string EncryptString(string strPlainText) + { + if (string.IsNullOrEmpty(strPlainText)) return string.Empty; + + try + { + byte[] pbPlain = StrUtil.Utf8.GetBytes(strPlainText); + byte[] pbEnc = CryptoUtil.ProtectData(pbPlain, m_pbOptEnt, + DataProtectionScope.CurrentUser); + +#if (!KeePassLibSD && !KeePassUAP) + return Convert.ToBase64String(pbEnc, Base64FormattingOptions.None); +#else + return Convert.ToBase64String(pbEnc); +#endif + } + catch (Exception) { Debug.Assert(false); } + + return strPlainText; + } + + public static string DecryptString(string strCipherText) + { + if (string.IsNullOrEmpty(strCipherText)) return string.Empty; + + try + { + byte[] pbEnc = Convert.FromBase64String(strCipherText); + byte[] pbPlain = CryptoUtil.UnprotectData(pbEnc, m_pbOptEnt, + DataProtectionScope.CurrentUser); + + return StrUtil.Utf8.GetString(pbPlain, 0, pbPlain.Length); + } + catch (Exception) { Debug.Assert(false); } + + return strCipherText; + } + + public static string SerializeIntArray(int[] vNumbers) + { + if (vNumbers == null) throw new ArgumentNullException("vNumbers"); + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < vNumbers.Length; ++i) + { + if (i > 0) sb.Append(' '); + sb.Append(vNumbers[i].ToString(NumberFormatInfo.InvariantInfo)); + } + + return sb.ToString(); + } + + public static int[] DeserializeIntArray(string strSerialized) + { + if (strSerialized == null) throw new ArgumentNullException("strSerialized"); + if (strSerialized.Length == 0) return new int[0]; + + string[] vParts = strSerialized.Split(' '); + int[] v = new int[vParts.Length]; + + for (int i = 0; i < vParts.Length; ++i) + { + int n; + if (!TryParseIntInvariant(vParts[i], out n)) { Debug.Assert(false); } + v[i] = n; + } + + return v; + } + + private static readonly char[] g_vTagSep = new char[] { ',', ';' }; + internal static string NormalizeTag(string strTag) + { + if (strTag == null) { Debug.Assert(false); return string.Empty; } + + strTag = strTag.Trim(); + + for (int i = g_vTagSep.Length - 1; i >= 0; --i) + strTag = strTag.Replace(g_vTagSep[i], '.'); + + return strTag; + } + + internal static void NormalizeTags(List lTags) + { + if (lTags == null) { Debug.Assert(false); return; } + + bool bRemoveNulls = false; + for (int i = lTags.Count - 1; i >= 0; --i) + { + string str = NormalizeTag(lTags[i]); + + if (string.IsNullOrEmpty(str)) + { + lTags[i] = null; + bRemoveNulls = true; + } + else lTags[i] = str; + } + + if (bRemoveNulls) + { + Predicate f = delegate (string str) { return (str == null); }; + lTags.RemoveAll(f); + } + + if (lTags.Count >= 2) + { + // Deduplicate + Dictionary d = new Dictionary(); + for (int i = lTags.Count - 1; i >= 0; --i) + d[lTags[i]] = true; + if (d.Count != lTags.Count) + { + lTags.Clear(); + lTags.AddRange(d.Keys); + } + + lTags.Sort(StrUtil.CompareNaturally); + } + } + + internal static void AddTags(List lTags, IEnumerable eNewTags) + { + if (lTags == null) { Debug.Assert(false); return; } + if (eNewTags == null) { Debug.Assert(false); return; } + + lTags.AddRange(eNewTags); + NormalizeTags(lTags); + } + + public static string TagsToString(List lTags, bool bForDisplay) + { + if (lTags == null) throw new ArgumentNullException("lTags"); + +#if DEBUG + // The input should be normalized + foreach (string str in lTags) { Debug.Assert(NormalizeTag(str) == str); } + List l = new List(lTags); + NormalizeTags(l); + Debug.Assert(l.Count == lTags.Count); +#endif + + int n = lTags.Count; + if (n == 0) return string.Empty; + if (n == 1) return (lTags[0] ?? string.Empty); + + StringBuilder sb = new StringBuilder(); + bool bFirst = true; + + foreach (string strTag in lTags) + { + if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); continue; } + + if (bFirst) bFirst = false; + else + { + if (bForDisplay) sb.Append(", "); + else sb.Append(';'); + } + + sb.Append(strTag); + } + + return sb.ToString(); + } + + public static List StringToTags(string strTags) + { + if (strTags == null) throw new ArgumentNullException("strTags"); + + List lTags = new List(); + if (strTags.Length == 0) return lTags; + + lTags.AddRange(strTags.Split(g_vTagSep)); + + NormalizeTags(lTags); + return lTags; + } + + public static string Obfuscate(string strPlain) + { + if (strPlain == null) { Debug.Assert(false); return string.Empty; } + if (strPlain.Length == 0) return string.Empty; + + byte[] pb = StrUtil.Utf8.GetBytes(strPlain); + + Array.Reverse(pb); + for (int i = 0; i < pb.Length; ++i) pb[i] = (byte)(pb[i] ^ 0x65); + +#if (!KeePassLibSD && !KeePassUAP) + return Convert.ToBase64String(pb, Base64FormattingOptions.None); +#else + return Convert.ToBase64String(pb); +#endif + } + + public static string Deobfuscate(string strObf) + { + if (strObf == null) { Debug.Assert(false); return string.Empty; } + if (strObf.Length == 0) return string.Empty; + + try + { + byte[] pb = Convert.FromBase64String(strObf); + + for (int i = 0; i < pb.Length; ++i) pb[i] = (byte)(pb[i] ^ 0x65); + Array.Reverse(pb); + + return StrUtil.Utf8.GetString(pb, 0, pb.Length); + } + catch (Exception) { Debug.Assert(false); } + + return string.Empty; + } + + /// + /// Split a string and include the separators in the splitted array. + /// + /// String to split. + /// Separators. + /// Specifies whether separators are + /// matched case-sensitively or not. + /// Splitted string including separators. + public static List SplitWithSep(string str, string[] vSeps, + bool bCaseSensitive) + { + if (str == null) throw new ArgumentNullException("str"); + if (vSeps == null) throw new ArgumentNullException("vSeps"); + + List v = new List(); + while (true) + { + int minIndex = int.MaxValue, minSep = -1; + for (int i = 0; i < vSeps.Length; ++i) + { + string strSep = vSeps[i]; + if (string.IsNullOrEmpty(strSep)) { Debug.Assert(false); continue; } + + int iIndex = (bCaseSensitive ? str.IndexOf(strSep) : + str.IndexOf(strSep, StrUtil.CaseIgnoreCmp)); + if ((iIndex >= 0) && (iIndex < minIndex)) + { + minIndex = iIndex; + minSep = i; + } + } + + if (minIndex == int.MaxValue) break; + + v.Add(str.Substring(0, minIndex)); + v.Add(vSeps[minSep]); + + str = str.Substring(minIndex + vSeps[minSep].Length); + } + + v.Add(str); + return v; + } + + public static string MultiToSingleLine(string strMulti) + { + if (strMulti == null) { Debug.Assert(false); return string.Empty; } + if (strMulti.Length == 0) return string.Empty; + + string str = strMulti; + str = str.Replace("\r\n", " "); + str = str.Replace('\r', ' '); + str = str.Replace('\n', ' '); + + return str; + } + + public static List SplitSearchTerms(string strSearch) + { + List l = new List(); + if (strSearch == null) { Debug.Assert(false); return l; } + + StringBuilder sbTerm = new StringBuilder(); + bool bQuoted = false; + + for (int i = 0; i < strSearch.Length; ++i) + { + char ch = strSearch[i]; + + if (((ch == ' ') || (ch == '\t') || (ch == '\r') || + (ch == '\n')) && !bQuoted) + { + if (sbTerm.Length != 0) + { + l.Add(sbTerm.ToString()); + sbTerm.Remove(0, sbTerm.Length); + } + } + else if (ch == '\"') bQuoted = !bQuoted; + else sbTerm.Append(ch); + } + if (sbTerm.Length != 0) l.Add(sbTerm.ToString()); + + return l; + } + + public static int CompareLengthGt(string x, string y) + { + if (x.Length == y.Length) return 0; + return ((x.Length > y.Length) ? -1 : 1); + } + + public static bool IsDataUri(string strUri) + { + return IsDataUri(strUri, null); + } + + public static bool IsDataUri(string strUri, string strReqMediaType) + { + if (strUri == null) { Debug.Assert(false); return false; } + // strReqMediaType may be null + + const string strPrefix = "data:"; + if (!strUri.StartsWith(strPrefix, StrUtil.CaseIgnoreCmp)) + return false; + + int iC = strUri.IndexOf(','); + if (iC < 0) return false; + + if (!string.IsNullOrEmpty(strReqMediaType)) + { + int iS = strUri.IndexOf(';', 0, iC); + int iTerm = ((iS >= 0) ? iS : iC); + + string strMedia = strUri.Substring(strPrefix.Length, + iTerm - strPrefix.Length); + if (!strMedia.Equals(strReqMediaType, StrUtil.CaseIgnoreCmp)) + return false; + } + + return true; + } + + /// + /// Create a data URI (according to RFC 2397). + /// + /// Data to encode. + /// Optional MIME type. If null, + /// an appropriate type is used. + /// Data URI. + public static string DataToDataUri(byte[] pbData, string strMediaType) + { + if (pbData == null) throw new ArgumentNullException("pbData"); + + if (strMediaType == null) strMediaType = "application/octet-stream"; + +#if (!KeePassLibSD && !KeePassUAP) + return ("data:" + strMediaType + ";base64," + Convert.ToBase64String( + pbData, Base64FormattingOptions.None)); +#else + return ("data:" + strMediaType + ";base64," + Convert.ToBase64String( + pbData)); +#endif + } + + /// + /// Convert a data URI (according to RFC 2397) to binary data. + /// + /// Data URI to decode. + /// Decoded binary data. + public static byte[] DataUriToData(string strDataUri) + { + if (strDataUri == null) throw new ArgumentNullException("strDataUri"); + if (!strDataUri.StartsWith("data:", StrUtil.CaseIgnoreCmp)) return null; + + int iSep = strDataUri.IndexOf(','); + if (iSep < 0) return null; + + string strDesc = strDataUri.Substring(5, iSep - 5); + bool bBase64 = strDesc.EndsWith(";base64", StrUtil.CaseIgnoreCmp); + + string strData = strDataUri.Substring(iSep + 1); + + if (bBase64) return Convert.FromBase64String(strData); + + MemoryStream ms = new MemoryStream(); + Encoding enc = Encoding.ASCII; + + string[] v = strData.Split('%'); + byte[] pb = enc.GetBytes(v[0]); + ms.Write(pb, 0, pb.Length); + for (int i = 1; i < v.Length; ++i) + { + ms.WriteByte(Convert.ToByte(v[i].Substring(0, 2), 16)); + pb = enc.GetBytes(v[i].Substring(2)); + ms.Write(pb, 0, pb.Length); + } + + pb = ms.ToArray(); + ms.Close(); + return pb; + } + + // https://www.iana.org/assignments/media-types/media-types.xhtml + private static readonly string[] g_vMediaTypePfx = new string[] { + "application/", "audio/", "example/", "font/", "image/", + "message/", "model/", "multipart/", "text/", "video/" + }; + internal static bool IsMediaType(string str) + { + if (str == null) { Debug.Assert(false); return false; } + if (str.Length == 0) return false; + + foreach (string strPfx in g_vMediaTypePfx) + { + if (str.StartsWith(strPfx, StrUtil.CaseIgnoreCmp)) + return true; + } + + return false; + } + + internal static string GetCustomMediaType(string strFormat) + { + if (strFormat == null) + { + Debug.Assert(false); + return "application/octet-stream"; + } + + if (IsMediaType(strFormat)) return strFormat; + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < strFormat.Length; ++i) + { + char ch = strFormat[i]; + + if (((ch >= 'A') && (ch <= 'Z')) || ((ch >= 'a') && (ch <= 'z')) || + ((ch >= '0') && (ch <= '9'))) + sb.Append(ch); + else if ((sb.Length != 0) && ((ch == '-') || (ch == '_'))) + sb.Append(ch); + else { Debug.Assert(false); } + } + + if (sb.Length == 0) return "application/octet-stream"; + + return ("application/vnd." + PwDefs.ShortProductName + + "." + sb.ToString()); + } + + /// + /// Remove placeholders from a string (wrapped in '{' and '}'). + /// This doesn't remove environment variables (wrapped in '%'). + /// + public static string RemovePlaceholders(string str) + { + if (str == null) { Debug.Assert(false); return string.Empty; } + + while (true) + { + int iPlhStart = str.IndexOf('{'); + if (iPlhStart < 0) break; + + int iPlhEnd = str.IndexOf('}', iPlhStart); // '{' might be at end + if (iPlhEnd < 0) break; + + str = (str.Substring(0, iPlhStart) + str.Substring(iPlhEnd + 1)); + } + + return str; + } + + public static StrEncodingInfo GetEncoding(StrEncodingType t) + { + foreach (StrEncodingInfo sei in StrUtil.Encodings) + { + if (sei.Type == t) return sei; + } + + return null; + } + + public static StrEncodingInfo GetEncoding(string strName) + { + foreach (StrEncodingInfo sei in StrUtil.Encodings) + { + if (sei.Name == strName) return sei; + } + + return null; + } + + private static string[] m_vPrefSepChars = null; + /// + /// Find a character that does not occur within a given text. + /// + public static char GetUnusedChar(string strText) + { + if (strText == null) { Debug.Assert(false); return '@'; } + + if (m_vPrefSepChars == null) + m_vPrefSepChars = new string[5] { + "@!$%#/\\:;,.*-_?", + PwCharSet.UpperCase, PwCharSet.LowerCase, + PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial + }; + + for (int i = 0; i < m_vPrefSepChars.Length; ++i) + { + foreach (char ch in m_vPrefSepChars[i]) + { + if (strText.IndexOf(ch) < 0) return ch; + } + } + + for (char ch = '\u00C0'; ch < char.MaxValue; ++ch) + { + if (strText.IndexOf(ch) < 0) return ch; + } + + return char.MinValue; + } + + public static char ByteToSafeChar(byte bt) + { + const char chDefault = '.'; + + // 00-1F are C0 control chars + if (bt < 0x20) return chDefault; + + // 20-7F are basic Latin; 7F is DEL + if (bt < 0x7F) return (char)bt; + + // 80-9F are C1 control chars + if (bt < 0xA0) return chDefault; + + // A0-FF are Latin-1 supplement; AD is soft hyphen + if (bt == 0xAD) return '-'; + return (char)bt; + } + + public static int Count(string str, string strNeedle) + { + if (str == null) { Debug.Assert(false); return 0; } + if (string.IsNullOrEmpty(strNeedle)) { Debug.Assert(false); return 0; } + + int iOffset = 0, iCount = 0; + while (iOffset < str.Length) + { + int p = str.IndexOf(strNeedle, iOffset); + if (p < 0) break; + + ++iCount; + iOffset = p + 1; + } + + return iCount; + } + + internal static string ReplaceNulls(string str) + { + if (str == null) { Debug.Assert(false); return null; } + + if (str.IndexOf('\0') < 0) return str; + + // Replacing null characters by spaces is the + // behavior of Notepad (on Windows 10) + return str.Replace('\0', ' '); + } + + // https://sourceforge.net/p/keepass/discussion/329220/thread/f98dece5/ + internal static string EnsureLtrPath(string strPath) + { + if (strPath == null) { Debug.Assert(false); return string.Empty; } + + string str = strPath; + + // U+200E = left-to-right mark + str = str.Replace("\\", "\\\u200E"); + str = str.Replace("/", "/\u200E"); + str = str.Replace("\u200E\u200E", "\u200E"); // Remove duplicates + + return str; + } + + internal static bool IsValid(string str) + { + if (str == null) { Debug.Assert(false); return false; } + + int cc = str.Length; + for (int i = 0; i < cc; ++i) + { + char ch = str[i]; + if (ch == '\0') return false; + + if (char.IsLowSurrogate(ch)) return false; + if (char.IsHighSurrogate(ch)) + { + if (++i >= cc) return false; // High surrogate at end + if (!char.IsLowSurrogate(str[i])) return false; + + UnicodeCategory uc2 = char.GetUnicodeCategory(str, i - 1); + if (uc2 == UnicodeCategory.OtherNotAssigned) return false; + + continue; + } + + UnicodeCategory uc = char.GetUnicodeCategory(ch); + if (uc == UnicodeCategory.OtherNotAssigned) return false; + } + + return true; + } + + internal static string RemoveWhiteSpace(string str) + { + if (str == null) { Debug.Assert(false); return string.Empty; } + + int cc = str.Length; + if (cc == 0) return string.Empty; + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < cc; ++i) + { + char ch = str[i]; + if (char.IsWhiteSpace(ch)) continue; + sb.Append(ch); + } + + return sb.ToString(); + } + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Utility/TimeUtil.cs b/src/KeePassLib2AndroidSdkStyle/Utility/TimeUtil.cs new file mode 100644 index 00000000..9d4b410a --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Utility/TimeUtil.cs @@ -0,0 +1,491 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Text; + +using KeePassLib.Interfaces; + +namespace KeePassLib.Utility +{ + /// + /// Contains various static time structure manipulation and conversion + /// routines. + /// + public static class TimeUtil + { + /// + /// Length of a compressed PW_TIME structure in bytes. + /// + public const int PwTimeLength = 7; + + public static readonly DateTime SafeMinValueUtc = new DateTime( + DateTime.MinValue.Ticks + TimeSpan.TicksPerDay, DateTimeKind.Utc); + public static readonly DateTime SafeMaxValueUtc = new DateTime( + DateTime.MaxValue.Ticks - TimeSpan.TicksPerDay, DateTimeKind.Utc); + +#if !KeePassLibSD + private static string m_strDtfStd = null; + private static string m_strDtfDate = null; +#endif + /// + // private static long m_lTicks2PowLess1s = 0; + + private static DateTime? m_odtUnixRoot = null; + public static DateTime UnixRoot + { + get + { + if(m_odtUnixRoot.HasValue) return m_odtUnixRoot.Value; + /// Pack a DateTime object into 5 bytes. Layout: 2 zero bits, + DateTime dtRoot = new DateTime(1970, 1, 1, 0, 0, 0, 0, + DateTimeKind.Utc); + /// year 12 bits, month 4 bits, day 5 bits, hour 5 bits, minute 6 + m_odtUnixRoot = dtRoot; + return dtRoot; + } + } + /// bits, second 6 bits. + /// + /// + /// + /// bits, second 6 bits. + /// + [Obsolete] + public static byte[] PackTime(DateTime dt) + { + dt = ToLocal(dt, true); + + byte[] pb = new byte[5]; + + // Pack time to 5 byte structure: + // Byte bits: 11111111 22222222 33333333 44444444 55555555 + // Contents : 00YYYYYY YYYYYYMM MMDDDDDH HHHHMMMM MMSSSSSS + pb[0] = (byte)((dt.Year >> 6) & 0x3F); + pb[1] = (byte)(((dt.Year & 0x3F) << 2) | ((dt.Month >> 2) & 0x03)); + pb[2] = (byte)(((dt.Month & 0x03) << 6) | ((dt.Day & 0x1F) << 1) | + ((dt.Hour >> 4) & 0x01)); + pb[3] = (byte)(((dt.Hour & 0x0F) << 4) | ((dt.Minute >> 2) & 0x0F)); + pb[4] = (byte)(((dt.Minute & 0x03) << 6) | (dt.Second & 0x3F)); + + return pb; + } + + /// + /// Unpack a packed time (5 bytes, packed by the PackTime + /// member function) to a DateTime object. + /// + /// Packed time, 5 bytes. + /// Unpacked DateTime object. + [Obsolete] + public static DateTime UnpackTime(byte[] pb) + { + Debug.Assert((pb != null) && (pb.Length == 5)); + if(pb == null) throw new ArgumentNullException("pb"); + if(pb.Length != 5) throw new ArgumentException(); + + int n1 = pb[0], n2 = pb[1], n3 = pb[2], n4 = pb[3], n5 = pb[4]; + + // Unpack 5 byte structure to date and time + int nYear = (n1 << 6) | (n2 >> 2); + int nMonth = ((n2 & 0x00000003) << 2) | (n3 >> 6); + int nDay = (n3 >> 1) & 0x0000001F; + int nHour = ((n3 & 0x00000001) << 4) | (n4 >> 4); + int nMinute = ((n4 & 0x0000000F) << 2) | (n5 >> 6); + int nSecond = n5 & 0x0000003F; + + return (new DateTime(nYear, nMonth, nDay, nHour, nMinute, + nSecond, DateTimeKind.Local)).ToUniversalTime(); + } + + /// + /// Pack a DateTime object into 7 bytes (PW_TIME). + /// + /// Object to be encoded. + /// Packed time, 7 bytes (PW_TIME). + [Obsolete] + public static byte[] PackPwTime(DateTime dt) + { + Debug.Assert(PwTimeLength == 7); + + dt = ToLocal(dt, true); + + byte[] pb = new byte[7]; + pb[0] = (byte)(dt.Year & 0xFF); + pb[1] = (byte)(dt.Year >> 8); + pb[2] = (byte)dt.Month; + pb[3] = (byte)dt.Day; + pb[4] = (byte)dt.Hour; + pb[5] = (byte)dt.Minute; + pb[6] = (byte)dt.Second; + + return pb; + } + + /// + /// Unpack a packed time (7 bytes, PW_TIME) to a DateTime object. + /// + /// Packed time, 7 bytes. + /// Unpacked DateTime object. + [Obsolete] + public static DateTime UnpackPwTime(byte[] pb) + { + Debug.Assert(PwTimeLength == 7); + + Debug.Assert(pb != null); if(pb == null) throw new ArgumentNullException("pb"); + Debug.Assert(pb.Length == 7); if(pb.Length != 7) throw new ArgumentException(); + + return (new DateTime(((int)pb[1] << 8) | (int)pb[0], (int)pb[2], (int)pb[3], + (int)pb[4], (int)pb[5], (int)pb[6], DateTimeKind.Local)).ToUniversalTime(); + } + + /// + /// Convert a DateTime object to a displayable string. + /// + /// DateTime object to convert to a string. + /// String representing the specified DateTime object. + public static string ToDisplayString(DateTime dt) + { + return ToLocal(dt, true).ToString(); + } + + public static string ToDisplayStringDateOnly(DateTime dt) + { + return ToLocal(dt, true).ToString("d"); + } + + public static DateTime FromDisplayString(string strDisplay) + { + DateTime dt; + if(FromDisplayStringEx(strDisplay, out dt)) return dt; + return DateTime.Now; + } + + public static bool FromDisplayStringEx(string strDisplay, out DateTime dt) + { +#if KeePassLibSD + try { dt = ToLocal(DateTime.Parse(strDisplay), true); return true; } + catch(Exception) { } +#else + if(DateTime.TryParse(strDisplay, out dt)) + { + dt = ToLocal(dt, true); + return true; + } + + // For some custom formats specified using the Control Panel, + // DateTime.ToString returns the correct string, but + // DateTime.TryParse fails (e.g. for "//dd/MMM/yyyy"); + // https://sourceforge.net/p/keepass/discussion/329221/thread/3a225b29/?limit=25&page=1#c6ae + if((m_strDtfStd == null) || (m_strDtfDate == null)) + { + DateTime dtUni = new DateTime(2111, 3, 4, 5, 6, 7, DateTimeKind.Local); + m_strDtfStd = DeriveCustomFormat(ToDisplayString(dtUni), dtUni); + m_strDtfDate = DeriveCustomFormat(ToDisplayStringDateOnly(dtUni), dtUni); + } + const DateTimeStyles dts = DateTimeStyles.AllowWhiteSpaces; + if(DateTime.TryParseExact(strDisplay, m_strDtfStd, null, dts, out dt)) + { + dt = ToLocal(dt, true); + return true; + } + if(DateTime.TryParseExact(strDisplay, m_strDtfDate, null, dts, out dt)) + { + dt = ToLocal(dt, true); + return true; + } +#endif + + Debug.Assert(false); + return false; + } + +#if !KeePassLibSD + private static string DeriveCustomFormat(string strDT, DateTime dt) + { + string[] vPlh = new string[] { + // Names, sorted by length + "MMMM", "dddd", + "MMM", "ddd", + "gg", "g", + + // Numbers, the ones with prefix '0' first + "yyyy", "yyy", "yy", "y", + "MM", "M", + "dd", "d", + "HH", "hh", "H", "h", + "mm", "m", + "ss", "s", + + "tt", "t" + }; + + List lValues = new List(); + foreach(string strPlh in vPlh) + { + string strEval = strPlh; + if(strEval.Length == 1) strEval = @"%" + strPlh; // Make custom + + lValues.Add(dt.ToString(strEval)); + } + + StringBuilder sbAll = new StringBuilder(); + sbAll.Append("dfFghHKmMstyz:/\"\'\\%"); + sbAll.Append(strDT); + foreach(string strVEnum in lValues) { sbAll.Append(strVEnum); } + + List lCodes = new List(); + for(int i = 0; i < vPlh.Length; ++i) + { + char ch = StrUtil.GetUnusedChar(sbAll.ToString()); + lCodes.Add(ch); + sbAll.Append(ch); + } + + string str = strDT; + for(int i = 0; i < vPlh.Length; ++i) + { + string strValue = lValues[i]; + if(string.IsNullOrEmpty(strValue)) continue; + + str = str.Replace(strValue, new string(lCodes[i], 1)); + } + + StringBuilder sbFmt = new StringBuilder(); + bool bInLiteral = false; + foreach(char ch in str) + { + int iCode = lCodes.IndexOf(ch); + + // The escape character doesn't work correctly (e.g. + // "dd\\.MM\\.yyyy\\ HH\\:mm\\:ss" doesn't work, but + // "dd'.'MM'.'yyyy' 'HH':'mm':'ss" does); use '' instead + + // if(iCode >= 0) sbFmt.Append(vPlh[iCode]); + // else // Literal + // { + // sbFmt.Append('\\'); + // sbFmt.Append(ch); + // } + + if(iCode >= 0) + { + if(bInLiteral) { sbFmt.Append('\''); bInLiteral = false; } + sbFmt.Append(vPlh[iCode]); + } + else // Literal + { + if(!bInLiteral) { sbFmt.Append('\''); bInLiteral = true; } + sbFmt.Append(ch); + } + } + if(bInLiteral) sbFmt.Append('\''); + + return sbFmt.ToString(); + } +#endif + + public static string SerializeUtc(DateTime dt) + { + Debug.Assert(dt.Kind != DateTimeKind.Unspecified); + + string str = ToUtc(dt, false).ToString("s"); + if(!str.EndsWith("Z")) str += "Z"; + return str; + } + + public static bool TryDeserializeUtc(string str, out DateTime dt) + { + if(str == null) throw new ArgumentNullException("str"); + + if(str.EndsWith("Z")) str = str.Substring(0, str.Length - 1); + + bool bResult = StrUtil.TryParseDateTime(str, out dt); + if(bResult) dt = ToUtc(dt, true); + return bResult; + } + + public static double SerializeUnix(DateTime dt) + { + return (ToUtc(dt, false) - TimeUtil.UnixRoot).TotalSeconds; + } + + + private static DateTime? m_dtUnixRoot = null; + public static DateTime ConvertUnixTime(double dtUnix) + { + try + { + if(!m_dtUnixRoot.HasValue) + m_dtUnixRoot = (new DateTime(1970, 1, 1, 0, 0, 0, 0, + DateTimeKind.Utc)).ToLocalTime(); + + return m_dtUnixRoot.Value.AddSeconds(dtUnix); + } + catch(Exception) { Debug.Assert(false); } + + return DateTime.UtcNow; + } + +#if !KeePassLibSD + [Obsolete] + public static DateTime? ParseUSTextDate(string strDate) + { + return ParseUSTextDate(strDate, DateTimeKind.Unspecified); + } + + private static string[] m_vUSMonths = null; + /// + /// Parse a US textual date string, like e.g. "January 02, 2012". + /// + public static DateTime? ParseUSTextDate(string strDate, DateTimeKind k) + { + if(strDate == null) { Debug.Assert(false); return null; } + + if(m_vUSMonths == null) + m_vUSMonths = new string[]{ "January", "February", "March", + "April", "May", "June", "July", "August", "September", + "October", "November", "December" }; + + string str = strDate.Trim(); + for(int i = 0; i < m_vUSMonths.Length; ++i) + { + if(str.StartsWith(m_vUSMonths[i], StrUtil.CaseIgnoreCmp)) + { + str = str.Substring(m_vUSMonths[i].Length); + string[] v = str.Split(new char[]{ ',', ';' }); + if((v == null) || (v.Length != 2)) return null; + + string strDay = v[0].Trim().TrimStart('0'); + int iDay, iYear; + if(int.TryParse(strDay, out iDay) && + int.TryParse(v[1].Trim(), out iYear)) + return new DateTime(iYear, i + 1, iDay, 0, 0, 0, k); + else { Debug.Assert(false); return null; } + } + } + + return null; + } +#endif + + private static readonly DateTime m_dtInvMin = + new DateTime(2999, 12, 27, 23, 59, 59, DateTimeKind.Utc); + private static readonly DateTime m_dtInvMax = + new DateTime(2999, 12, 29, 23, 59, 59, DateTimeKind.Utc); + public static int Compare(DateTime dtA, DateTime dtB, bool bUnkIsPast) + { + Debug.Assert(dtA.Kind == dtB.Kind); + + if(bUnkIsPast) + { + // 2999-12-28 23:59:59 in KeePass 1.x means 'unknown'; + // expect time zone corruption (twice) + // bool bInvA = ((dtA.Year == 2999) && (dtA.Month == 12) && + // (dtA.Day >= 27) && (dtA.Day <= 29) && (dtA.Minute == 59) && + // (dtA.Second == 59)); + // bool bInvB = ((dtB.Year == 2999) && (dtB.Month == 12) && + // (dtB.Day >= 27) && (dtB.Day <= 29) && (dtB.Minute == 59) && + // (dtB.Second == 59)); + // Faster due to internal implementation of DateTime: + bool bInvA = ((dtA >= m_dtInvMin) && (dtA <= m_dtInvMax) && + (dtA.Minute == 59) && (dtA.Second == 59)); + bool bInvB = ((dtB >= m_dtInvMin) && (dtB <= m_dtInvMax) && + (dtB.Minute == 59) && (dtB.Second == 59)); + + if(bInvA) return (bInvB ? 0 : -1); + if(bInvB) return 1; + } + + return dtA.CompareTo(dtB); + } + + internal static int CompareLastMod(ITimeLogger tlA, ITimeLogger tlB, + bool bUnkIsPast) + { + if(tlA == null) { Debug.Assert(false); return ((tlB == null) ? 0 : -1); } + if(tlB == null) { Debug.Assert(false); return 1; } + + return Compare(tlA.LastModificationTime, tlB.LastModificationTime, + bUnkIsPast); + } + + public static DateTime ToUtc(DateTime dt, bool bUnspecifiedIsUtc) + { + DateTimeKind k = dt.Kind; + if(k == DateTimeKind.Utc) return dt; + if(k == DateTimeKind.Local) return dt.ToUniversalTime(); + + Debug.Assert(k == DateTimeKind.Unspecified); + if(bUnspecifiedIsUtc) + return new DateTime(dt.Ticks, DateTimeKind.Utc); + return dt.ToUniversalTime(); // Unspecified = local + } + + public static DateTime ToLocal(DateTime dt, bool bUnspecifiedIsLocal) + { + DateTimeKind k = dt.Kind; + if(k == DateTimeKind.Local) return dt; + if(k == DateTimeKind.Utc) return dt.ToLocalTime(); + + Debug.Assert(k == DateTimeKind.Unspecified); + if(bUnspecifiedIsLocal) + return new DateTime(dt.Ticks, DateTimeKind.Local); + return dt.ToLocalTime(); // Unspecified = UTC +} + + /* internal static DateTime RoundToMultOf2PowLess1s(DateTime dt) + { + long l2Pow = m_lTicks2PowLess1s; + if(l2Pow == 0) + { + l2Pow = 1; + while(true) + { + l2Pow <<= 1; + if(l2Pow >= TimeSpan.TicksPerSecond) break; + } + l2Pow >>= 1; + m_lTicks2PowLess1s = l2Pow; + + Debug.Assert(TimeSpan.TicksPerSecond == 10000000L); // .NET + Debug.Assert(l2Pow == (1L << 23)); // .NET + } + + long l = dt.Ticks; + if((l % l2Pow) == 0L) return dt; + + // Round down to full second + l /= TimeSpan.TicksPerSecond; + l *= TimeSpan.TicksPerSecond; + + // Round up to multiple of l2Pow + long l2PowM1 = l2Pow - 1L; + l = (l + l2PowM1) & ~l2PowM1; + DateTime dtRnd = new DateTime(l, dt.Kind); + + Debug.Assert((dtRnd.Ticks % l2Pow) == 0L); + Debug.Assert(dtRnd.ToString("u") == dt.ToString("u")); + return dtRnd; + } */ + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Utility/UrlUtil.cs b/src/KeePassLib2AndroidSdkStyle/Utility/UrlUtil.cs new file mode 100644 index 00000000..cca71920 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Utility/UrlUtil.cs @@ -0,0 +1,861 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; + +using KeePassLib.Native; + +namespace KeePassLib.Utility +{ + /// + /// A class containing various static path utility helper methods (like + /// stripping extension from a file, etc.). + /// + public static class UrlUtil + { + private static readonly char[] g_vPathTrimCharsWs = new char[] { + '\"', ' ', '\t', '\r', '\n' }; + + public static char LocalDirSepChar + { + get { return Path.DirectorySeparatorChar; } + } + + 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: + /// passing C:\\My Documents\\My File.kdb in + /// and true to + /// would produce this string: C:\\My Documents\\. + /// + /// Full path of a file. + /// Append a terminating directory separator + /// character to the returned path. + /// If true, the returned path + /// is guaranteed to be a valid directory path (for example X:\\ instead + /// of X:, overriding ). + /// This should only be set to true, if the returned path is directly + /// passed to some directory API. + /// Directory of the file. + public static string GetFileDirectory(string strFile, bool bAppendTerminatingChar, + bool bEnsureValidDirSpec) + { + Debug.Assert(strFile != null); + if (strFile == null) throw new ArgumentNullException("strFile"); + + int nLastSep = strFile.LastIndexOfAny(UrlUtil.DirSepChars); + if (nLastSep < 0) return string.Empty; // No directory + + if (bEnsureValidDirSpec && (nLastSep == 2) && (strFile[1] == ':') && + (strFile[2] == '\\')) // Length >= 3 and Windows root directory + bAppendTerminatingChar = true; + + if (!bAppendTerminatingChar) return strFile.Substring(0, nLastSep); + return EnsureTerminatingSeparator(strFile.Substring(0, nLastSep), + (strFile[nLastSep] == '/')); + } + + /// + /// Gets the file name of the specified file (full path). Example: + /// if is C:\\My Documents\\My File.kdb + /// the returned string is My File.kdb. + /// + /// Full path of a file. + /// File name of the specified file. The return value is + /// an empty string ("") if the input parameter is null. + public static string GetFileName(string strPath) + { + Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath"); + + int nLastSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars); + + if (nLastSep < 0) return strPath; + if (nLastSep >= (strPath.Length - 1)) return string.Empty; + + return strPath.Substring(nLastSep + 1); + } + + /// + /// Strip the extension of a file. + /// + /// Full path of a file with extension. + /// File name without extension. + public static string StripExtension(string strPath) + { + Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath"); + + int nLastDirSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars); + int nLastExtDot = strPath.LastIndexOf('.'); + + if (nLastExtDot <= nLastDirSep) return strPath; + + return strPath.Substring(0, nLastExtDot); + } + + /// + /// Get the extension of a file. + /// + /// Full path of a file with extension. + /// Extension without prepending dot. + public static string GetExtension(string strPath) + { + Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath"); + + int nLastDirSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars); + int nLastExtDot = strPath.LastIndexOf('.'); + + if (nLastExtDot <= nLastDirSep) return string.Empty; + if (nLastExtDot == (strPath.Length - 1)) return string.Empty; + + return strPath.Substring(nLastExtDot + 1); + } + + /// + /// Ensure that a path is terminated with a directory separator character. + /// + /// Input path. + /// If true, a slash (/) is appended to + /// the string if it's not terminated already. If false, the + /// default system directory separator character is used. + /// Path having a directory separator as last character. + public static string EnsureTerminatingSeparator(string strPath, bool bUrl) + { + Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath"); + + int nLength = strPath.Length; + if (nLength <= 0) return string.Empty; + + char chLast = strPath[nLength - 1]; + if (Array.IndexOf(UrlUtil.DirSepChars, chLast) >= 0) + return strPath; + + if (bUrl) return (strPath + '/'); + return (strPath + UrlUtil.LocalDirSepChar); + } + + /* /// + /// File access mode enumeration. Used by the FileAccessible + /// method. + /// + public enum FileAccessMode + { + /// + /// Opening a file in read mode. The specified file must exist. + /// + Read = 0, + + /// + /// Opening a file in create mode. If the file exists already, it + /// will be overwritten. If it doesn't exist, it will be created. + /// The return value is true, if data can be written to the + /// file. + /// + Create + } */ + + /* /// + /// Test if a specified path is accessible, either in read or write mode. + /// + /// Path to test. + /// Requested file access mode. + /// Returns true if the specified path is accessible in + /// the requested mode, otherwise the return value is false. + public static bool FileAccessible(string strFilePath, FileAccessMode fMode) + { + Debug.Assert(strFilePath != null); + if(strFilePath == null) throw new ArgumentNullException("strFilePath"); + + if(fMode == FileAccessMode.Read) + { + FileStream fs; + + try { fs = File.OpenRead(strFilePath); } + catch(Exception) { return false; } + if(fs == null) return false; + + fs.Close(); + return true; + } + else if(fMode == FileAccessMode.Create) + { + FileStream fs; + + try { fs = File.Create(strFilePath); } + catch(Exception) { return false; } + if(fs == null) return false; + + fs.Close(); + return true; + } + + 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; } + + string str = strPath.Trim(); + if (str.Length <= 1) return str; + if (str[0] != '\"') return str; + + int iSecond = IndexOfSecondEnclQuote(str); + if (iSecond <= 0) return str; + + return str.Substring(1, iSecond - 1); + } + + public static string FileUrlToPath(string strUrl) + { + if (strUrl == null) { Debug.Assert(false); throw new ArgumentNullException("strUrl"); } + if (strUrl.Length == 0) { Debug.Assert(false); return string.Empty; } + + if (!strUrl.StartsWith(Uri.UriSchemeFile + ":", StrUtil.CaseIgnoreCmp)) + { + Debug.Assert(false); + return strUrl; + } + + try + { + Uri uri = new Uri(strUrl); + string str = uri.LocalPath; + if (!string.IsNullOrEmpty(str)) return str; + } + catch (Exception) { Debug.Assert(false); } + + Debug.Assert(false); + return strUrl; + } + + public static bool UnhideFile(string strFile) + { +#if KeePassLibSD + return false; +#else + if (strFile == null) throw new ArgumentNullException("strFile"); + + try + { + FileAttributes fa = File.GetAttributes(strFile); + if ((long)(fa & FileAttributes.Hidden) == 0) return false; + + return HideFile(strFile, false); + } + catch (Exception) { } + + return false; +#endif + } + + public static bool HideFile(string strFile, bool bHide) + { +#if KeePassLibSD + return false; +#else + if (strFile == null) throw new ArgumentNullException("strFile"); + + try + { + FileAttributes fa = File.GetAttributes(strFile); + + if (bHide) fa = ((fa & ~FileAttributes.Normal) | FileAttributes.Hidden); + else // Unhide + { + fa &= ~FileAttributes.Hidden; + if ((long)fa == 0) fa = FileAttributes.Normal; + } + + File.SetAttributes(strFile, fa); + return true; + } + catch (Exception) { } + + return false; +#endif + } + + public static string MakeRelativePath(string strBaseFile, string strTargetFile) + { + if (strBaseFile == null) throw new ArgumentNullException("strBasePath"); + if (strTargetFile == null) throw new ArgumentNullException("strTargetPath"); + if (strBaseFile.Length == 0) return strTargetFile; + if (strTargetFile.Length == 0) return string.Empty; + + // Test whether on different Windows drives + if ((strBaseFile.Length >= 3) && (strTargetFile.Length >= 3)) + { + if ((strBaseFile[1] == ':') && (strTargetFile[1] == ':') && + (strBaseFile[2] == '\\') && (strTargetFile[2] == '\\') && + (strBaseFile[0] != strTargetFile[0])) + return strTargetFile; + } + +#if (!KeePassLibSD && !KeePassUAP) + if (NativeLib.IsUnix()) + { +#endif + bool bBaseUnc = IsUncPath(strBaseFile); + bool bTargetUnc = IsUncPath(strTargetFile); + if ((!bBaseUnc && bTargetUnc) || (bBaseUnc && !bTargetUnc)) + return strTargetFile; + + string strBase = GetShortestAbsolutePath(strBaseFile); + string strTarget = GetShortestAbsolutePath(strTargetFile); + string[] vBase = strBase.Split(UrlUtil.DirSepChars); + string[] vTarget = strTarget.Split(UrlUtil.DirSepChars); + + int i = 0; + while ((i < (vBase.Length - 1)) && (i < (vTarget.Length - 1)) && + (vBase[i] == vTarget[i])) { ++i; } + + StringBuilder sbRel = new StringBuilder(); + for (int j = i; j < (vBase.Length - 1); ++j) + { + if (sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar); + sbRel.Append(".."); + } + for (int k = i; k < vTarget.Length; ++k) + { + if (sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar); + sbRel.Append(vTarget[k]); + } + + return sbRel.ToString(); +#if (!KeePassLibSD && !KeePassUAP) + } + + try // Windows + { + const int nMaxPath = NativeMethods.MAX_PATH * 2; + StringBuilder sb = new StringBuilder(nMaxPath + 2); + if (!NativeMethods.PathRelativePathTo(sb, strBaseFile, 0, + strTargetFile, 0)) + return strTargetFile; + + string str = sb.ToString(); + while (str.StartsWith(".\\")) str = str.Substring(2, str.Length - 2); + + return str; + } + catch (Exception) { Debug.Assert(false); } + return strTargetFile; +#endif + } + + public static string MakeAbsolutePath(string strBaseFile, string strTargetFile) + { + if (strBaseFile == null) throw new ArgumentNullException("strBasePath"); + if (strTargetFile == null) throw new ArgumentNullException("strTargetPath"); + if (strBaseFile.Length == 0) return strTargetFile; + if (strTargetFile.Length == 0) return string.Empty; + + if (IsAbsolutePath(strTargetFile)) return strTargetFile; + + string strBaseDir = GetFileDirectory(strBaseFile, true, false); + return GetShortestAbsolutePath(strBaseDir + strTargetFile); + } + + public static bool IsAbsolutePath(string strPath) + { + if (strPath == null) throw new ArgumentNullException("strPath"); + if (strPath.Length == 0) return false; + + if (IsUncPath(strPath)) return true; + + try { return Path.IsPathRooted(strPath); } + catch (Exception) { Debug.Assert(false); } + + return true; + } + + public static string GetShortestAbsolutePath(string strPath) + { + if (strPath == null) throw new ArgumentNullException("strPath"); + if (strPath.Length == 0) return string.Empty; + + // Path.GetFullPath is incompatible with UNC paths traversing over + // different server shares (which are created by PathRelativePathTo); + // we need to build the absolute path on our own... + if (IsUncPath(strPath)) + { + char chSep = strPath[0]; + char[] vSep = ((chSep == '/') ? (new char[] { '/' }) : + (new char[] { '\\', '/' })); + + List l = new List(); +#if !KeePassLibSD + string[] v = strPath.Split(vSep, StringSplitOptions.None); +#else + string[] v = strPath.Split(vSep); +#endif + Debug.Assert((v.Length >= 3) && (v[0].Length == 0) && + (v[1].Length == 0)); + + foreach (string strPart in v) + { + if (strPart.Equals(".")) continue; + else if (strPart.Equals("..")) + { + if (l.Count > 0) l.RemoveAt(l.Count - 1); + else { Debug.Assert(false); } + } + else l.Add(strPart); // Do not ignore zero length parts + } + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < l.Count; ++i) + { + // Don't test length of sb, might be 0 due to initial UNC seps + if (i > 0) sb.Append(chSep); + + sb.Append(l[i]); + } + + return sb.ToString(); + } + + string str; + try { str = Path.GetFullPath(strPath); } + catch (Exception) { Debug.Assert(false); return strPath; } + + 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); + } + + return str; + } + + public static int GetUrlLength(string strText, int nOffset) + { + if (strText == null) throw new ArgumentNullException("strText"); + if (nOffset > strText.Length) throw new ArgumentException(); // Not >= (0 len) + + int iPosition = nOffset, nLength = 0, nStrLen = strText.Length; + + while (iPosition < nStrLen) + { + char ch = strText[iPosition]; + ++iPosition; + + if ((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n')) + break; + + ++nLength; + } + + 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 i = strUrl.IndexOf(':'); + if (i < 0) return strUrl; // No scheme to remove + ++i; + + // 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 + + return strUrl.Substring(i); + } + + public static string ConvertSeparators(string strPath) + { + return ConvertSeparators(strPath, UrlUtil.LocalDirSepChar); + } + + public static string ConvertSeparators(string strPath, char chSeparator) + { + if (string.IsNullOrEmpty(strPath)) return string.Empty; + + strPath = strPath.Replace('/', chSeparator); + strPath = strPath.Replace('\\', chSeparator); + + return strPath; + } + + public static bool IsUncPath(string strPath) + { + if (strPath == null) throw new ArgumentNullException("strPath"); + + return (strPath.StartsWith("\\\\") || strPath.StartsWith("//")); + } + + public static string FilterFileName(string strName) + { + if (string.IsNullOrEmpty(strName)) { Debug.Assert(false); return string.Empty; } + + // https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file + + StringBuilder sb = new StringBuilder(strName.Length); + foreach (char ch in strName) + { + if (ch < '\u0020') continue; + + 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(); + } + + /// + /// Get the host component of a URL. + /// This method is faster and more fault-tolerant than creating + /// an Uri object and querying its Host + /// property. + /// + /// + /// For the input s://u:p@d.tld:p/p?q#f the return + /// value is d.tld. + /// + public static string GetHost(string strUrl) + { + if (strUrl == null) { Debug.Assert(false); return string.Empty; } + + StringBuilder sb = new StringBuilder(); + bool bInExtHost = false; + for (int i = 0; i < strUrl.Length; ++i) + { + char ch = strUrl[i]; + if (bInExtHost) + { + if (ch == '/') + { + if (sb.Length == 0) { } // Ignore leading '/'s + else break; + } + else sb.Append(ch); + } + else // !bInExtHost + { + if (ch == ':') bInExtHost = true; + } + } + + string str = sb.ToString(); + if (str.Length == 0) str = strUrl; + + // Remove the login part + int nLoginLen = str.IndexOf('@'); + if (nLoginLen >= 0) str = str.Substring(nLoginLen + 1); + + // Remove the port + int iPort = str.LastIndexOf(':'); + if (iPort >= 0) str = str.Substring(0, iPort); + + return str; + } + + public static bool AssemblyEquals(string strExt, string strShort) + { + if ((strExt == null) || (strShort == null)) { Debug.Assert(false); return false; } + + if (strExt.Equals(strShort, StrUtil.CaseIgnoreCmp) || + strExt.StartsWith(strShort + ",", StrUtil.CaseIgnoreCmp)) + return true; + + if (!strShort.EndsWith(".dll", StrUtil.CaseIgnoreCmp)) + { + if (strExt.Equals(strShort + ".dll", StrUtil.CaseIgnoreCmp) || + strExt.StartsWith(strShort + ".dll,", StrUtil.CaseIgnoreCmp)) + return true; + } + + if (!strShort.EndsWith(".exe", StrUtil.CaseIgnoreCmp)) + { + if (strExt.Equals(strShort + ".exe", StrUtil.CaseIgnoreCmp) || + strExt.StartsWith(strShort + ".exe,", StrUtil.CaseIgnoreCmp)) + return true; + } + + return false; + } + + public static string GetTempPath() + { + string strDir; + if (NativeLib.IsUnix()) + strDir = NativeMethods.GetUserRuntimeDir(); +#if KeePassUAP + else strDir = Windows.Storage.ApplicationData.Current.TemporaryFolder.Path; +#else + else strDir = Path.GetTempPath(); +#endif + + try + { + if (!Directory.Exists(strDir)) Directory.CreateDirectory(strDir); + } + catch (Exception) { Debug.Assert(false); } + + return strDir; + } + +#if !KeePassLibSD + // Structurally mostly equivalent to UrlUtil.GetFileInfos + public static List GetFilePaths(string strDir, string strPattern, + SearchOption opt) + { + List l = new List(); + if (strDir == null) { Debug.Assert(false); return l; } + if (strPattern == null) { Debug.Assert(false); return l; } + + string[] v = Directory.GetFiles(strDir, strPattern, opt); + if (v == null) { Debug.Assert(false); return l; } + + // Only accept files with the correct extension; GetFiles may + // return additional files, see GetFiles documentation + string strExt = GetExtension(strPattern); + if (!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) && + (strExt.IndexOf('?') < 0)) + { + strExt = "." + strExt; + + foreach (string strPathRaw in v) + { + if (strPathRaw == null) { Debug.Assert(false); continue; } + string strPath = strPathRaw.Trim(g_vPathTrimCharsWs); + if (strPath.Length == 0) { Debug.Assert(false); continue; } + Debug.Assert(strPath == strPathRaw); + + if (strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp)) + l.Add(strPathRaw); + } + } + else l.AddRange(v); + + return l; + } + + // Structurally mostly equivalent to UrlUtil.GetFilePaths + public static List GetFileInfos(DirectoryInfo di, string strPattern, + SearchOption opt) + { + List l = new List(); + if (di == null) { Debug.Assert(false); return l; } + if (strPattern == null) { Debug.Assert(false); return l; } + + FileInfo[] v = di.GetFiles(strPattern, opt); + if (v == null) { Debug.Assert(false); return l; } + + // Only accept files with the correct extension; GetFiles may + // return additional files, see GetFiles documentation + string strExt = GetExtension(strPattern); + if (!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) && + (strExt.IndexOf('?') < 0)) + { + strExt = "." + strExt; + + foreach (FileInfo fi in v) + { + if (fi == null) { Debug.Assert(false); continue; } + string strPathRaw = fi.FullName; + if (strPathRaw == null) { Debug.Assert(false); continue; } + string strPath = strPathRaw.Trim(g_vPathTrimCharsWs); + if (strPath.Length == 0) { Debug.Assert(false); continue; } + Debug.Assert(strPath == strPathRaw); + + if (strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp)) + l.Add(fi); + } + } + else l.AddRange(v); + + return l; + } +#endif + + + public static char GetDriveLetter(string strPath) + { + if (strPath == null) throw new ArgumentNullException("strPath"); + + Debug.Assert(default(char) == '\0'); + if (strPath.Length < 3) return '\0'; + if ((strPath[1] != ':') || (strPath[2] != '\\')) return '\0'; + + 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; + } + + internal static string GetCanonicalUri(string strUri) + { + if (string.IsNullOrEmpty(strUri)) { Debug.Assert(false); return strUri; } + + try + { + Uri uri = new Uri(strUri); + + if (uri.IsAbsoluteUri) return uri.AbsoluteUri; + else { Debug.Assert(false); } + } + catch (Exception) { Debug.Assert(false); } + + return strUri; + } + + /* internal static Dictionary ParseQuery(string strQuery) + { + Dictionary d = new Dictionary(); + if(string.IsNullOrEmpty(strQuery)) return d; + + string[] vKvp = strQuery.Split(new char[] { '?', '&' }); + if(vKvp == null) { Debug.Assert(false); return d; } + + foreach(string strKvp in vKvp) + { + if(string.IsNullOrEmpty(strKvp)) continue; + + string strKey, strValue; + int iSep = strKvp.IndexOf('='); + if(iSep < 0) + { + strKey = strKvp; + strValue = string.Empty; + } + else + { + strKey = strKvp.Substring(0, iSep); + strValue = strKvp.Substring(iSep + 1); + } + + strKey = Uri.UnescapeDataString(strKey); + strValue = Uri.UnescapeDataString(strValue); + + d[strKey] = strValue; + } + + return d; + } */ + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/Utility/XmlUtilEx.cs b/src/KeePassLib2AndroidSdkStyle/Utility/XmlUtilEx.cs new file mode 100644 index 00000000..097ed979 --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/Utility/XmlUtilEx.cs @@ -0,0 +1,290 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2021 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Xml; +using System.Xml.Serialization; +using System.Xml.XPath; + +using KeePassLib.Delegates; +using KeePassLib.Interfaces; +using KeePassLib.Serialization; + +namespace KeePassLib.Utility +{ + public static class XmlUtilEx + { + public static XmlDocument CreateXmlDocument() + { + XmlDocument d = new XmlDocument(); + + // .NET 4.5.2 and newer do not resolve external XML resources + // by default; for older .NET versions, we explicitly + // prevent resolving + d.XmlResolver = null; // Default in old .NET: XmlUrlResolver object + + return d; + } + + public static XmlReaderSettings CreateXmlReaderSettings() + { + XmlReaderSettings xrs = new XmlReaderSettings(); + + xrs.CloseInput = false; + xrs.IgnoreComments = true; + xrs.IgnoreProcessingInstructions = true; + xrs.IgnoreWhitespace = true; + +#if KeePassUAP + xrs.DtdProcessing = DtdProcessing.Prohibit; +#else + // 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; + xrs.XmlResolver = null; + + return xrs; + } + + public static XmlReader CreateXmlReader(Stream s) + { + if(s == null) { Debug.Assert(false); throw new ArgumentNullException("s"); } + + return XmlReader.Create(s, CreateXmlReaderSettings()); + } + + public static XmlWriterSettings CreateXmlWriterSettings() + { + XmlWriterSettings xws = new XmlWriterSettings(); + + xws.CloseOutput = false; + xws.Encoding = StrUtil.Utf8; + xws.Indent = true; + xws.IndentChars = "\t"; + xws.NewLineOnAttributes = false; + + return xws; + } + + public static XmlWriter CreateXmlWriter(Stream s) + { + if(s == null) { Debug.Assert(false); throw new ArgumentNullException("s"); } + + return XmlWriter.Create(s, CreateXmlWriterSettings()); + } + + public static T Deserialize(Stream s) + { + if(s == null) { Debug.Assert(false); throw new ArgumentNullException("s"); } + + XmlSerializer xs = new XmlSerializer(typeof(T)); + + T t = default(T); + using(XmlReader xr = CreateXmlReader(s)) + { + t = (T)xs.Deserialize(xr); + } + + return t; + } + + public static void Serialize(Stream s, T t) + { + if(s == null) { Debug.Assert(false); throw new ArgumentNullException("s"); } + + XmlSerializer xs = new XmlSerializer(typeof(T)); + using(XmlWriter xw = CreateXmlWriter(s)) + { + xs.Serialize(xw, t); + } + } + + internal static void Serialize(Stream s, T t, bool bRemoveXsdXsi) + { + // One way to remove the "xsd" and "xsi" namespace declarations + // is to use an XmlSerializerNamespaces object containing only + // a ""/"" pair; this seems to work, but Microsoft's + // documentation explicitly states that it isn't supported: + // https://docs.microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlserializernamespaces + // There are other, more complex ways, but these either rely on + // undocumented details or require the type T to be modified. + + string str; + using(MemoryStream ms = new MemoryStream()) + { + Serialize(ms, t); + + str = StrUtil.Utf8.GetString(ms.ToArray()); + } + + Func fFindPfx = delegate(string strText, string strSub) + { + int i = strText.IndexOf(strSub, StringComparison.Ordinal); + if(i < 0) return false; + if(i == 0) return true; + return char.IsWhiteSpace(strText[i - 1]); + }; + + if(bRemoveXsdXsi) + { + if(!fFindPfx(str, "xsd:") && !fFindPfx(str, "xsi:")) + { + Debug.Assert(str.IndexOf("xmlns:xsd") > 0); + str = str.Replace(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"", string.Empty); + Debug.Assert(str.IndexOf("xmlns:xsd") < 0); + + Debug.Assert(str.IndexOf("xmlns:xsi") > 0); + str = str.Replace(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", string.Empty); + Debug.Assert(str.IndexOf("xmlns:xsi") < 0); + } + else { Debug.Assert(false); } // "xsd"/"xsi" decl. may be required + } + + MemUtil.Write(s, StrUtil.Utf8.GetBytes(str)); + } + +#if DEBUG + internal static void ValidateXml(string strXml, bool bReplaceStdEntities) + { + if(strXml == null) throw new ArgumentNullException("strXml"); + if(strXml.Length == 0) { Debug.Assert(false); return; } + + string str = strXml; + + if(bReplaceStdEntities) + str = str.Replace(" ", " "); + + XmlDocument d = new XmlDocument(); + d.LoadXml(str); + } +#endif + + internal static XPathNodeIterator FindNodes(PwDatabase pd, string strXPath, + IStatusLogger sl, out XmlDocument xd) + { + if(pd == null) throw new ArgumentNullException("pd"); + if(strXPath == null) { Debug.Assert(false); strXPath = string.Empty; } + + KdbxFile kdbx = new KdbxFile(pd); + + byte[] pbXml; + using(MemoryStream ms = new MemoryStream()) + { + kdbx.Save(ms, null, KdbxFormat.PlainXml, sl); + pbXml = ms.ToArray(); + } + string strXml = StrUtil.Utf8.GetString(pbXml); + + xd = CreateXmlDocument(); + xd.LoadXml(strXml); + + XPathNavigator xpNav = xd.CreateNavigator(); + return xpNav.Select(strXPath); + // XPathExpression xpExpr = xpNav.Compile(strXPath); + // xpExpr.SetContext(new XuXsltContext()); + // return xpNav.Select(xpExpr); + } + + /* private sealed class XuFnMatches : IXsltContextFunction + { + private readonly XPathResultType[] m_vArgTypes = new XPathResultType[] { + XPathResultType.String, XPathResultType.String, XPathResultType.String + }; + public XPathResultType[] ArgTypes { get { return m_vArgTypes; } } + + public int Maxargs { get { return 3; } } + public int Minargs { get { return 2; } } + + public XPathResultType ReturnType { get { return XPathResultType.Boolean; } } + + private static string GetArgString(object[] args, int i, string strDefault) + { + if(args == null) { Debug.Assert(false); return strDefault; } + if(i >= args.Length) return strDefault; + + object o = args[i]; + if(o == null) return strDefault; + + XPathNodeIterator it = (o as XPathNodeIterator); + if(it != null) o = it.Current.Value; + + return (o.ToString() ?? strDefault); + } + + public object Invoke(XsltContext xsltContext, object[] args, + XPathNavigator docContext) + { + string strInput = GetArgString(args, 0, string.Empty); + string strPattern = GetArgString(args, 1, string.Empty); + string strFlags = GetArgString(args, 2, null); + + RegexOptions ro = RegexOptions.None; + if(!string.IsNullOrEmpty(strFlags)) + { + if(strFlags.IndexOf('s') >= 0) ro |= RegexOptions.Singleline; + if(strFlags.IndexOf('m') >= 0) ro |= RegexOptions.Multiline; + if(strFlags.IndexOf('i') >= 0) ro |= RegexOptions.IgnoreCase; + if(strFlags.IndexOf('x') >= 0) ro |= RegexOptions.IgnorePatternWhitespace; + } + + return Regex.IsMatch(strInput, strPattern, ro); + } + } + + private sealed class XuXsltContext : XsltContext + { + public override bool Whitespace { get { return false; } } + + public override int CompareDocument(string baseUri, string nextbaseUri) + { + return string.CompareOrdinal(baseUri, nextbaseUri); + } + + public override bool PreserveWhitespace(XPathNavigator node) + { + return false; + } + + public override IXsltContextFunction ResolveFunction(string prefix, + string name, XPathResultType[] ArgTypes) + { + if(prefix != "kp") { Debug.Assert(false); return null; } + + if(name == "matches") return new XuFnMatches(); + + Debug.Assert(false); + return null; + } + + public override IXsltContextVariable ResolveVariable(string prefix, + string name) + { + Debug.Assert(false); + return null; + } + } */ + } +} diff --git a/src/KeePassLib2AndroidSdkStyle/packages.config b/src/KeePassLib2AndroidSdkStyle/packages.config new file mode 100644 index 00000000..6b8deb9c --- /dev/null +++ b/src/KeePassLib2AndroidSdkStyle/packages.config @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Kp2aAutofillParserSdkStyle/AutofillParser.cs b/src/Kp2aAutofillParserSdkStyle/AutofillParser.cs new file mode 100644 index 00000000..718cfb5a --- /dev/null +++ b/src/Kp2aAutofillParserSdkStyle/AutofillParser.cs @@ -0,0 +1,987 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Newtonsoft.Json; +using Formatting = System.Xml.Formatting; + +namespace Kp2aAutofillParser +{ + public class W3cHints + { + + // Supported W3C autofill tokens (https://html.spec.whatwg.org/multipage/forms.html#autofill) + public const string HONORIFIC_PREFIX = "honorific-prefix"; + public const string NAME = "name"; + public const string GIVEN_NAME = "given-name"; + public const string ADDITIONAL_NAME = "additional-name"; + public const string FAMILY_NAME = "family-name"; + public const string HONORIFIC_SUFFIX = "honorific-suffix"; + public const string USERNAME = "username"; + public const string NEW_PASSWORD = "new-password"; + public const string CURRENT_PASSWORD = "current-password"; + public const string ORGANIZATION_TITLE = "organization-title"; + public const string ORGANIZATION = "organization"; + public const string STREET_ADDRESS = "street-address"; + public const string ADDRESS_LINE1 = "address-line1"; + public const string ADDRESS_LINE2 = "address-line2"; + public const string ADDRESS_LINE3 = "address-line3"; + public const string ADDRESS_LEVEL4 = "address-level4"; + public const string ADDRESS_LEVEL3 = "address-level3"; + public const string ADDRESS_LEVEL2 = "address-level2"; + public const string ADDRESS_LEVEL1 = "address-level1"; + public const string COUNTRY = "country"; + public const string COUNTRY_NAME = "country-name"; + public const string POSTAL_CODE = "postal-code"; + public const string CC_NAME = "cc-name"; + public const string CC_GIVEN_NAME = "cc-given-name"; + public const string CC_ADDITIONAL_NAME = "cc-additional-name"; + public const string CC_FAMILY_NAME = "cc-family-name"; + public const string CC_NUMBER = "cc-number"; + public const string CC_EXPIRATION = "cc-exp"; + public const string CC_EXPIRATION_MONTH = "cc-exp-month"; + public const string CC_EXPIRATION_YEAR = "cc-exp-year"; + public const string CC_CSC = "cc-csc"; + public const string CC_TYPE = "cc-type"; + public const string TRANSACTION_CURRENCY = "transaction-currency"; + public const string TRANSACTION_AMOUNT = "transaction-amount"; + public const string LANGUAGE = "language"; + public const string BDAY = "bday"; + public const string BDAY_DAY = "bday-day"; + public const string BDAY_MONTH = "bday-month"; + public const string BDAY_YEAR = "bday-year"; + public const string SEX = "sex"; + public const string URL = "url"; + public const string PHOTO = "photo"; + // Optional W3C prefixes + public const string PREFIX_SECTION = "section-"; + public const string SHIPPING = "shipping"; + public const string BILLING = "billing"; + // W3C prefixes below... + public const string PREFIX_HOME = "home"; + public const string PREFIX_WORK = "work"; + public const string PREFIX_FAX = "fax"; + public const string PREFIX_PAGER = "pager"; + // ... require those suffix + public const string TEL = "tel"; + public const string TEL_COUNTRY_CODE = "tel-country-code"; + public const string TEL_NATIONAL = "tel-national"; + public const string TEL_AREA_CODE = "tel-area-code"; + public const string TEL_LOCAL = "tel-local"; + public const string TEL_LOCAL_PREFIX = "tel-local-prefix"; + public const string TEL_LOCAL_SUFFIX = "tel-local-suffix"; + public const string TEL_EXTENSION = "tel_extension"; + public const string EMAIL = "email"; + public const string IMPP = "impp"; + + private W3cHints() + { + } + + + + public static bool isW3cSectionPrefix(string hint) + { + return hint.ToLower().StartsWith(W3cHints.PREFIX_SECTION); + } + + public static bool isW3cAddressType(string hint) + { + switch (hint.ToLower()) + { + case W3cHints.SHIPPING: + case W3cHints.BILLING: + return true; + } + return false; + } + + public static bool isW3cTypePrefix(string hint) + { + switch (hint.ToLower()) + { + case W3cHints.PREFIX_WORK: + case W3cHints.PREFIX_FAX: + case W3cHints.PREFIX_HOME: + case W3cHints.PREFIX_PAGER: + return true; + } + return false; + } + + public static bool isW3cTypeHint(string hint) + { + switch (hint.ToLower()) + { + case W3cHints.TEL: + case W3cHints.TEL_COUNTRY_CODE: + case W3cHints.TEL_NATIONAL: + case W3cHints.TEL_AREA_CODE: + case W3cHints.TEL_LOCAL: + case W3cHints.TEL_LOCAL_PREFIX: + case W3cHints.TEL_LOCAL_SUFFIX: + case W3cHints.TEL_EXTENSION: + case W3cHints.EMAIL: + case W3cHints.IMPP: + return true; + } + return false; + } + } + /// + /// FilledAutofillFieldCollection is the model that holds all of the data on a client app's page, + /// plus the dataset name associated with it. + /// + public class FilledAutofillFieldCollection where FieldT:InputField + { + public Dictionary HintMap { get; } + public string DatasetName { get; set; } + + public FilledAutofillFieldCollection(Dictionary hintMap, string datasetName = "") + { + //recreate hint map making sure we compare case insensitive + HintMap = BuildHintMap(); + foreach (var p in hintMap) + HintMap.Add(p.Key, p.Value); + DatasetName = datasetName; + } + + public FilledAutofillFieldCollection() : this(BuildHintMap()) + { } + + private static Dictionary BuildHintMap() + { + return new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + /// + /// Adds a filledAutofillField to the collection, indexed by all of its hints. + /// + /// The add. + /// Filled autofill field. + public void Add(FilledAutofillField filledAutofillField) + { + foreach (string hint in filledAutofillField.AutofillHints) + { + if (AutofillHintsHelper.IsSupportedHint(hint)) + { + HintMap.TryAdd(hint, filledAutofillField); + } + } + + } + + + + + /// + /// Takes in a list of autofill hints (`autofillHints`), usually associated with a View or set of + /// Views. Returns whether any of the filled fields on the page have at least 1 of these + /// `autofillHint`s. + /// + /// true, if with hints was helpsed, false otherwise. + /// Autofill hints. + public bool HelpsWithHints(List autofillHints) + { + for (int i = 0; i < autofillHints.Count; i++) + { + var autofillHint = autofillHints[i]; + if (HintMap.ContainsKey(autofillHint) && !HintMap[autofillHint].IsNull()) + { + return true; + } + } + return false; + } + } + public class AutofillHintsHelper + { + public const string AutofillHint2faAppOtp = "2faAppOTPCode"; + public const string AutofillHintBirthDateDay = "birthDateDay"; + public const string AutofillHintBirthDateFull = "birthDateFull"; + public const string AutofillHintBirthDateMonth = "birthDateMonth"; + public const string AutofillHintBirthDateYear = "birthDateYear"; + public const string AutofillHintCreditCardExpirationDate = "creditCardExpirationDate"; + public const string AutofillHintCreditCardExpirationDay = "creditCardExpirationDay"; + public const string AutofillHintCreditCardExpirationMonth = "creditCardExpirationMonth"; + public const string AutofillHintCreditCardExpirationYear = "creditCardExpirationYear"; + public const string AutofillHintCreditCardNumber = "creditCardNumber"; + public const string AutofillHintCreditCardSecurityCode = "creditCardSecurityCode"; + public const string AutofillHintEmailAddress = "emailAddress"; + public const string AutofillHintEmailOtp = "emailOTPCode"; + public const string AutofillHintGender = "gender"; + public const string AutofillHintName = "name"; + public const string AutofillHintNewPassword = "newPassword"; + public const string AutofillHintNewUsername = "newUsername"; + public const string AutofillHintNotApplicable = "notApplicable"; + public const string AutofillHintPassword = "password"; + public const string AutofillHintPersonName = "personName"; + public const string AutofillHintPersonNameFAMILY = "personFamilyName"; + public const string AutofillHintPersonNameGIVEN = "personGivenName"; + public const string AutofillHintPersonNameMIDDLE = "personMiddleName"; + public const string AutofillHintPersonNameMIDDLE_INITIAL = "personMiddleInitial"; + public const string AutofillHintPersonNamePREFIX = "personNamePrefix"; + public const string AutofillHintPersonNameSUFFIX = "personNameSuffix"; + public const string AutofillHintPhone = "phone"; + public const string AutofillHintPhoneContryCode = "phoneCountryCode"; + public const string AutofillHintPostalAddressAPT_NUMBER = "aptNumber"; + public const string AutofillHintPostalAddressCOUNTRY = "addressCountry"; + public const string AutofillHintPostalAddressDEPENDENT_LOCALITY = "dependentLocality"; + public const string AutofillHintPostalAddressEXTENDED_ADDRESS = "extendedAddress"; + public const string AutofillHintPostalAddressEXTENDED_POSTAL_CODE = "extendedPostalCode"; + public const string AutofillHintPostalAddressLOCALITY = "addressLocality"; + public const string AutofillHintPostalAddressREGION = "addressRegion"; + public const string AutofillHintPostalAddressSTREET_ADDRESS = "streetAddress"; + public const string AutofillHintPostalCode = "postalCode"; + public const string AutofillHintPromoCode = "promoCode"; + public const string AutofillHintSMS_OTP = "smsOTPCode"; + public const string AutofillHintUPI_VPA = "upiVirtualPaymentAddress"; + public const string AutofillHintUsername = "username"; + public const string AutofillHintWifiPassword = "wifiPassword"; + public const string AutofillHintPhoneNational = "phoneNational"; + public const string AutofillHintPhoneNumber = "phoneNumber"; + public const string AutofillHintPhoneNumberDevice = "phoneNumberDevice"; + public const string AutofillHintPostalAddress = "postalAddress"; + + private static readonly HashSet _allSupportedHints = new HashSet(StringComparer.OrdinalIgnoreCase) + { + AutofillHintCreditCardExpirationDate, + AutofillHintCreditCardExpirationDay, + AutofillHintCreditCardExpirationMonth, + AutofillHintCreditCardExpirationYear, + AutofillHintCreditCardNumber, + AutofillHintCreditCardSecurityCode, + AutofillHintEmailAddress, + AutofillHintPhone, + AutofillHintName, + AutofillHintPassword, + AutofillHintPostalAddress, + AutofillHintPostalCode, + AutofillHintUsername, + W3cHints.HONORIFIC_PREFIX, + W3cHints.NAME, + W3cHints.GIVEN_NAME, + W3cHints.ADDITIONAL_NAME, + W3cHints.FAMILY_NAME, + W3cHints.HONORIFIC_SUFFIX, + W3cHints.USERNAME, + W3cHints.NEW_PASSWORD, + W3cHints.CURRENT_PASSWORD, + W3cHints.ORGANIZATION_TITLE, + W3cHints.ORGANIZATION, + W3cHints.STREET_ADDRESS, + W3cHints.ADDRESS_LINE1, + W3cHints.ADDRESS_LINE2, + W3cHints.ADDRESS_LINE3, + W3cHints.ADDRESS_LEVEL4, + W3cHints.ADDRESS_LEVEL3, + W3cHints.ADDRESS_LEVEL2, + W3cHints.ADDRESS_LEVEL1, + W3cHints.COUNTRY, + W3cHints.COUNTRY_NAME, + W3cHints.POSTAL_CODE, + W3cHints.CC_NAME, + W3cHints.CC_GIVEN_NAME, + W3cHints.CC_ADDITIONAL_NAME, + W3cHints.CC_FAMILY_NAME, + W3cHints.CC_NUMBER, + W3cHints.CC_EXPIRATION, + W3cHints.CC_EXPIRATION_MONTH, + W3cHints.CC_EXPIRATION_YEAR, + W3cHints.CC_CSC, + W3cHints.CC_TYPE, + W3cHints.TRANSACTION_CURRENCY, + W3cHints.TRANSACTION_AMOUNT, + W3cHints.LANGUAGE, + W3cHints.BDAY, + W3cHints.BDAY_DAY, + W3cHints.BDAY_MONTH, + W3cHints.BDAY_YEAR, + W3cHints.SEX, + W3cHints.URL, + W3cHints.PHOTO, + W3cHints.TEL, + W3cHints.TEL_COUNTRY_CODE, + W3cHints.TEL_NATIONAL, + W3cHints.TEL_AREA_CODE, + W3cHints.TEL_LOCAL, + W3cHints.TEL_LOCAL_PREFIX, + W3cHints.TEL_LOCAL_SUFFIX, + W3cHints.TEL_EXTENSION, + W3cHints.EMAIL, + W3cHints.IMPP, + }; + + private static readonly List> partitionsOfCanonicalHints = new List>() + { + + new HashSet(StringComparer.OrdinalIgnoreCase) + { + AutofillHintEmailAddress, + AutofillHintPhone, + AutofillHintName, + AutofillHintPassword, + AutofillHintUsername, + W3cHints.HONORIFIC_PREFIX, + W3cHints.EMAIL, + W3cHints.NAME, + W3cHints.GIVEN_NAME, + W3cHints.ADDITIONAL_NAME, + W3cHints.FAMILY_NAME, + W3cHints.HONORIFIC_SUFFIX, + W3cHints.ORGANIZATION_TITLE, + W3cHints.ORGANIZATION, + W3cHints.LANGUAGE, + W3cHints.BDAY, + W3cHints.BDAY_DAY, + W3cHints.BDAY_MONTH, + W3cHints.BDAY_YEAR, + W3cHints.SEX, + W3cHints.URL, + W3cHints.PHOTO, + W3cHints.TEL, + W3cHints.TEL_COUNTRY_CODE, + W3cHints.TEL_NATIONAL, + W3cHints.TEL_AREA_CODE, + W3cHints.TEL_LOCAL, + W3cHints.TEL_LOCAL_PREFIX, + W3cHints.TEL_LOCAL_SUFFIX, + W3cHints.TEL_EXTENSION, + W3cHints.IMPP, + }, + new HashSet(StringComparer.OrdinalIgnoreCase) + { + AutofillHintPostalAddress, + AutofillHintPostalCode, + + W3cHints.STREET_ADDRESS, + W3cHints.ADDRESS_LINE1, + W3cHints.ADDRESS_LINE2, + W3cHints.ADDRESS_LINE3, + W3cHints.ADDRESS_LEVEL4, + W3cHints.ADDRESS_LEVEL3, + W3cHints.ADDRESS_LEVEL2, + W3cHints.ADDRESS_LEVEL1, + W3cHints.COUNTRY, + W3cHints.COUNTRY_NAME + }, + new HashSet(StringComparer.OrdinalIgnoreCase) + { + AutofillHintCreditCardExpirationDate, + AutofillHintCreditCardExpirationDay, + AutofillHintCreditCardExpirationMonth, + AutofillHintCreditCardExpirationYear, + AutofillHintCreditCardNumber, + AutofillHintCreditCardSecurityCode, + + W3cHints.CC_NAME, + W3cHints.CC_GIVEN_NAME, + W3cHints.CC_ADDITIONAL_NAME, + W3cHints.CC_FAMILY_NAME, + W3cHints.CC_TYPE, + W3cHints.TRANSACTION_CURRENCY, + W3cHints.TRANSACTION_AMOUNT, + }, + + }; + + private static readonly Dictionary hintToCanonicalReplacement = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + {W3cHints.EMAIL, AutofillHintEmailAddress}, + {W3cHints.USERNAME, AutofillHintUsername}, + {W3cHints.CURRENT_PASSWORD, AutofillHintPassword}, + {W3cHints.NEW_PASSWORD, AutofillHintPassword}, + {W3cHints.CC_EXPIRATION_MONTH, AutofillHintCreditCardExpirationMonth }, + {W3cHints.CC_EXPIRATION_YEAR, AutofillHintCreditCardExpirationYear }, + {W3cHints.CC_EXPIRATION, AutofillHintCreditCardExpirationDate }, + {W3cHints.CC_NUMBER, AutofillHintCreditCardNumber }, + {W3cHints.CC_CSC, AutofillHintCreditCardSecurityCode }, + {W3cHints.POSTAL_CODE, AutofillHintPostalCode }, + + + }; + + public static bool IsSupportedHint(string hint) + { + return _allSupportedHints.Contains(hint); + } + + + public static string[] FilterForSupportedHints(string[] hints) + { + if (hints == null) + return Array.Empty(); + var filteredHints = new string[hints.Length]; + int i = 0; + foreach (var hint in hints) + { + if (IsSupportedHint(hint)) + { + filteredHints[i++] = hint; + } + + } + var finalFilteredHints = new string[i]; + Array.Copy(filteredHints, 0, finalFilteredHints, 0, i); + return finalFilteredHints; + } + + + + /// + /// transforms hints by replacing some W3cHints by their Android counterparts and transforming everything to lowercase + /// + public static List ConvertToCanonicalLowerCaseHints(string[] supportedHints) + { + List result = new List(); + foreach (string hint in supportedHints.Where(h => h != null)) + { + var canonicalHint = ToCanonicalHint(hint); + result.Add(canonicalHint.ToLower()); + } + return result; + + } + + public static string ToCanonicalHint(string hint) + { + string canonicalHint; + if (!hintToCanonicalReplacement.TryGetValue(hint, out canonicalHint)) + canonicalHint = hint; + return canonicalHint; + } + + public static int GetPartitionIndex(string hint) + { + for (int i = 0; i < partitionsOfCanonicalHints.Count; i++) + { + if (partitionsOfCanonicalHints[i].Contains(hint)) + { + return i; + } + } + return -1; + } + + public static FilledAutofillFieldCollection FilterForPartition(FilledAutofillFieldCollection autofillFields, int partitionIndex) where FieldT: InputField + { + FilledAutofillFieldCollection filteredCollection = + new FilledAutofillFieldCollection { DatasetName = autofillFields.DatasetName }; + + if (partitionIndex == -1) + return filteredCollection; + + foreach (var field in autofillFields.HintMap.Values.Distinct()) + { + foreach (var hint in field.AutofillHints) + { + if (GetPartitionIndex(hint) == partitionIndex) + { + filteredCollection.Add(field); + break; + } + } + } + + return filteredCollection; + } + + public static FilledAutofillFieldCollection FilterForPartition(FilledAutofillFieldCollection filledAutofillFieldCollection, List autofillFieldsFocusedAutofillCanonicalHints) where FieldT: InputField + { + + //only apply partition data if we have FocusedAutofillCanonicalHints. This may be empty on buggy Firefox. + if (autofillFieldsFocusedAutofillCanonicalHints.Any()) + { + int partitionIndex = AutofillHintsHelper.GetPartitionIndex(autofillFieldsFocusedAutofillCanonicalHints.FirstOrDefault()); + return AutofillHintsHelper.FilterForPartition(filledAutofillFieldCollection, partitionIndex); + } + + return filledAutofillFieldCollection; + } + } + /// + /// This enum represents the Android.Text.InputTypes values. For testability, this is duplicated here. + /// + public enum InputTypes + { + ClassDatetime = 4, + ClassNumber = 2, + ClassPhone = 3, + ClassText = 1, + DatetimeVariationDate = 16, + DatetimeVariationNormal = 0, + DatetimeVariationTime = 32, + MaskClass = 15, + MaskFlags = 16773120, + MaskVariation = 4080, + Null = 0, + NumberFlagDecimal = 8192, + NumberFlagSigned = 4096, + NumberVariationNormal = 0, + NumberVariationPassword = 16, + TextFlagAutoComplete = 65536, + TextFlagAutoCorrect = 32768, + TextFlagCapCharacters = 4096, + TextFlagCapSentences = 16384, + TextFlagCapWords = 8192, + TextFlagEnableTextConversionSuggestions = 1048576, + TextFlagImeMultiLine = 262144, + TextFlagMultiLine = 131072, + TextFlagNoSuggestions = 524288, + TextVariationEmailAddress = 32, + TextVariationEmailSubject = 48, + TextVariationFilter = 176, + TextVariationLongMessage = 80, + TextVariationNormal = 0, + TextVariationPassword = 128, + TextVariationPersonName = 96, + TextVariationPhonetic = 192, + TextVariationPostalAddress = 112, + TextVariationShortMessage = 64, + TextVariationUri = 16, + TextVariationVisiblePassword = 144, + TextVariationWebEditText = 160, + TextVariationWebEmailAddress = 208, + TextVariationWebPassword = 224 + } + + public interface IKp2aDigitalAssetLinksDataSource + { + bool IsTrustedApp(string packageName); + bool IsTrustedLink(string domain, string targetPackage); + bool IsEnabled(); + + } + + class TimeUtil + { + private static DateTime? m_dtUnixRoot = null; + public static DateTime ConvertUnixTime(double dtUnix) + { + try + { + if (!m_dtUnixRoot.HasValue) + m_dtUnixRoot = (new DateTime(1970, 1, 1, 0, 0, 0, 0, + DateTimeKind.Utc)).ToLocalTime(); + + return m_dtUnixRoot.Value.AddSeconds(dtUnix); + } + catch (Exception) { Debug.Assert(false); } + + return DateTime.UtcNow; + } + } + + public class FilledAutofillField + { + private string[] _autofillHints; + public string TextValue { get; set; } + public long? DateValue { get; set; } + public bool? ToggleValue { get; set; } + + public string ValueToString() + { + if (DateValue != null) + { + return TimeUtil.ConvertUnixTime((long)DateValue / 1000.0).ToLongDateString(); + } + if (ToggleValue != null) + return ToggleValue.ToString(); + return TextValue; + } + + /// + /// returns the autofill hints for the filled field. These are always lowercased for simpler string comparison. + /// + public string[] AutofillHints + { + get + { + return _autofillHints; + } + set + { + _autofillHints = value; + for (int i = 0; i < _autofillHints.Length; i++) + _autofillHints[i] = _autofillHints[i].ToLower(); + } + } + + + public FilledAutofillField() + { } + + public FilledAutofillField(InputField inputField) + : this(inputField, inputField.AutofillHints) + { + + } + + public FilledAutofillField(InputField inputField, string[] hints) + { + + string[] rawHints = AutofillHintsHelper.FilterForSupportedHints(hints); + List hintList = new List(); + + string nextHint = null; + for (int i = 0; i < rawHints.Length; i++) + { + string hint = rawHints[i]; + if (i < rawHints.Length - 1) + { + nextHint = rawHints[i + 1]; + } + // First convert the compound W3C autofill hints + if (W3cHints.isW3cSectionPrefix(hint) && i < rawHints.Length - 1) + { + hint = rawHints[++i]; + + if (i < rawHints.Length - 1) + { + nextHint = rawHints[i + 1]; + } + } + if (W3cHints.isW3cTypePrefix(hint) && nextHint != null && W3cHints.isW3cTypeHint(nextHint)) + { + hint = nextHint; + i++; + + } + if (W3cHints.isW3cAddressType(hint) && nextHint != null) + { + hint = nextHint; + i++; + + } + + // Then check if the "actual" hint is supported. + if (AutofillHintsHelper.IsSupportedHint(hint)) + { + hintList.Add(hint); + } + else + { + + } + } + AutofillHints = AutofillHintsHelper.ConvertToCanonicalLowerCaseHints(hintList.ToArray()).ToArray(); + inputField.FillFilledAutofillValue(this); + + + } + + public bool IsNull() + { + return TextValue == null && DateValue == null && ToggleValue == null; + } + + public override bool Equals(object obj) + { + if (this == obj) return true; + if (obj == null || GetType() != obj.GetType()) return false; + + FilledAutofillField that = (FilledAutofillField)obj; + + if (!TextValue?.Equals(that.TextValue) ?? that.TextValue != null) + return false; + if (DateValue != null ? !DateValue.Equals(that.DateValue) : that.DateValue != null) + return false; + return ToggleValue != null ? ToggleValue.Equals(that.ToggleValue) : that.ToggleValue == null; + } + + public override int GetHashCode() + { + unchecked + { + var result = TextValue != null ? TextValue.GetHashCode() : 0; + result = 31 * result + (DateValue != null ? DateValue.GetHashCode() : 0); + result = 31 * result + (ToggleValue != null ? ToggleValue.GetHashCode() : 0); + return result; + } + } + } + + /// + /// Base class for everything that is (or could be) an input field which might (or might not) be autofilled. + /// For testability, this is independent from Android classes like ViewNode + /// + public abstract class InputField + { + public string? IdEntry { get; set; } + public string? Hint { get; set; } + public string ClassName { get; set; } + public string[] AutofillHints { get; set; } + public bool IsFocused { get; set; } + + public InputTypes InputType { get; set; } + + public string HtmlInfoTag { get; set; } + public string HtmlInfoTypeAttribute { get; set; } + + public abstract void FillFilledAutofillValue(FilledAutofillField filledField); + + } + + /// + /// Serializable structure defining the contents of the current view (from an autofill perspective) + /// + /// + public class AutofillView where TField : InputField + { + public List InputFields { get; set; } = new List(); + + public string PackageId { get; set; } = null; + public string WebDomain { get; set; } = null; + } + + public interface ILogger + { + void Log(string x); + } + + public class StructureParserBase where FieldT: InputField + { + private readonly ILogger _log; + private readonly IKp2aDigitalAssetLinksDataSource _digitalAssetLinksDataSource; + + private readonly List _autofillHintsForLogin = new List + { + AutofillHintsHelper.AutofillHintPassword, + AutofillHintsHelper.AutofillHintUsername, + AutofillHintsHelper.AutofillHintEmailAddress + }; + + public string PackageId { get; set; } + + public Dictionary FieldsMappedToHints = new Dictionary(); + + public StructureParserBase(ILogger logger, IKp2aDigitalAssetLinksDataSource digitalAssetLinksDataSource) + { + _log = logger; + _digitalAssetLinksDataSource = digitalAssetLinksDataSource; + } + + public class AutofillTargetId + { + public string PackageName { get; set; } + + public string PackageNameWithPseudoSchema + { + get { return AndroidAppScheme + PackageName; } + } + + public const string AndroidAppScheme = "androidapp://"; + + public string WebDomain { get; set; } + + /// + /// If PackageName and WebDomain are not compatible (by DAL or because PackageName is a trusted browser in which case we treat all domains as "compatible" + /// we need to issue a warning. If we would fill credentials for the package, a malicious website could try to get credentials for the app. + /// If we would fill credentials for the domain, a malicious app could get credentials for the domain. + /// + public bool IncompatiblePackageAndDomain { get; set; } + + public string DomainOrPackage + { + get + { + return WebDomain ?? PackageNameWithPseudoSchema; + } + } + } + + public AutofillTargetId ParseForFill(bool isManual, AutofillView autofillView) + { + return Parse(true, isManual, autofillView); + } + + public AutofillTargetId ParseForSave(AutofillView autofillView) + { + return Parse(false, true, autofillView); + } + + /// + /// Traverse AssistStructure and add ViewNode metadata to a flat list. + /// + /// The parse. + /// If set to true for fill. + /// + protected virtual AutofillTargetId Parse(bool forFill, bool isManualRequest, AutofillView autofillView) + { + AutofillTargetId result = new AutofillTargetId() + { + PackageName = autofillView.PackageId, + WebDomain = autofillView.WebDomain + }; + + _log.Log("parsing autofillStructure..."); + + if (LogAutofillView) + { + string debugInfo = JsonConvert.SerializeObject(autofillView, Newtonsoft.Json.Formatting.Indented); + _log.Log("This is the autofillStructure: \n\n " + debugInfo); + } + + + //go through each input field and determine username/password fields. + //Depending on the target this can require more or less heuristics. + // * if there is a valid & supported autofill hint, we assume that all fields which should be filled do have an appropriate Autofill hint + // * if there is no such autofill hint, we use IsPassword to + + HashSet autofillHintsOfAllFields = autofillView.InputFields.Where(f => f.AutofillHints != null) + .SelectMany(f => f.AutofillHints).Where(x => x != null).Select(AutofillHintsHelper.ToCanonicalHint).ToHashSet(); + bool hasLoginAutofillHints = autofillHintsOfAllFields.Intersect(_autofillHintsForLogin).Any(); + + if (hasLoginAutofillHints) + { + foreach (var viewNode in autofillView.InputFields) + { + string[] viewHints = viewNode.AutofillHints; + if (viewHints == null) + continue; + if (viewHints.Where(h => h != null).Select(AutofillHintsHelper.ToCanonicalHint).Intersect(_autofillHintsForLogin).Any()) + { + AddFieldToHintMap(viewNode, viewHints.Where(h => h != null).Select(AutofillHintsHelper.ToCanonicalHint).ToHashSet().ToArray()); + } + + } + } + else + { + //determine password fields, first by type, then by hint: + List editTexts = autofillView.InputFields.Where(f => IsEditText(f)).ToList(); + List passwordFields = autofillView.InputFields.Where(f => IsEditText(f) && IsPassword(f)).ToList(); + if (!passwordFields.Any()) + { + passwordFields = autofillView.InputFields.Where(f => IsEditText(f) && HasPasswordHint(f)).ToList(); + } + + //determine username fields. Try by hint, if that fails use the one before the password + List usernameFields = autofillView.InputFields.Where(f => IsEditText(f) && HasUsernameHint(f)).ToList(); + if (!usernameFields.Any()) + { + foreach (var passwordField in passwordFields) + { + + var lastInputBeforePassword = autofillView.InputFields.Where(IsEditText) + .TakeWhile(f => f != passwordField && !passwordFields.Contains(f)).LastOrDefault(); + + if (lastInputBeforePassword != null) + usernameFields.Add(lastInputBeforePassword); + } + + } + + //for "heuristic determination" we demand that one of the filled fields is focused: + if (passwordFields.Concat(usernameFields).Any(f => f.IsFocused)) + { + foreach (var uf in usernameFields) + AddFieldToHintMap(uf, new string[] { AutofillHintsHelper.AutofillHintUsername }); + foreach (var pf in passwordFields.Except(usernameFields)) + AddFieldToHintMap(pf, new string[] { AutofillHintsHelper.AutofillHintPassword }); + } + } + + + if (!string.IsNullOrEmpty(autofillView.WebDomain) && _digitalAssetLinksDataSource.IsEnabled()) + { + result.IncompatiblePackageAndDomain = !_digitalAssetLinksDataSource.IsTrustedLink(autofillView.WebDomain, result.PackageName); + if (result.IncompatiblePackageAndDomain) + { + _log.Log($"DAL verification failed for {result.PackageName}/{result.WebDomain}"); + } + } + else + { + result.IncompatiblePackageAndDomain = false; + } + return result; + } + + private void AddFieldToHintMap(FieldT field, string[] hints) + { + if (FieldsMappedToHints.ContainsKey(field)) + { + FieldsMappedToHints[field] = FieldsMappedToHints[field].Concat(hints).ToArray(); + } + else + { + FieldsMappedToHints[field] = hints; + } + } + + public bool LogAutofillView { get; set; } + + private bool IsEditText(FieldT f) + { + return (f.ClassName == "android.widget.EditText" + || f.ClassName == "android.widget.AutoCompleteTextView" + || f.HtmlInfoTag == "input"); + } + + private static readonly HashSet _passwordHints = new HashSet { "password", "passwort", "passwordAuto", "pswd" }; + private static bool HasPasswordHint(InputField f) + { + return IsAny(f.IdEntry, _passwordHints) || + IsAny(f.Hint, _passwordHints); + } + + private static readonly HashSet _usernameHints = new HashSet { "email", "e-mail", "username", "user id" }; + + private static bool HasUsernameHint(InputField f) + { + return IsAny(f.IdEntry?.ToLower(), _usernameHints) || + IsAny(f.Hint?.ToLower(), _usernameHints); + } + + private static bool IsAny(string? value, IEnumerable terms) + { + if (string.IsNullOrWhiteSpace(value)) + { + return false; + } + var lowerValue = value.ToLowerInvariant(); + return terms.Any(t => lowerValue == t); + } + + private static bool IsInputTypeClass(InputTypes inputType, InputTypes inputTypeClass) + { + if (!InputTypes.MaskClass.HasFlag(inputTypeClass)) + throw new Exception("invalid inputTypeClass"); + return (((int)inputType) & (int)InputTypes.MaskClass) == (int)(inputTypeClass); + } + private static bool IsInputTypeVariation(InputTypes inputType, InputTypes inputTypeVariation) + { + if (!InputTypes.MaskVariation.HasFlag(inputTypeVariation)) + throw new Exception("invalid inputTypeVariation"); + return (((int)inputType) & (int)InputTypes.MaskVariation) == (int)(inputTypeVariation); + } + + private static bool IsPassword(InputField f) + { + InputTypes inputType = f.InputType; + + return + (!f.IdEntry?.ToLowerInvariant().Contains("search") ?? true) && + (!f.Hint?.ToLowerInvariant().Contains("search") ?? true) && + ( + (IsInputTypeClass(inputType, InputTypes.ClassText) + && + ( + IsInputTypeVariation(inputType, InputTypes.TextVariationPassword) + || IsInputTypeVariation(inputType, InputTypes.TextVariationVisiblePassword) + || IsInputTypeVariation(inputType, InputTypes.TextVariationWebPassword) + ) + ) + || (f.AutofillHints != null && f.AutofillHints.FirstOrDefault() == "passwordAuto") + || (f.HtmlInfoTypeAttribute == "password") + ); + } + + + + + + + } +} diff --git a/src/Kp2aAutofillParserSdkStyle/Kp2aAutofillParserSdkStyle.csproj b/src/Kp2aAutofillParserSdkStyle/Kp2aAutofillParserSdkStyle.csproj new file mode 100644 index 00000000..7afa4f89 --- /dev/null +++ b/src/Kp2aAutofillParserSdkStyle/Kp2aAutofillParserSdkStyle.csproj @@ -0,0 +1,11 @@ + + + net8.0-android + 21 + enable + enable + + + + + \ No newline at end of file diff --git a/src/Kp2aAutofillParserTest/citibank.json b/src/Kp2aAutofillParserTest/citibank.json new file mode 100644 index 00000000..7bfc2db4 --- /dev/null +++ b/src/Kp2aAutofillParserTest/citibank.json @@ -0,0 +1,1110 @@ +{ + "InputFields": [ + { + "IdEntry": null, + "Hint": null, + "ClassName": "android.widget.FrameLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "action_bar_root", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "action_mode_bar_stub", + "Hint": null, + "ClassName": "android.view.View", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "container_fl", + "Hint": null, + "ClassName": "android.widget.FrameLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "ll", + "Hint": null, + "ClassName": "android.widget.FrameLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": null, + "Hint": null, + "ClassName": "android.webkit.WebView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "rootLayout", + "Hint": null, + "ClassName": "android.widget.RelativeLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "odyssey_bg_layout", + "Hint": null, + "ClassName": "android.widget.FrameLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "scrollViewOuter", + "Hint": null, + "ClassName": "android.widget.ScrollView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "outerLayout", + "Hint": null, + "ClassName": "android.view.ViewGroup", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "bgGradientOverlay", + "Hint": null, + "ClassName": "android.view.View", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "loadingLayout", + "Hint": null, + "ClassName": "android.widget.RelativeLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "network_notify", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "scrollViewInner", + "Hint": null, + "ClassName": "android.widget.ScrollView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "innerLayout", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "ody_login_linearlayout", + "Hint": null, + "ClassName": "android.widget.FrameLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "odyssey_constraint_layout", + "Hint": null, + "ClassName": "android.view.ViewGroup", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "odyFooterButtons", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "lnrCULogin", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "odyssey_multiple_container", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "openABankAccount_link", + "Hint": null, + "ClassName": "android.widget.Button", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "login_button", + "Hint": null, + "ClassName": "android.widget.Button", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "register_link", + "Hint": null, + "ClassName": "android.widget.Button", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "tellus", + "Hint": null, + "ClassName": "android.widget.FrameLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "tellus_rel", + "Hint": null, + "ClassName": "android.widget.RelativeLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "tellusBtn", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "tell_us_LL", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "tell_us_text", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "tellusSingleLine", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "footerLayout", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "footerLayoutTop", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "footerButtons", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "footerSeparator", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "footerLabels", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "cdic_logo", + "Hint": null, + "ClassName": "android.widget.RelativeLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "language_link", + "Hint": null, + "ClassName": "android.widget.RelativeLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "txtLanguageLink", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "copyright_message", + "Hint": null, + "ClassName": "android.widget.RelativeLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "txtCopyrightMessage", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "support_link", + "Hint": null, + "ClassName": "android.widget.RelativeLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "txtSupportLink", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "footerLayoutBottom", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "txtDisclaim", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "authlogin_button", + "Hint": null, + "ClassName": "android.widget.RelativeLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "parentRL", + "Hint": null, + "ClassName": "android.widget.RelativeLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "component_progress_button_text_view", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "component_progress_button_loading", + "Hint": null, + "ClassName": "android.widget.ProgressBar", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "default_logo_greet_layout", + "Hint": null, + "ClassName": "android.view.ViewGroup", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "tvGreetText", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "tvReBranding", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "tell_us_LL", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "headerView", + "Hint": null, + "ClassName": "android.widget.FrameLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "seperator_view", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "headerView", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "headerLeftActionIconLL", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "titleViewRo", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "tablelyt", + "Hint": null, + "ClassName": "android.widget.TableLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "titleView", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "headerTitleTextView", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "headerSubTitleTextView", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "headerRightActionIconLL", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "separator", + "Hint": null, + "ClassName": "android.view.View", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "announcement_notify", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "notificationParentLL", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "notifyTitle", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "notifySubTitle", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "topErrorLayout", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "introductionLayout", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "mfa_container_fl", + "Hint": null, + "ClassName": "android.widget.FrameLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "notify", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "loader", + "Hint": null, + "ClassName": "android.widget.RelativeLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "loading", + "Hint": null, + "ClassName": "android.widget.ProgressBar", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "status_txt", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": null, + "Hint": null, + "ClassName": "android.widget.FrameLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "popuplayout", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": null, + "Hint": null, + "ClassName": "android.widget.FrameLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "action_bar_root", + "Hint": null, + "ClassName": "android.widget.FrameLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "container", + "Hint": null, + "ClassName": "android.widget.FrameLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "coordinator", + "Hint": null, + "ClassName": "android.view.ViewGroup", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "touch_outside", + "Hint": null, + "ClassName": "android.view.View", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "design_bottom_sheet", + "Hint": null, + "ClassName": "android.widget.FrameLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "root", + "Hint": null, + "ClassName": "android.widget.FrameLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "loadingLayout", + "Hint": null, + "ClassName": "android.widget.RelativeLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "container", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "titleTextView", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "userIDTv", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "txtUserID", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "parentRL", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "frmRoot", + "Hint": null, + "ClassName": "android.widget.FrameLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "odyTextLayout", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "odyEditText", + "Hint": null, + "ClassName": "android.widget.EditText", + "AutofillHints": [ + "passwordAuto" + ], + "IsFocused": true, + "InputType": 524433, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "rightActionText", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "lblErrorMsg", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "txtUserError", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "lblUsername", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "middleLayout", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "txtPassword", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "parentRL", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "frmRoot", + "Hint": null, + "ClassName": "android.widget.FrameLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "odyTextLayout", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "odyEditText", + "Hint": null, + "ClassName": "android.widget.EditText", + "AutofillHints": [ + "passwordAuto" + ], + "IsFocused": false, + "InputType": 129, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "rightActionText", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "lblErrorMsg", + "Hint": null, + "ClassName": "android.widget.TextView", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "odyssey_login_text_input_sipedittxt", + "Hint": null, + "ClassName": "android.view.View", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "llErrorMsg", + "Hint": null, + "ClassName": "android.widget.LinearLayout", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "odyssey_login_button", + "Hint": null, + "ClassName": "android.widget.Button", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "odyssey_send_sms", + "Hint": null, + "ClassName": "android.widget.Button", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "odyssey_use_finger_button", + "Hint": null, + "ClassName": "android.widget.Button", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "odyssey_reset_button", + "Hint": null, + "ClassName": "android.widget.Button", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + }, + { + "IdEntry": "action_mode_bar_stub", + "Hint": null, + "ClassName": "android.view.View", + "AutofillHints": null, + "IsFocused": false, + "InputType": 0, + "HtmlInfoTag": null, + "HtmlInfoTypeAttribute": null + } + ], + "PackageId": "com.konylabs.cbplpat", + "WebDomain": null +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/DataExchange/FileFormatProvider.cs b/src/Kp2aBusinessLogicSdkStyle/DataExchange/FileFormatProvider.cs new file mode 100644 index 00000000..a5a37183 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/DataExchange/FileFormatProvider.cs @@ -0,0 +1,119 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2013 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Drawing; + +using KeePassLib; +using KeePassLib.Interfaces; + +namespace KeePass.DataExchange +{ + public abstract class FileFormatProvider + { + public abstract bool SupportsImport { get; } + public abstract bool SupportsExport { get; } + + public abstract string FormatName { get; } + + public virtual string DisplayName + { + get { return this.FormatName; } + } + + /// + /// Default file name extension, without leading dot. + /// If there are multiple default/equivalent extensions + /// (like e.g. "html" and "htm"), specify all of them + /// separated by a '|' (e.g. "html|htm"). + /// + public virtual string DefaultExtension + { + get { return string.Empty; } + } + + + public virtual bool RequiresFile + { + get { return true; } + } + + public virtual bool SupportsUuids + { + get { return false; } + } + + public virtual bool RequiresKey + { + get { return false; } + } + + /// + /// This property specifies if entries are only appended to the + /// end of the root group. This is true for example if the + /// file format doesn't support groups (i.e. no hierarchy). + /// + public virtual bool ImportAppendsToRootGroupOnly + { + get { return false; } + } + + + + /// + /// Called before the Export method is invoked. + /// + /// Returns true, if the Export method + /// can be invoked. If it returns false, something has + /// failed and the export process should be aborted. + public virtual bool TryBeginExport() + { + return true; + } + + /// + /// Import a stream into a database. Throws an exception if an error + /// occurs. Do not call the base class method when overriding it. + /// + /// Data storage into which the data will be imported. + /// Input stream to read the data from. + /// Status logger. May be null. + public abstract void Import(PwDatabase pwStorage, Stream sInput, + IStatusLogger slLogger); + /// + /// Export data into a stream. Throws an exception if an error + /// occurs (like writing to stream fails, etc.). Returns true, + /// if the export was successful. + /// + /// Contains the data source and detailed + /// information about which entries should be exported. + /// Output stream to write the data to. + /// Status logger. May be null. + /// Returns false, if the user has aborted the export + /// process (like clicking Cancel in an additional export settings + /// dialog). + public virtual bool Export(PwExportInfo pwExportInfo, Stream sOutput, + IStatusLogger slLogger) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/DataExchange/Formats/KeePassCsv1x.cs b/src/Kp2aBusinessLogicSdkStyle/DataExchange/Formats/KeePassCsv1x.cs new file mode 100644 index 00000000..3a6af8a7 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/DataExchange/Formats/KeePassCsv1x.cs @@ -0,0 +1,183 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2013 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System.Diagnostics; +using System.IO; + +using KeePassLib; +using KeePassLib.Delegates; +using KeePassLib.Interfaces; +using KeePassLib.Utility; + +namespace KeePass.DataExchange.Formats +{ + public sealed class KeePassCsv1x : FileFormatProvider + { + public override bool SupportsImport { get { return false; } } + public override bool SupportsExport { get { return true; } } + + public override string FormatName { get { return "KeePass CSV (1.x)"; } } + public override string DefaultExtension { get { return "csv"; } } + + // public override bool ImportAppendsToRootGroupOnly { get { return true; } } + + /* public override void Import(PwDatabase pwStorage, Stream sInput, + IStatusLogger slLogger) + { + StreamReader sr = new StreamReader(sInput, Encoding.UTF8); + string strFileContents = sr.ReadToEnd(); + sr.Close(); + + CharStream csSource = new CharStream(strFileContents); + + while(true) + { + if(ReadEntry(pwStorage, csSource) == false) + break; + } + } + + private static bool ReadEntry(PwDatabase pwStorage, CharStream csSource) + { + PwEntry pe = new PwEntry(true, true); + + string strTitle = ReadCsvField(csSource); + if(strTitle == null) return false; // No entry available + + string strUser = ReadCsvField(csSource); + if(strUser == null) throw new InvalidDataException(); + + string strPassword = ReadCsvField(csSource); + if(strPassword == null) throw new InvalidDataException(); + + string strUrl = ReadCsvField(csSource); + if(strUrl == null) throw new InvalidDataException(); + + string strNotes = ReadCsvField(csSource); + if(strNotes == null) throw new InvalidDataException(); + + if((strTitle == "Account") && (strUser == "Login Name") && + (strPassword == "Password") && (strUrl == "Web Site") && + (strNotes == "Comments")) + { + return true; // Ignore header entry + } + + pe.Strings.Set(PwDefs.TitleField, new ProtectedString( + pwStorage.MemoryProtection.ProtectTitle, strTitle)); + pe.Strings.Set(PwDefs.UserNameField, new ProtectedString( + pwStorage.MemoryProtection.ProtectUserName, strUser)); + pe.Strings.Set(PwDefs.PasswordField, new ProtectedString( + pwStorage.MemoryProtection.ProtectPassword, strPassword)); + pe.Strings.Set(PwDefs.UrlField, new ProtectedString( + pwStorage.MemoryProtection.ProtectUrl, strUrl)); + pe.Strings.Set(PwDefs.NotesField, new ProtectedString( + pwStorage.MemoryProtection.ProtectNotes, strNotes)); + + pwStorage.RootGroup.AddEntry(pe, true); + return true; + } + + private static string ReadCsvField(CharStream csSource) + { + StringBuilder sb = new StringBuilder(); + bool bInField = false; + + while(true) + { + char ch = csSource.ReadChar(); + if(ch == char.MinValue) + return null; + + if((ch == '\"') && !bInField) + bInField = true; + else if((ch == '\"') && bInField) + break; + else if(ch == '\\') + { + char chSub = csSource.ReadChar(); + if(chSub == char.MinValue) + throw new InvalidDataException(); + + sb.Append(chSub); + } + else if(bInField) + sb.Append(ch); + } + + return sb.ToString(); + } */ + + public override void Import(PwDatabase pwStorage, Stream sInput, IStatusLogger slLogger) + { + throw new System.NotImplementedException(); + } + + public override bool Export(PwExportInfo pwExportInfo, Stream sOutput, + IStatusLogger slLogger) + { + PwGroup pg = (pwExportInfo.DataGroup ?? ((pwExportInfo.ContextDatabase != + null) ? pwExportInfo.ContextDatabase.RootGroup : null)); + + StreamWriter sw = new StreamWriter(sOutput, StrUtil.Utf8); + sw.Write("\"Account\",\"Login Name\",\"Password\",\"Web Site\",\"Comments\"\r\n"); + + EntryHandler eh = delegate(PwEntry pe) + { + WriteCsvEntry(sw, pe); + return true; + }; + + if(pg != null) pg.TraverseTree(TraversalMethod.PreOrder, null, eh); + + sw.Close(); + return true; + } + + private static void WriteCsvEntry(StreamWriter sw, PwEntry pe) + { + if(sw == null) { Debug.Assert(false); return; } + if(pe == null) { Debug.Assert(false); return; } + + const string strSep = "\",\""; + + sw.Write("\""); + WriteCsvString(sw, pe.Strings.ReadSafe(PwDefs.TitleField), strSep); + WriteCsvString(sw, pe.Strings.ReadSafe(PwDefs.UserNameField), strSep); + WriteCsvString(sw, pe.Strings.ReadSafe(PwDefs.PasswordField), strSep); + WriteCsvString(sw, pe.Strings.ReadSafe(PwDefs.UrlField), strSep); + WriteCsvString(sw, pe.Strings.ReadSafe(PwDefs.NotesField), "\"\r\n"); + } + + private static void WriteCsvString(StreamWriter sw, string strText, + string strAppend) + { + string str = strText; + if(!string.IsNullOrEmpty(str)) + { + str = str.Replace("\\", "\\\\"); + str = str.Replace("\"", "\\\""); + + sw.Write(str); + } + + if(!string.IsNullOrEmpty(strAppend)) sw.Write(strAppend); + } + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/DataExchange/Formats/KeePassKdb2x.cs b/src/Kp2aBusinessLogicSdkStyle/DataExchange/Formats/KeePassKdb2x.cs new file mode 100644 index 00000000..3412f2e7 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/DataExchange/Formats/KeePassKdb2x.cs @@ -0,0 +1,60 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2013 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Drawing; +using System.IO; + + +using KeePassLib; +using KeePassLib.Interfaces; +using KeePassLib.Serialization; + +namespace KeePass.DataExchange.Formats +{ + public sealed class KeePassKdb2x : FileFormatProvider + { + public override bool SupportsImport { get { return true; } } + public override bool SupportsExport { get { return true; } } + + public override string FormatName { get { return "KeePass KDBX (2.x)"; } } + public override string DefaultExtension { get { return "kdbx"; } } + + public override bool SupportsUuids { get { return true; } } + public override bool RequiresKey { get { return true; } } + + + public override void Import(PwDatabase pwStorage, Stream sInput, + IStatusLogger slLogger) + { + KdbxFile kdbx = new KdbxFile(pwStorage); + kdbx.Load(sInput, KdbxFormat.Default, slLogger); + } + + public override bool Export(PwExportInfo pwExportInfo, Stream sOutput, + IStatusLogger slLogger) + { + KdbxFile kdbx = new KdbxFile(pwExportInfo.ContextDatabase); + kdbx.Save(sOutput, pwExportInfo.DataGroup, KdbxFormat.Default, slLogger); + return true; + } + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/DataExchange/Formats/KeePassXml2x.cs b/src/Kp2aBusinessLogicSdkStyle/DataExchange/Formats/KeePassXml2x.cs new file mode 100644 index 00000000..42dce2b8 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/DataExchange/Formats/KeePassXml2x.cs @@ -0,0 +1,72 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2013 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Drawing; +using System.IO; + + +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Interfaces; +using KeePassLib.Serialization; + +namespace KeePass.DataExchange.Formats +{ + public sealed class KeePassXml2x : FileFormatProvider + { + public override bool SupportsImport { get { return true; } } + public override bool SupportsExport { get { return true; } } + + public override string FormatName { get { return "KeePass XML (2.x)"; } } + public override string DefaultExtension { get { return "xml"; } } + + public override bool SupportsUuids { get { return true; } } + + public override void Import(PwDatabase pwStorage, Stream sInput, + IStatusLogger slLogger) + { + KdbxFile kdbx = new KdbxFile(pwStorage); + kdbx.Load(sInput, KdbxFormat.PlainXml, slLogger); + } + + public override bool Export(PwExportInfo pwExportInfo, Stream sOutput, + IStatusLogger slLogger) + { + PwDatabase pd = (pwExportInfo.ContextDatabase ?? new PwDatabase()); + + PwObjectList vDel = null; + if(pwExportInfo.ExportDeletedObjects == false) + { + vDel = pd.DeletedObjects.CloneShallow(); + pd.DeletedObjects.Clear(); + } + + KdbxFile kdb = new KdbxFile(pd); + kdb.Save(sOutput, pwExportInfo.DataGroup, KdbxFormat.PlainXml, slLogger); + + // Restore deleted objects list + if(vDel != null) pd.DeletedObjects.Add(vDel); + + return true; + } + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/DataExchange/PwExportInfo.cs b/src/Kp2aBusinessLogicSdkStyle/DataExchange/PwExportInfo.cs new file mode 100644 index 00000000..2c39f235 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/DataExchange/PwExportInfo.cs @@ -0,0 +1,82 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2013 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +using KeePassLib; + +namespace KeePass.DataExchange +{ + public sealed class PwExportInfo + { + private PwGroup m_pg; + /// + /// This group contains all entries and subgroups that should + /// be exported. Is never null. + /// + public PwGroup DataGroup + { + get { return m_pg; } + } + + private PwDatabase m_pd; + /// + /// Optional context database reference. May be null. + /// + public PwDatabase ContextDatabase + { + get { return m_pd; } + } + + private bool m_bExpDel = true; + /// + /// Indicates whether deleted objects should be exported, if + /// the data format supports it. + /// + public bool ExportDeletedObjects + { + get { return m_bExpDel; } + } + + public PwExportInfo(PwGroup pgDataSource, PwDatabase pwContextInfo) + { + ConstructEx(pgDataSource, pwContextInfo, null); + } + + public PwExportInfo(PwGroup pgDataSource, PwDatabase pwContextInfo, + bool bExportDeleted) + { + ConstructEx(pgDataSource, pwContextInfo, bExportDeleted); + } + + private void ConstructEx(PwGroup pgDataSource, PwDatabase pwContextInfo, + bool? bExportDeleted) + { + if(pgDataSource == null) throw new ArgumentNullException("pgDataSource"); + // pwContextInfo may be null. + + m_pg = pgDataSource; + m_pd = pwContextInfo; + + if(bExportDeleted.HasValue) m_bExpDel = bExportDeleted.Value; + } + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/ElementAndDatabaseId.cs b/src/Kp2aBusinessLogicSdkStyle/ElementAndDatabaseId.cs new file mode 100644 index 00000000..3a833856 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/ElementAndDatabaseId.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; +using keepass2android.Io; +using KeePassLib; +using KeePassLib.Interfaces; +using KeePassLib.Utility; + +namespace keepass2android +{ + public class ElementAndDatabaseId + { + private const char Separator = '+'; + + public ElementAndDatabaseId(Database db, IStructureItem element) + { + DatabaseId = db.IocAsHexString(); + ElementIdString = element.Uuid.ToHexString(); + } + + public ElementAndDatabaseId(string fullId) + { + string[] parts = fullId.Split(Separator); + if (parts.Length != 2) + throw new Exception("Invalid full id " + fullId); + DatabaseId = parts[0]; + ElementIdString = parts[1]; + } + + public string DatabaseId { get; set; } + public string ElementIdString { get; set; } + public PwUuid ElementId { get { return new PwUuid(MemUtil.HexStringToByteArray(ElementIdString));} } + + public string FullId + { + get { return DatabaseId + Separator + ElementIdString; } + } + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/IDrawableFactory.cs b/src/Kp2aBusinessLogicSdkStyle/IDrawableFactory.cs new file mode 100644 index 00000000..90238ecc --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/IDrawableFactory.cs @@ -0,0 +1,19 @@ +using Android.Content; +using Android.Widget; +using Android.Content.Res; +using KeePassLib; +using Android.Graphics.Drawables; + +namespace keepass2android +{ + public interface IDrawableFactory + { + void AssignDrawableTo(ImageView iv, Context context, PwDatabase db, PwIcon icon, PwUuid customIconId, bool forGroup); + + Drawable GetIconDrawable(Context context, PwDatabase db, PwIcon icon, PwUuid customIconId, bool forGroup); + + bool IsWhiteIconSet { get; } + + void Clear(); + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/IKp2aApp.cs b/src/Kp2aBusinessLogicSdkStyle/IKp2aApp.cs new file mode 100644 index 00000000..d4602a17 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/IKp2aApp.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using Android.App; +using System.IO; +using Android.Content; +using Android.OS; +using KeePassLib; +using KeePassLib.Keys; +using KeePassLib.Serialization; +using keepass2android.Io; +using KeePassLib.Interfaces; +#if !NoNet && !EXCLUDE_JAVAFILESTORAGE +using Keepass2android.Javafilestorage; +#endif + +namespace keepass2android +{ + public interface ICertificateValidationHandler + { + /// + /// Handles a failed certificate validation. Returns true if the users wants to continue, false otherwise. + /// see http://msdn.microsoft.com/en-us/library/system.net.icertificatepolicy(v=vs.110).aspx + /// + //bool OnServerCertificateError(int certificateProblem); + + RemoteCertificateValidationCallback CertificateValidationCallback { get; } + + } + + /// + /// Interface through which Activities and the logic layer can access some app specific functionalities and Application static data + /// + /// This also contains methods which are UI specific and should be replacable for testing. + public interface IKp2aApp : ICertificateValidationHandler + { + /// + /// Locks all currently open databases, quicklocking if available (unless false is passed for allowQuickUnlock) + /// + void Lock(bool allowQuickUnlock, bool lockWasTriggeredByTimeout); + + + /// + /// Loads the specified data as the currently open database, as unlocked. + /// + Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey, ProgressDialogStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent); + + + HashSet DirtyGroups { get; } + + void MarkAllGroupsAsDirty(); + + /// + /// Returns the current database + /// + Database CurrentDb { get; } + + IEnumerable OpenDatabases { get; } + void CloseDatabase(Database db); + + Database FindDatabaseForElement(IStructureItem element); + + /// + /// Tell the app that the file from ioc was opened with keyfile. + /// + void StoreOpenedFileAsRecent(IOConnectionInfo ioc, string keyfile, bool updateTimestamp, string displayName = ""); + + /// + /// Creates a new database and returns it + /// + Database CreateNewDatabase(bool makeCurrent); + + /// + /// Returns the user-displayable string identified by stringKey + /// + string GetResourceString(UiStringKey stringKey); + + /// + /// Returns the value from the preferences corresponding to key + /// + bool GetBooleanPreference(PreferenceKey key); + + /// + /// Asks the user the question "messageKey" with the options Yes/No/Cancel, calls the handler corresponding to the answer. + /// + void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey, + EventHandler yesHandler, + EventHandler noHandler, + EventHandler cancelHandler, + Context ctx, + string messageSuffix = ""); + + /// + /// Asks the user the question "messageKey" with the options Yes/No/Cancel, but the yes/no strings can be selected freely, calls the handler corresponding to the answer. + /// + void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey, + UiStringKey yesString, UiStringKey noString, + EventHandler yesHandler, + EventHandler noHandler, + EventHandler cancelHandler, + Context ctx, + string messageSuffix = ""); + + /// + /// Returns a Handler object which can run tasks on the UI thread + /// + Handler UiThreadHandler { get; } + + IProgressDialog CreateProgressDialog(Context ctx); + + /// + /// returns the file storage for the given ioc. might be a caching file storage + /// + IFileStorage GetFileStorage(IOConnectionInfo iocInfo); + + /// + /// returns the file storage for the given ioc. if allowCache=false, no cached file storage is returned + /// + IFileStorage GetFileStorage(IOConnectionInfo iocInfo, bool allowCache); + + void TriggerReload(Context context, Action actionOnResult /*if not null, called when the user selected yes (true) or no (false)*/); + + + bool CheckForDuplicateUuids { get; } +#if !NoNet && !EXCLUDE_JAVAFILESTORAGE + ICertificateErrorHandler CertificateErrorHandler { get; } + + + +#endif + + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/IProgressDialog.cs b/src/Kp2aBusinessLogicSdkStyle/IProgressDialog.cs new file mode 100644 index 00000000..1035b195 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/IProgressDialog.cs @@ -0,0 +1,10 @@ +namespace keepass2android +{ + public interface IProgressDialog + { + void SetTitle(string title); + void SetMessage(string resourceString); + void Dismiss(); + void Show(); + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/AndroidContentStorage.cs b/src/Kp2aBusinessLogicSdkStyle/Io/AndroidContentStorage.cs new file mode 100644 index 00000000..5eee766d --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/AndroidContentStorage.cs @@ -0,0 +1,335 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using Android; +using Android.Content; +using Android.Database; +using Android.OS; +using Android.Provider; +using Java.IO; +using KeePassLib.Serialization; +using KeePassLib.Utility; +using Console = System.Console; + +namespace keepass2android.Io +{ + /** FileStorage to work with content URIs + * Supports both "old" system where data is available only temporarily as + * well as the SAF system. Assumes that persistable permissions are "taken" by + * the activity which receives OnActivityResult from the system file picker.*/ + public class AndroidContentStorage: IFileStorage + { + private readonly Context _ctx; + + public AndroidContentStorage(Context ctx) + { + _ctx = ctx; + } + + public IEnumerable SupportedProtocols + { + get { yield return "content"; } + } + + public bool UserShouldBackup + { + get { return true; } + } + + public void Delete(IOConnectionInfo ioc) + { + throw new NotImplementedException(); + } + + public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion) + { + return false; + } + + public string GetCurrentFileVersionFast(IOConnectionInfo ioc) + { + return null; + } + + public Stream OpenFileForRead(IOConnectionInfo ioc) + { + try + { + return _ctx.ContentResolver.OpenInputStream(Android.Net.Uri.Parse(ioc.Path)); + } + catch (Exception e) + { + if (e.Message.Contains("requires that you obtain access using ACTION_OPEN_DOCUMENT")) + { + //looks like permission was revoked. + throw new DocumentAccessRevokedException(); + } + throw; + } + + } + + public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) + { + return new AndroidContentWriteTransaction(ioc.Path, _ctx); + } + + public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc) + { + return UrlUtil.StripExtension( + UrlUtil.GetFileName(ioc.Path)); + } + + public string GetFileExtension(IOConnectionInfo ioc) + { + return UrlUtil.GetExtension(ioc.Path); + } + + public bool RequiresCredentials(IOConnectionInfo ioc) + { + return false; + } + + public void CreateDirectory(IOConnectionInfo ioc, string newDirName) + { + throw new NotImplementedException(); + } + + public IEnumerable ListContents(IOConnectionInfo ioc) + { + throw new NotImplementedException(); + } + + public FileDescription GetFileDescription(IOConnectionInfo ioc) + { + throw new NotImplementedException(); + } + + public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc) + { + + } + + public bool RequiresSetup(IOConnectionInfo ioConnection) + { + return false; + } + + public string IocToPath(IOConnectionInfo ioc) + { + return ioc.Path; + } + + public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId) + { + Intent intent = new Intent(); + activity.IocToIntent(intent, new IOConnectionInfo { Path = protocolId + "://" }); + activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileChooserPrepared, intent); + } + + public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode, + bool alwaysReturnSuccess) + { + Intent intent = new Intent(); + activity.IocToIntent(intent, ioc); + activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileUsagePrepared, intent); + } + + public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState) + { + throw new NotImplementedException(); + } + + public void OnResume(IFileStorageSetupActivity activity) + { + throw new NotImplementedException(); + } + + public void OnStart(IFileStorageSetupActivity activity) + { + throw new NotImplementedException(); + } + + public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data) + { + throw new NotImplementedException(); + } + + public string GetDisplayName(IOConnectionInfo ioc) + { + string displayName = null; + if (TryGetDisplayName(ioc, ref displayName)) + return "content://" + displayName; //make sure we return the protocol in the display name for consistency, also expected e.g. by CreateDatabaseActivity + return ioc.Path; + } + + private bool TryGetDisplayName(IOConnectionInfo ioc, ref string displayName) + { + var uri = Android.Net.Uri.Parse(ioc.Path); + try + { + var cursor = _ctx.ContentResolver.Query(uri, null, null, null, null, null); + try + { + + if (cursor != null && cursor.MoveToFirst()) + { + displayName = cursor.GetString(cursor.GetColumnIndex(OpenableColumns.DisplayName)); + if (!string.IsNullOrEmpty(displayName)) + { + return true; + } + + } + } + finally + { + if (cursor != null) + cursor.Close(); + } + + return false; + } + catch (Exception e) + { + Kp2aLog.Log(e.ToString()); + + return false; + } + + + + } + + public string CreateFilePath(string parent, string newFilename) + { + if (!parent.EndsWith("/")) + parent += "/"; + return parent + newFilename; + } + + public IOConnectionInfo GetParentPath(IOConnectionInfo ioc) + { + return IoUtil.GetParentPath(ioc); + } + + public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename) + { + throw new NotImplementedException(); + } + + private static bool IsKitKatOrLater + { + get { return (int)Build.VERSION.SdkInt >= 19; } + } + + public bool IsPermanentLocation(IOConnectionInfo ioc) + { + //on pre-Kitkat devices, content:// is always temporary: + if (!IsKitKatOrLater) + return false; + + //try to get a persisted permission for the file + return _ctx.ContentResolver.PersistedUriPermissions.Any(p => p.Uri.ToString().Equals(ioc.Path)); + } + + + public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null) + { + ICursor cursor = null; + try + { + //on pre-Kitkat devices, we can't write content:// files + if (!IsKitKatOrLater) + { + Kp2aLog.Log("File is read-only because we're not on KitKat or later."); + if (reason != null) + reason.Result = UiStringKey.ReadOnlyReason_PreKitKat; + return true; + } + + //in previous implementations, we were checking for FLAG_SUPPORTS_WRITE in the document flags, + //but it seems like this is very poorly supported, e.g. Dropbox and OneDrive return !FLAG_SUPPORTS_WRITE + //even though writing work. + return false; + } + catch (Exception e) + { + Kp2aLog.LogUnexpectedError(e); + //better return false here. We don't really know what happened (as this is unexpected). + //let the user try to write the file. If it fails they will get an exception string. + return false; + } + finally + { + if (cursor != null) + cursor.Close(); + } + + } + + } + + public class DocumentAccessRevokedException : Exception + { + public DocumentAccessRevokedException() + { + } + + public DocumentAccessRevokedException(string message) : base(message) + { + } + + public DocumentAccessRevokedException(string message, Exception innerException) : base(message, innerException) + { + } + + protected DocumentAccessRevokedException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } + + class AndroidContentWriteTransaction : IWriteTransaction + { + private readonly string _path; + private readonly Context _ctx; + private MemoryStream _memoryStream; + + public AndroidContentWriteTransaction(string path, Context ctx) + { + _path = path; + _ctx = ctx; + } + + public void Dispose() + { + _memoryStream.Dispose(); + } + + public Stream OpenFile() + { + _memoryStream = new MemoryStream(); + return _memoryStream; + } + + public void CommitWrite() + { + ParcelFileDescriptor fileDescriptor = _ctx.ContentResolver.OpenFileDescriptor(Android.Net.Uri.Parse(_path), "rwt"); + + using (var outputStream = new FileOutputStream(fileDescriptor.FileDescriptor)) + { + byte[] data = _memoryStream.ToArray(); + + outputStream.Write(data); + outputStream.Close(); + } + fileDescriptor.Close(); + + + } + } + + +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/BuiltInFileStorage.cs b/src/Kp2aBusinessLogicSdkStyle/Io/BuiltInFileStorage.cs new file mode 100644 index 00000000..f47b777e --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/BuiltInFileStorage.cs @@ -0,0 +1,512 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; + +using System.Security; +using Android; +using Android.App; +using Android.Content; +using Android.Content.PM; +using Android.OS; +using Android.Preferences; +using Java.IO; + +using KeePassLib.Serialization; +using KeePassLib.Utility; +using File = System.IO.File; +using FileNotFoundException = System.IO.FileNotFoundException; +using IOException = System.IO.IOException; + +namespace keepass2android.Io +{ + public abstract class BuiltInFileStorage : IFileStorage, IPermissionRequestingFileStorage + { + private const string PermissionGrantedKey = "PermissionGranted"; + + public enum CertificateProblem :long + { + CertEXPIRED = 0x800B0101, + CertVALIDITYPERIODNESTING = 0x800B0102, + CertROLE = 0x800B0103, + CertPATHLENCONST = 0x800B0104, + CertCRITICAL = 0x800B0105, + CertPURPOSE = 0x800B0106, + CertISSUERCHAINING = 0x800B0107, + CertMALFORMED = 0x800B0108, + CertUNTRUSTEDROOT = 0x800B0109, + CertCHAINING = 0x800B010A, + CertREVOKED = 0x800B010C, + CertUNTRUSTEDTESTROOT = 0x800B010D, + CertREVOCATION_FAILURE = 0x800B010E, + CertCN_NO_MATCH = 0x800B010F, + CertWRONG_USAGE = 0x800B0110, + CertUNTRUSTEDCA = 0x800B0112 + } + + + private readonly IKp2aApp _app; + + public BuiltInFileStorage(IKp2aApp app) + { + _app = app; + //use the obsolute CertificatePolicy because the ServerCertificateValidationCallback isn't called in Mono for Android (?) + //ServicePointManager.CertificatePolicy = new CertificatePolicity(app); + IOConnection.CertificateValidationCallback = app.CertificateValidationCallback; + + } + + + public abstract IEnumerable SupportedProtocols { get; } + + public bool UserShouldBackup + { + get { return true; } + } + + public void Delete(IOConnectionInfo ioc) + { + //todo check if directory + IOConnection.DeleteFile(ioc); + } + public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion) + { + if (!ioc.IsLocalFile()) + return false; + if (previousFileVersion == null) + return false; + DateTime previousDate; + if (!DateTime.TryParse(previousFileVersion, CultureInfo.InvariantCulture, DateTimeStyles.None, out previousDate)) + return false; + DateTime currentModificationDate = File.GetLastWriteTimeUtc(ioc.Path); + TimeSpan diff = currentModificationDate - previousDate; + return diff > TimeSpan.FromSeconds(1); + //don't use > operator because milliseconds are truncated + //return File.GetLastWriteTimeUtc(ioc.Path) - previousDate >= TimeSpan.FromSeconds(1); + } + + + public string GetCurrentFileVersionFast(IOConnectionInfo ioc) + { + + if (ioc.IsLocalFile()) + { + return File.GetLastWriteTimeUtc(ioc.Path).ToString(CultureInfo.InvariantCulture); + } + else + { + return DateTime.MinValue.ToString(CultureInfo.InvariantCulture); + } + + + } + + public Stream OpenFileForRead(IOConnectionInfo ioc) + { + try + { + return IOConnection.OpenRead(ioc); + } + catch (WebException ex) + { + ConvertException(ioc, ex); + throw; + } + } + + private void ConvertException(IOConnectionInfo ioc, WebException ex) + { + var response = ex.Response as HttpWebResponse; + if ((response != null) && (response.StatusCode == HttpStatusCode.NotFound)) + { + throw new FileNotFoundException(ex.Message, ioc.Path, ex); + } + if (ex.Status == WebExceptionStatus.TrustFailure) + { + throw new Exception(_app.GetResourceString(UiStringKey.CertificateFailure), ex); + } + var inner1 = ex.InnerException as IOException; + if (inner1 != null) + { + var inner2 = inner1.InnerException; + if (inner2 != null) + { + if (inner2.Message.Contains("Invalid certificate received from server.")) + { + throw new Exception(_app.GetResourceString(UiStringKey.CertificateFailure), ex); + } + } + + } + } + + public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) + { + return new BuiltInFileTransaction(ioc, useFileTransaction, this); + } + + public class BuiltInFileTransaction : IWriteTransaction + { + private readonly IOConnectionInfo _ioc; + private readonly BuiltInFileStorage _fileStorage; + private readonly FileTransactionEx _transaction; + + public BuiltInFileTransaction(IOConnectionInfo ioc, bool useFileTransaction, BuiltInFileStorage fileStorage) + { + _ioc = ioc; + _fileStorage = fileStorage; + _transaction = new FileTransactionEx(ioc, useFileTransaction); + } + + public void Dispose() + { + + } + + public Stream OpenFile() + { + try + { + return _transaction.OpenWrite(); + } + catch (WebException ex) + { + _fileStorage.ConvertException(_ioc, ex); + throw; + } + + } + + public void CommitWrite() + { + try + { + _transaction.CommitWrite(); + } + catch (WebException ex) + { + _fileStorage.ConvertException(_ioc, ex); + throw; + } + + } + } + + public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc) + { + return UrlUtil.StripExtension( + UrlUtil.GetFileName(ioc.Path)); + + } + + public string GetFileExtension(IOConnectionInfo ioc) + { + return UrlUtil.GetExtension(ioc.Path); + } + + public bool RequiresCredentials(IOConnectionInfo ioc) + { + return (!ioc.IsLocalFile()) && (ioc.CredSaveMode != IOCredSaveMode.SaveCred); + } + + public void CreateDirectory(IOConnectionInfo ioc, string newDirName) + { + //TODO + throw new NotImplementedException(); + } + + public IEnumerable ListContents(IOConnectionInfo ioc) + { + //TODO + throw new NotImplementedException(); + } + + public FileDescription GetFileDescription(IOConnectionInfo ioc) + { + //TODO + throw new NotImplementedException(); + } + + public bool RequiresSetup(IOConnectionInfo ioConnection) + { + return false; + } + + public string IocToPath(IOConnectionInfo ioc) + { + return ioc.Path; + } + + public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId) + { + if (protocolId != "file") + activity.PerformManualFileSelect(isForSave, requestCode, protocolId); + else + { + Intent intent = new Intent(); + activity.IocToIntent(intent, new IOConnectionInfo() { Path = protocolId+"://", }); + activity.OnImmediateResult(requestCode, (int) FileStorageResults.FileChooserPrepared, intent); + } + } + + public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode, bool alwaysReturnSuccess) + { + //check if we need to request the external-storage-permission at runtime + if (ioc.IsLocalFile()) + { + bool requiresPermission = !(ioc.Path.StartsWith(activity.Activity.FilesDir.CanonicalPath) + || ioc.Path.StartsWith(IoUtil.GetInternalDirectory(activity.Activity).CanonicalPath) + || ioc.Path.StartsWith(IoUtil.GetInternalDirectory(activity.Activity).CanonicalPath)); + + var extDirectory = activity.Activity.GetExternalFilesDir(null); + if ((extDirectory != null) && (ioc.Path.StartsWith(extDirectory.CanonicalPath))) + requiresPermission = false; + + if (requiresPermission && (Build.VERSION.SdkInt >= BuildVersionCodes.M)) + { + if ((activity.Activity.CheckSelfPermission(Manifest.Permission.WriteExternalStorage) == + Permission.Denied) + || + (activity.Activity.CheckSelfPermission(Manifest.Permission.ReadExternalStorage) == + Permission.Denied)) + + { + activity.StartFileUsageProcess(ioc, requestCode, alwaysReturnSuccess); + return; + } + } + + } + Intent intent = new Intent(); + activity.IocToIntent(intent, ioc); + activity.OnImmediateResult(requestCode, (int) FileStorageResults.FileUsagePrepared, intent); + } + + public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc) + { + //nothing to do, we're ready to go + } + + public void OnCreate(IFileStorageSetupActivity fileStorageSetupActivity, Bundle savedInstanceState) + { + AndroidX.Core.App.ActivityCompat.RequestPermissions(((Activity)fileStorageSetupActivity), new[] { Manifest.Permission.WriteExternalStorage, Manifest.Permission.ReadExternalStorage }, 0); + } + + public void OnResume(IFileStorageSetupActivity activity) + { + if (activity.State.ContainsKey(PermissionGrantedKey)) + { + if (activity.State.GetBoolean(PermissionGrantedKey)) + { + Intent data = new Intent(); + data.PutExtra(FileStorageSetupDefs.ExtraIsForSave, activity.IsForSave); + data.PutExtra(FileStorageSetupDefs.ExtraPath, IocToPath(activity.Ioc)); + ((Activity) activity).SetResult((Result) FileStorageResults.FileUsagePrepared, data); + ((Activity) activity).Finish(); + } + else + { + Intent data = new Intent(); + data.PutExtra(FileStorageSetupDefs.ExtraErrorMessage, "Permission denied. Please grant file access permission for this app."); + ((Activity)activity).SetResult(Result.Canceled, data); + ((Activity)activity).Finish(); + + } + } + } + + public void OnStart(IFileStorageSetupActivity activity) + { + + } + + public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data) + { + + } + + public string GetDisplayName(IOConnectionInfo ioc) + { + return ioc.GetDisplayName(); + } + + public string CreateFilePath(string parent, string newFilename) + { + if (!parent.EndsWith("/")) + parent += "/"; + return parent + newFilename; + } + + public IOConnectionInfo GetParentPath(IOConnectionInfo ioc) + { + return IoUtil.GetParentPath(ioc); + } + + public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename) + { + IOConnectionInfo res = folderPath.CloneDeep(); + if (!res.Path.EndsWith("/")) + res.Path += "/"; + res.Path += filename; + return res; + } + + public bool IsPermanentLocation(IOConnectionInfo ioc) + { + return true; + } + + public bool IsReadOnlyBecauseKitkatRestrictions(IOConnectionInfo ioc) + { + if (IsLocalFileFlaggedReadOnly(ioc)) + return false; //it's not read-only because of the restrictions introduced in kitkat + try + { + //test if we can open + //http://www.doubleencore.com/2014/03/android-external-storage/#comment-1294469517 + using (var writer = new FileOutputStream(ioc.Path, true)) + { + writer.Close(); + return false; //we can write + } + } + catch (Java.IO.IOException) + { + //seems like we can't write to that location even though it's not read-only + return true; + } + + } + + public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null) + { + if (ioc.IsLocalFile()) + { + if (IsLocalBackup(ioc)) + { + if (reason != null) + reason.Result = UiStringKey.ReadOnlyReason_LocalBackup; + return true; + } + + if (IsLocalFileFlaggedReadOnly(ioc)) + { + if (reason != null) + reason.Result = UiStringKey.ReadOnlyReason_ReadOnlyFlag; + return true; + } + + if (IsReadOnlyBecauseKitkatRestrictions(ioc)) + { + if (reason != null) + reason.Result = UiStringKey.ReadOnlyReason_ReadOnlyKitKat; + return true; + } + + + return false; + } + //for remote files assume they can be written: (think positive! :-) ) + return false; + } + + private readonly Dictionary _isLocalBackupCache = new Dictionary(); + public bool IsLocalBackup(IOConnectionInfo ioc) + { + if (!ioc.IsLocalFile()) + return false; + bool result; + if (_isLocalBackupCache.TryGetValue(ioc.Path, out result)) + return result; + + result = (PreferenceManager.GetDefaultSharedPreferences(Application.Context) + .GetBoolean(IoUtil.GetIocPrefKey(ioc, "is_local_backup"), false)); + _isLocalBackupCache[ioc.Path] = result; + return result; + } + + private bool IsLocalFileFlaggedReadOnly(IOConnectionInfo ioc) + { + //see http://stackoverflow.com/a/33292700/292233 + try + { + return new FileInfo(ioc.Path).IsReadOnly; + } + catch (SecurityException) + { + return true; + } + catch (UnauthorizedAccessException) + { + return true; + } + catch (Exception) + { + return false; + } + } + + public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode, + string[] permissions, Permission[] grantResults) + { + fileStorageSetupActivity.State.PutBoolean(PermissionGrantedKey, grantResults.All(res => res == Permission.Granted)); + } + } + + + public class LocalFileStorage : BuiltInFileStorage + { + public LocalFileStorage(IKp2aApp app) + : base(app) + { + } + + public override IEnumerable SupportedProtocols + { + get + { + yield return "file"; + } + } + } +#if !NoNet + public class LegacyFtpStorage : BuiltInFileStorage + { + public LegacyFtpStorage(IKp2aApp app) : base(app) + { + } + + public override IEnumerable SupportedProtocols + { + get + { + + yield return "ftp"; + + } + } + } + + public class LegacyWebDavStorage : BuiltInFileStorage + { + public LegacyWebDavStorage(IKp2aApp app) : base(app) + { + } + + public override IEnumerable SupportedProtocols + { + get + { + + yield return "http"; + yield return "https"; + + + } + } + } + +#endif +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/CachingFileStorage.cs b/src/Kp2aBusinessLogicSdkStyle/Io/CachingFileStorage.cs new file mode 100644 index 00000000..0d8e5e27 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/CachingFileStorage.cs @@ -0,0 +1,641 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using Android.Content; +using Android.Content.PM; +using Android.OS; +using KeePassLib.Cryptography; +using KeePassLib.Serialization; +using KeePassLib.Utility; + +namespace keepass2android.Io +{ + /// + /// Interface for classes which can handle certain Cache events on a higher level (e.g. by user interaction) + /// + public interface ICacheSupervisor + { + /// + /// called when a save operation only updated the cache but not the remote file + /// + /// The file which we tried to write + /// The exception why the remote file couldn't be updated + void CouldntSaveToRemote(IOConnectionInfo ioc, Exception ex); + + /// + /// Called when only the local file could be opened during an open operation. + /// + void CouldntOpenFromRemote(IOConnectionInfo ioc, Exception ex); + + /// + /// Called when the local file either didn't exist or was unmodified, so the remote file + /// was loaded and the cache was updated during the load operation. + /// + void UpdatedCachedFileOnLoad(IOConnectionInfo ioc); + + /// + /// Called when the remote file either didn't exist or was unmodified, so the local file + /// was loaded and the remote file was updated during the load operation. + /// + void UpdatedRemoteFileOnLoad(IOConnectionInfo ioc); + + /// + /// Called to notify the supervisor that the file described by ioc is opened from the cache because there's a conflict + /// with local and remote changes + /// + /// + void NotifyOpenFromLocalDueToConflict(IOConnectionInfo ioc); + + /// + /// Called when the load operation was performed and the remote file was identical with the local file + /// + void LoadedFromRemoteInSync(IOConnectionInfo ioc); + } + + + /// + /// Implements the IFileStorage interface as a proxy: A base storage is used as a remote storage. Local files are used to cache the + /// files on remote. + /// + public class CachingFileStorage : IFileStorage, IOfflineSwitchable, IPermissionRequestingFileStorage + { + + protected readonly OfflineSwitchableFileStorage _cachedStorage; + private readonly ICacheSupervisor _cacheSupervisor; + private readonly string _legacyCacheDir; + private readonly string _cacheDir; + + public CachingFileStorage(IFileStorage cachedStorage, Context cacheDirContext, ICacheSupervisor cacheSupervisor) + { + _cachedStorage = new OfflineSwitchableFileStorage(cachedStorage); + _cacheSupervisor = cacheSupervisor; + _legacyCacheDir = cacheDirContext.CacheDir.Path + Java.IO.File.Separator + "OfflineCache" + Java.IO.File.Separator; + if (!Directory.Exists(_legacyCacheDir)) + Directory.CreateDirectory(_legacyCacheDir); + + _cacheDir = IoUtil.GetInternalDirectory(cacheDirContext).Path + Java.IO.File.Separator + "OfflineCache" + Java.IO.File.Separator; + if (!Directory.Exists(_cacheDir)) + Directory.CreateDirectory(_cacheDir); + + } + + public void ClearCache() + { + IoUtil.DeleteDir(new Java.IO.File(_legacyCacheDir), true); + IoUtil.DeleteDir(new Java.IO.File(_cacheDir), true); + } + + public IEnumerable SupportedProtocols { get { return _cachedStorage.SupportedProtocols; } } + + public bool UserShouldBackup + { + get { return _cachedStorage.UserShouldBackup; } + } + + public void DeleteFile(IOConnectionInfo ioc) + { + if (IsCached(ioc)) + { + File.Delete(CachedFilePath(ioc)); + File.Delete(VersionFilePath(ioc)); + File.Delete(BaseVersionFilePath(ioc)); + } + + _cachedStorage.Delete(ioc); + } + + private string CachedFilePath(IOConnectionInfo ioc) + { + SHA256Managed sha256 = new SHA256Managed(); + string iocAsHexString = MemUtil.ByteArrayToHexString(sha256.ComputeHash(Encoding.Unicode.GetBytes(ioc.Path.ToCharArray())))+".cache"; + if (File.Exists(_legacyCacheDir + iocAsHexString)) + return _legacyCacheDir + iocAsHexString; + + return _cacheDir + iocAsHexString; + + } + + public bool IsCached(IOConnectionInfo ioc) + { + bool result = File.Exists(CachedFilePath(ioc)) + && File.Exists(VersionFilePath(ioc)) + && File.Exists(BaseVersionFilePath(ioc)); + + Kp2aLog.Log(GetDisplayName(ioc) + " isCached = " + result); + + return result; + } + + public void Delete(IOConnectionInfo ioc) + { + _cachedStorage.Delete(ioc); + } + + public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion) + { + //see comment in GetCurrentFileVersionFast + return false; + } + + public string GetCurrentFileVersionFast(IOConnectionInfo ioc) + { + //fast file version checking is not supported by CachingFileStorage: + //it's hard to return good versions in cases that the base source is offline + //or after modifying the cache. + //It's probably not relevant because fast file version checking is meant for local storage + //which is not cached. + return String.Empty; + } + + private string VersionFilePath(IOConnectionInfo ioc) + { + return CachedFilePath(ioc)+".version"; + } + + private string BaseVersionFilePath(IOConnectionInfo ioc) + { + return CachedFilePath(ioc) + ".baseversion"; + } + + public Stream OpenFileForRead(IOConnectionInfo ioc) + { + string cachedFilePath = CachedFilePath(ioc); + try + { + if (!IsCached(ioc) + || GetLocalVersionHash(ioc) == GetBaseVersionHash(ioc)) + { + Kp2aLog.Log("CFS: OpenWhenNoLocalChanges"); + return OpenFileForReadWhenNoLocalChanges(ioc, cachedFilePath); + } + else + { + Kp2aLog.Log("CFS: OpenWhenLocalChanges"); + return OpenFileForReadWhenLocalChanges(ioc, cachedFilePath); + } + } + catch (Exception ex) + { + if (!IsCached(ioc)) + throw; + +#if DEBUG + Kp2aLog.Log("couldn't open from remote " + ioc.Path); +#endif + Kp2aLog.Log(ex.ToString()); + + _cacheSupervisor.CouldntOpenFromRemote(ioc, ex); + return File.OpenRead(cachedFilePath); + } + } + + private Stream OpenFileForReadWhenLocalChanges(IOConnectionInfo ioc, string cachedFilePath) + { + //file is cached but has local modifications + //try to upload the changes if remote file doesn't have changes as well: + var hash = CalculateHash(ioc); + + if (File.ReadAllText(BaseVersionFilePath(ioc)) == hash) + { + Kp2aLog.Log("CFS: No changes in remote"); + //no changes in remote file -> upload + using (Stream localData = File.OpenRead(CachedFilePath(ioc))) + { + if (TryUpdateRemoteFile(localData, ioc, true, hash)) + { + _cacheSupervisor.UpdatedRemoteFileOnLoad(ioc); + Kp2aLog.Log("CFS: Updated remote file"); + } + return File.OpenRead(cachedFilePath); + } + } + else + { + Kp2aLog.Log("CFS: Files in conflict"); + //conflict: both files changed. + return OpenFileForReadWithConflict(ioc, cachedFilePath); + } + + } + + protected virtual Stream OpenFileForReadWithConflict(IOConnectionInfo ioc, string cachedFilePath) + { + //signal that we're loading from local + _cacheSupervisor.NotifyOpenFromLocalDueToConflict(ioc); + return File.OpenRead(cachedFilePath); + } + + public MemoryStream GetRemoteDataAndHash(IOConnectionInfo ioc, out string hash) + { + MemoryStream remoteData = new MemoryStream(); + + using (var remoteStream =_cachedStorage.OpenFileForRead(ioc)) + { + //note: directly copying to remoteData and hashing causes NullReferenceExceptions in FTP and with Digest auth + // -> use the temp data approach: + MemoryStream tempData = new MemoryStream(); + remoteStream.CopyTo(tempData); + tempData.Position = 0; + HashingStreamEx hashingRemoteStream = new HashingStreamEx(tempData, false, new SHA256Managed()); + + hashingRemoteStream.CopyTo(remoteData); + hashingRemoteStream.Close(); + hash = MemUtil.ByteArrayToHexString(hashingRemoteStream.Hash); + } + remoteData.Position = 0; + return remoteData; + } + + private string CalculateHash(IOConnectionInfo ioc) + { + string hash; + GetRemoteDataAndHash(ioc, out hash); + return hash; + } + + private Stream OpenFileForReadWhenNoLocalChanges(IOConnectionInfo ioc, string cachedFilePath) + { + + //remember current hash + string previousHash = null; + string baseVersionFilePath = BaseVersionFilePath(ioc); + if (File.Exists(baseVersionFilePath)) + { + Kp2aLog.Log("CFS: hashing cached version"); + previousHash = File.ReadAllText(baseVersionFilePath); + } + + //copy to cache: + var fileHash = UpdateCacheFromRemote(ioc, cachedFilePath); + + //notify supervisor what we did: + if (previousHash != fileHash) + { + Kp2aLog.Log("CFS: Updated Cache"); + _cacheSupervisor.UpdatedCachedFileOnLoad(ioc); + } + else + { + Kp2aLog.Log("CFS: Files in Sync"); + _cacheSupervisor.LoadedFromRemoteInSync(ioc); + } + + return File.OpenRead(cachedFilePath); + + + } + + /// + /// copies the file in ioc to the local cache. Updates the cache version files and returns the new file hash. + /// + protected string UpdateCacheFromRemote(IOConnectionInfo ioc, string cachedFilePath) + { + //note: we might use the file version to check if it's already in the cache and if copying is required. + //However, this is safer. + string fileHash; + + //open stream: + using (Stream remoteFile = _cachedStorage.OpenFileForRead(ioc)) + { + + using (HashingStreamEx cachedFile = new HashingStreamEx(File.Create(cachedFilePath), true, new SHA256Managed())) + { + remoteFile.CopyTo(cachedFile); + cachedFile.Close(); + fileHash = MemUtil.ByteArrayToHexString(cachedFile.Hash); + } + } + + //save hash in cache files: + File.WriteAllText(VersionFilePath(ioc), fileHash); + File.WriteAllText(BaseVersionFilePath(ioc), fileHash); + return fileHash; + } + + private bool TryUpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash) + { + try + { + UpdateRemoteFile(cachedData, ioc, useFileTransaction, hash); + return true; + } + catch (Exception e) + { + Kp2aLog.Log("couldn't save to remote " + ioc.Path); + Kp2aLog.Log(e.ToString()); + //notify the supervisor so it might display a warning or schedule a retry + _cacheSupervisor.CouldntSaveToRemote(ioc, e); + return false; + } + } + + protected void UpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash) + { + //try to write to remote: + using ( + IWriteTransaction remoteTrans = _cachedStorage.OpenWriteTransaction(ioc, useFileTransaction)) + { + Stream remoteStream = remoteTrans.OpenFile(); + cachedData.CopyTo(remoteStream); + remoteStream.Close(); + remoteTrans.CommitWrite(); + } + //success. Update base-version of cache: + File.WriteAllText(BaseVersionFilePath(ioc), hash); + File.WriteAllText(VersionFilePath(ioc), hash); + } + + public void UpdateRemoteFile(IOConnectionInfo ioc, bool useFileTransaction) + { + using (Stream cachedData = File.OpenRead(CachedFilePath(ioc))) + { + UpdateRemoteFile(cachedData, ioc, useFileTransaction, GetLocalVersionHash(ioc)); + } + + } + + + + private class CachedWriteTransaction: IWriteTransaction + { + + + private readonly IOConnectionInfo _ioc; + private readonly bool _useFileTransaction; + private readonly CachingFileStorage _cachingFileStorage; + private MemoryStream _memoryStream; + private bool _committed; + + public CachedWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction, CachingFileStorage cachingFileStorage) + { + _ioc = ioc; + _useFileTransaction = useFileTransaction; + _cachingFileStorage = cachingFileStorage; + } + + public void Dispose() + { + if (!_committed) + { + try + { + _memoryStream.Dispose(); + } + catch (ObjectDisposedException e) + { + Kp2aLog.Log("Ignoring exception in Dispose: "+e); + } + + } + + } + + public Stream OpenFile() + { + _memoryStream = new MemoryStream(); + return _memoryStream; + } + + public void CommitWrite() + { + _committed = true; + _memoryStream.Close(); + + //write file to cache: + //(note: this might overwrite local changes. It's assumed that a sync operation or check was performed before + + byte[] output = _memoryStream.ToArray(); + + string hash; + using (var hashingStream = new HashingStreamEx(File.Create(_cachingFileStorage.CachedFilePath(_ioc)), true, new SHA256Managed())) + { + hashingStream.Write(output, 0, output.Length); + + hashingStream.Close(); + hash = MemUtil.ByteArrayToHexString(hashingStream.Hash); + } + + File.WriteAllText(_cachingFileStorage.VersionFilePath(_ioc), hash); + //create another memory stream which is open for reading again + MemoryStream openMemStream = new MemoryStream(output); + //update file on remote. This might overwrite changes there as well, see above. + if (_cachingFileStorage.IsCached(_ioc)) + { + //if the file already is in the cache, it's ok if writing to remote fails. + _cachingFileStorage.TryUpdateRemoteFile(openMemStream, _ioc, _useFileTransaction, hash); + } + else + { + //if not, we don't accept a failure (e.g. invalid credentials would always remain a problem) + _cachingFileStorage.UpdateRemoteFile(openMemStream, _ioc, _useFileTransaction, hash); + } + + openMemStream.Dispose(); + } + + } + + + public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) + { + //create a transaction which writes to memory stream + //on close: write to cache. If possible, write to online + //update versions + return new CachedWriteTransaction(ioc, useFileTransaction, this); + } + + public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc) + { + return _cachedStorage.GetFilenameWithoutPathAndExt(ioc); + } + + public string GetFileExtension(IOConnectionInfo ioc) + { + return _cachedStorage.GetFileExtension(ioc); + } + + public bool RequiresCredentials(IOConnectionInfo ioc) + { + return _cachedStorage.RequiresCredentials(ioc); + } + + public void CreateDirectory(IOConnectionInfo ioc, string newDirName) + { + _cachedStorage.CreateDirectory(ioc, newDirName); + } + + public IEnumerable ListContents(IOConnectionInfo ioc) + { + return _cachedStorage.ListContents(ioc); + } + + public FileDescription GetFileDescription(IOConnectionInfo ioc) + { + return _cachedStorage.GetFileDescription(ioc); + } + + public bool RequiresSetup(IOConnectionInfo ioConnection) + { + return _cachedStorage.RequiresSetup(ioConnection); + } + + public string IocToPath(IOConnectionInfo ioc) + { + return _cachedStorage.IocToPath(ioc); + } + + public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId) + { + _cachedStorage.StartSelectFile(activity, isForSave, requestCode, protocolId); + } + + public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode, bool alwaysReturnSuccess) + { + //we try to prepare the file usage by the underlying file storage but if the ioc is cached, set the flag to ignore errors + _cachedStorage.PrepareFileUsage(activity, ioc, requestCode, alwaysReturnSuccess || IsCached(ioc)); + } + + public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc) + { + _cachedStorage.PrepareFileUsage(ctx, ioc); + } + + public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState) + { + _cachedStorage.OnCreate(activity, savedInstanceState); + } + + public void OnResume(IFileStorageSetupActivity activity) + { + _cachedStorage.OnResume(activity); + } + + public void OnStart(IFileStorageSetupActivity activity) + { + _cachedStorage.OnStart(activity); + } + + public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data) + { + _cachedStorage.OnActivityResult(activity, requestCode, resultCode, data); + } + + public string GetDisplayName(IOConnectionInfo ioc) + { + return _cachedStorage.GetDisplayName(ioc); + } + + public string CreateFilePath(string parent, string newFilename) + { + return _cachedStorage.CreateFilePath(parent, newFilename); + } + + public IOConnectionInfo GetParentPath(IOConnectionInfo ioc) + { + return _cachedStorage.GetParentPath(ioc); + } + + public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename) + { + try + { + IOConnectionInfo res = _cachedStorage.GetFilePath(folderPath, filename); + //some file storage implementations require accessing the network to determine the file path (e.g. because + //they might contain file ids). In this case, we need to cache the result to enable cached access to such files + StoreFilePath(folderPath, filename, res); + return res; + } + catch (Exception) + { + IOConnectionInfo res; + if (!TryGetCachedFilePath(folderPath, filename, out res)) throw; + return res; + } + + } + + public bool IsPermanentLocation(IOConnectionInfo ioc) + { + //even though the cache would be permanent, it's not a good idea to cache a temporary file, so return false in that case: + return _cachedStorage.IsPermanentLocation(ioc); + } + + public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null) + { + //even though the cache can always be written, the changes made in the cache could not be transferred to the cached file + //so we better treat the cache as read-only as well. + return _cachedStorage.IsReadOnly(ioc, reason); + } + + private void StoreFilePath(IOConnectionInfo folderPath, string filename, IOConnectionInfo res) + { + File.WriteAllText(CachedFilePath(GetPseudoIoc(folderPath, filename)) + ".filepath", res.Path); + } + + private IOConnectionInfo GetPseudoIoc(IOConnectionInfo folderPath, string filename) + { + IOConnectionInfo res = folderPath.CloneDeep(); + if (!res.Path.EndsWith("/")) + res.Path += "/"; + res.Path += filename; + return res; + } + + private bool TryGetCachedFilePath(IOConnectionInfo folderPath, string filename, out IOConnectionInfo res) + { + res = folderPath.CloneDeep(); + string filePathCache = CachedFilePath(GetPseudoIoc(folderPath, filename)) + ".filepath"; + if (!File.Exists(filePathCache)) + return false; + res.Path = File.ReadAllText(filePathCache); + return true; + } + + + public string GetBaseVersionHash(IOConnectionInfo ioc) + { + string hash = File.ReadAllText(BaseVersionFilePath(ioc)); + Kp2aLog.Log(GetDisplayName(ioc) + " baseVersionHash = " + hash); + return hash; + } + public string GetLocalVersionHash(IOConnectionInfo ioc) + { + string hash = File.ReadAllText(VersionFilePath(ioc)); + + Kp2aLog.Log(GetDisplayName(ioc) + " localVersionHash = " + hash); + return hash; + } + public bool HasLocalChanges(IOConnectionInfo ioc) + { + return IsCached(ioc) + && GetLocalVersionHash(ioc) != GetBaseVersionHash(ioc); + } + + public Stream OpenRemoteForReadIfAvailable(IOConnectionInfo ioc) + { + try + { + return _cachedStorage.OpenFileForRead(ioc); + } + catch (Exception) + { + return File.OpenRead(CachedFilePath(ioc)); + } + } + + public bool IsOffline + { + get { return _cachedStorage.IsOffline; } + set { _cachedStorage.IsOffline = value; } + } + + public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode, + string[] permissions, Permission[] grantResults) + { + _cachedStorage.OnRequestPermissionsResult(fileStorageSetupActivity, requestCode, permissions, grantResults); + } + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/DropboxFileStorage.cs b/src/Kp2aBusinessLogicSdkStyle/Io/DropboxFileStorage.cs new file mode 100644 index 00000000..4ad64472 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/DropboxFileStorage.cs @@ -0,0 +1,35 @@ +using Android.Content; +#if !EXCLUDE_JAVAFILESTORAGE + +namespace keepass2android.Io +{ + public partial class DropboxFileStorage: JavaFileStorage + { + public DropboxFileStorage(Context ctx, IKp2aApp app) : + base(new Keepass2android.Javafilestorage.DropboxV2Storage(ctx, AppKey, AppSecret), app) + { + } + + + public override bool UserShouldBackup + { + get { return false; } + } + } + + public partial class DropboxAppFolderFileStorage: JavaFileStorage + { + public DropboxAppFolderFileStorage(Context ctx, IKp2aApp app) : + base(new Keepass2android.Javafilestorage.DropboxV2AppFolderStorage(ctx, AppKey, AppSecret), app) + { + } + + public override bool UserShouldBackup + { + get { return false; } + } + + } + +} +#endif \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/DropboxFileStorageKeys.cs b/src/Kp2aBusinessLogicSdkStyle/Io/DropboxFileStorageKeys.cs new file mode 100644 index 00000000..0a2571b3 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/DropboxFileStorageKeys.cs @@ -0,0 +1,19 @@ +namespace keepass2android.Io +{ + public partial class DropboxFileStorage + { +#if DEBUG + private const string AppKey = "2gormiq7iq1jls1"; + private const string AppSecret = "40wbylk5qkihy41"; + +#else + private const string AppKey = "i8shu7v1hgh7ynt"; + private const string AppSecret = "xircwullfx3mbj2"; +#endif + } + public partial class DropboxAppFolderFileStorage + { + private const string AppKey = "ax0268uydp1ya57"; + private const string AppSecret = "3s86datjhkihwyc"; + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/DropboxFileStorageKeysDummy.cs b/src/Kp2aBusinessLogicSdkStyle/Io/DropboxFileStorageKeysDummy.cs new file mode 100644 index 00000000..924f7c50 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/DropboxFileStorageKeysDummy.cs @@ -0,0 +1,13 @@ +namespace keepass2android.Io +{ + public partial class DropboxFileStorage + { + private const string AppKey = "dummy"; + private const string AppSecret = "dummy"; + } + public partial class DropboxAppFolderFileStorage + { + private const string AppKey = "dummy"; + private const string AppSecret = "dummy"; + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/FileDescription.cs b/src/Kp2aBusinessLogicSdkStyle/Io/FileDescription.cs new file mode 100644 index 00000000..8c394a11 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/FileDescription.cs @@ -0,0 +1,16 @@ +using System; + +namespace keepass2android.Io +{ + public class FileDescription + { + public string Path { get; set; } + public bool IsDirectory { get; set; } + public DateTime LastModified { get; set; } + public bool CanRead { get; set; } + public bool CanWrite { get; set; } + public long SizeInBytes { get; set; } + + public String DisplayName { get; set; } + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/FileStorageSetupActivity.cs b/src/Kp2aBusinessLogicSdkStyle/Io/FileStorageSetupActivity.cs new file mode 100644 index 00000000..fd3534c1 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/FileStorageSetupActivity.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; +using KeePassLib.Serialization; + +namespace keepass2android.Io +{ + public interface IFileStorageSetupActivity + { + IOConnectionInfo Ioc { get; } + String ProcessName { get; } + bool IsForSave { get; } + Bundle State { get; } + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/FileStorageSetupInitiatorActivity.cs b/src/Kp2aBusinessLogicSdkStyle/Io/FileStorageSetupInitiatorActivity.cs new file mode 100644 index 00000000..01bddb09 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/FileStorageSetupInitiatorActivity.cs @@ -0,0 +1,20 @@ +using System; +using Android.App; +using Android.Content; +using Android.OS; +using KeePassLib.Serialization; + +namespace keepass2android.Io +{ + public interface IFileStorageSetupInitiatorActivity + { + void StartSelectFileProcess(IOConnectionInfo ioc, bool isForSave, int requestCode); + void StartFileUsageProcess(IOConnectionInfo ioc, int requestCode, bool alwaysReturnSuccess); + void OnImmediateResult(int requestCode, int result, Intent intent); + + Activity Activity { get; } + + void IocToIntent(Intent intent, IOConnectionInfo ioc); + void PerformManualFileSelect(bool isForSave, int requestCode, string protocolId); + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/GDriveFileStorage.cs b/src/Kp2aBusinessLogicSdkStyle/Io/GDriveFileStorage.cs new file mode 100644 index 00000000..9d83c3ba --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/GDriveFileStorage.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; +using KeePassLib.Serialization; +#if !EXCLUDE_JAVAFILESTORAGE +namespace keepass2android.Io +{ + public class GoogleDriveFileStorage : JavaFileStorage + { + public GoogleDriveFileStorage(Context ctx, IKp2aApp app) : + base(new Keepass2android.Javafilestorage.GoogleDriveFullFileStorage(), app) + { + } + + + public override bool UserShouldBackup + { + get { return false; } + } + } + + public class GoogleDriveAppDataFileStorage : JavaFileStorage + { + public GoogleDriveAppDataFileStorage(Context ctx, IKp2aApp app) : + base(new Keepass2android.Javafilestorage.GoogleDriveAppDataFileStorage(), app) + { + } + + + public override bool UserShouldBackup + { + get { return false; } + } + } +} +#endif \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/IFileStorage.cs b/src/Kp2aBusinessLogicSdkStyle/Io/IFileStorage.cs new file mode 100644 index 00000000..b02aa7ee --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/IFileStorage.cs @@ -0,0 +1,221 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Android.Content; +using Android.Content.PM; +using Android.OS; +using KeePassLib.Serialization; + +namespace keepass2android.Io +{ + + public enum FileStorageResults + { + FullFilenameSelected = 874345 + 1, + FileChooserPrepared = FullFilenameSelected + 1, + FileUsagePrepared = FileChooserPrepared + 1 + } + + public class OptionalOut + { + public T Result { get; set; } + } + + public static class FileStorageSetupDefs + { + public static String ProcessNameSelectfile = "SELECT_FILE"; + public static String ProcessNameFileUsageSetup = "FILE_USAGE_SETUP"; + + public static String ExtraProcessName = "EXTRA_PROCESS_NAME"; + public static String ExtraAlwaysReturnSuccess = "EXTRA_ALWAYS_RETURN_SUCCESS"; + public static String ExtraPath = "fileName"; //match KP2A PasswordActivity Ioc-Path Extra key + public static String ExtraIsForSave = "IS_FOR_SAVE"; + public static String ExtraErrorMessage = "EXTRA_ERROR_MESSAGE"; + + } + + /// + /// Interface to encapsulate all access to disk or cloud. + /// + /// Note that it was decided to use the IOConnectionInfo also for cloud storage. + /// The advantage is that the database for saving recent files etc. will then work without + /// much work to do. Furthermore, the IOConnectionInfo seems generic info to capture all required data, even though it might be nicer to + /// have an IIoStorageId interface in few cases.*/ + public interface IFileStorage + { + /// + /// returns the protocol ids supported by this FileStorage. Can return pseudo-protocols like "dropbox" or real protocols like "ftp" + /// + IEnumerable SupportedProtocols { get; } + + /// + /// returns true if users should backup files on this file storage (if the file is important). Can be false for cloud providers with built-in versioning or backups. + /// + bool UserShouldBackup { get; } + + /// + /// Deletes the given file or directory. + /// + void Delete(IOConnectionInfo ioc); + + /// + /// Tests whether the file was changed. + /// + /// Note: This function may return false even if the file might have changed. The function + /// should focus on being fast and cheap instead of doing things like hashing or downloading a full file. + /// previousFileVersion may be null to indicate no previous version is known. + /// Returns true if a change was detected, false otherwise. + bool CheckForFileChangeFast(IOConnectionInfo ioc , string previousFileVersion); + + /// + /// Returns a string describing the "version" of the file specified by ioc. + /// + /// This string may have a deliberate value (except null) and should not be used by callers except for passing it to + /// CheckForFileChangeFast(). + /// A string describing the version. Null means, there is no way to get a file version (or it's not implemented). + string GetCurrentFileVersionFast(IOConnectionInfo ioc); + + /// + /// Opens the given file for reading + /// + Stream OpenFileForRead(IOConnectionInfo ioc); + + /// + /// Opens a write transaction for writing to the given ioc. + /// + /// ioc to write to + /// if true, force to use file system level transaction. This might be ignored if the file storage has built in transaction support + IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction); + + string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc); + + string GetFileExtension(IOConnectionInfo ioc); + + /// + /// Returns true if the the given ioc must be filled with username/password + /// + bool RequiresCredentials(IOConnectionInfo ioc); + + /// + /// Creates the directory described by ioc + /// + void CreateDirectory(IOConnectionInfo ioc, string newDirName); + + /// + /// Lists the contents of the given path + /// + IEnumerable ListContents(IOConnectionInfo ioc); + + /// + /// returns the description of the given file + /// + FileDescription GetFileDescription(IOConnectionInfo ioc); + + /// + /// returns true if everything is ok with connecting to the given file. + /// Returns False if PrepareFileUsage must be called first. + /// + bool RequiresSetup(IOConnectionInfo ioConnection); + + /// + /// converts the ioc to a path which may contain the credentials + /// + string IocToPath(IOConnectionInfo ioc); + + /// + /// Initiates the process for choosing a file in the given file storage. + /// The file storage should either call OnImmediateResult or StartSelectFileProcess + /// + void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId); + + /// + /// Initiates the process for using a file in the given file storage. + /// The file storage should either call OnImmediateResult or StartFileUsageProcess + /// If alwaysReturnSuccess is true, the activity should be finished with ResultCode Ok. + /// This can make sense if a higher-level file storage has the file cached but still wants to + /// give the cached storage the chance to initialize file access. + /// + void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode, bool alwaysReturnSuccess); + + /// + /// Initiates the process for using a file in the given file storage. + /// This method either silently prepares using the file (if any preparation is required) or throws + /// UserInteractionRequiredException (or any other exception in case of an error). + /// Can be used from a service, i.e. when no Activity is open. + /// + void PrepareFileUsage(Context ctx, IOConnectionInfo ioc); + + //Setup methods: these are called from the setup activity so the file storage can handle UI events for authorization etc. + void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState); + void OnResume(IFileStorageSetupActivity activity); + void OnStart(IFileStorageSetupActivity activity); + void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data); + + /// + /// Converts the given path to a displayable string + /// + string GetDisplayName(IOConnectionInfo ioc); + + //returns the path of a file "newFilename" in the folder "parent" + //this may create the file if this is required to get a path (if a UUID is part of the file path) + string CreateFilePath(string parent, string newFilename); + + /// + /// returns the parent folder of ioc + /// + IOConnectionInfo GetParentPath(IOConnectionInfo ioc); + + /// + /// returns the file path of the file "filename" in the folderPath. + /// + /// The method may throw FileNotFoundException or not in case the file doesn't exist. + IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename); + + /// + /// returns true if it can be expected that this location will be available permanently (in contrast to a cache copy or temporary URI permissions in Android) + /// + /// Does not require to exist forever! + bool IsPermanentLocation(IOConnectionInfo ioc); + + + + /// + /// Should return true if the file cannot be written. + /// + bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null ); + + } + + public interface IPermissionRequestingFileStorage + { + void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode, string[] permissions, Permission[] grantResults); + } + + public interface IWriteTransaction: IDisposable + { + Stream OpenFile(); + void CommitWrite(); + } + + public class FileStorageSelectionInfo + { + public enum FileStorageSelectionMessageType + { + Info, //show only ok button + CancellableInfo, //show Ok/Cancel + Error //show cancel only + } + + public UiStringKey SelectionMessage { get; set; } + public FileStorageSelectionMessageType MessageType { get; set; } + } + + /// + /// Can be implemented by IFileStorage implementers to add additional information for the + /// process of selecting the file storage + /// + public interface IFileStorageSelectionInfoProvider + { + FileStorageSelectionInfo TryGetSelectionInfo(string protocolId); + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/IoUtil.cs b/src/Kp2aBusinessLogicSdkStyle/Io/IoUtil.cs new file mode 100644 index 00000000..054b2a87 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/IoUtil.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; +using Android.Content; +using Android.OS; +using Java.IO; +using KeePassLib.Serialization; +using KeePassLib.Utility; + +namespace keepass2android.Io +{ + public static class IoUtil + { + + public static bool TryTakePersistablePermissions(ContentResolver contentResolver, Android.Net.Uri uri) + { + if ((int)Build.VERSION.SdkInt >= 19) + { + //try to take persistable permissions + try + { + Kp2aLog.Log("TakePersistableUriPermission"); + var takeFlags = (ActivityFlags.GrantReadUriPermission + | ActivityFlags.GrantWriteUriPermission); + contentResolver.TakePersistableUriPermission(uri, takeFlags); + return true; + } + catch (Exception e) + { + Kp2aLog.Log(e.ToString()); + } + + } + return false; + } + public static bool DeleteDir(Java.IO.File dir, bool contentsOnly=false) + { + if (dir != null && dir.IsDirectory) + { + String[] children = dir.List(); + for (int i = 0; i < children.Length; i++) + { + bool success = DeleteDir(new Java.IO.File(dir, children[i])); + if (!success) + { + return false; + } + } + } + + if (contentsOnly) + return true; + + // The directory is now empty so delete it + return dir.Delete(); + } + + + public static IOConnectionInfo GetParentPath(IOConnectionInfo ioc) + { + var iocParent = ioc.CloneDeep(); + if (iocParent.Path.EndsWith("/")) + iocParent.Path = iocParent.Path.Substring(0, iocParent.Path.Length - 1); + + int slashPos = iocParent.Path.LastIndexOf("/", StringComparison.Ordinal); + if (slashPos == -1) + iocParent.Path = ""; + else + { + iocParent.Path = iocParent.Path.Substring(0, slashPos); + } + return iocParent; + } + + public static bool IsInInternalDirectory(string path, Context context) + { + try + { + Java.IO.File filesDir = context.FilesDir.CanonicalFile; + Java.IO.File noBackupDir = GetInternalDirectory(context).CanonicalFile; + Java.IO.File ourFile = new Java.IO.File(path).CanonicalFile; + //http://www.java2s.com/Tutorial/Java/0180__File/Checkswhetherthechilddirectoryisasubdirectoryofthebasedirectory.htm + + Java.IO.File parentFile = ourFile; + while (parentFile != null) + { + if ((filesDir.Equals(parentFile) || noBackupDir.Equals(parentFile))) + { + return true; + } + parentFile = parentFile.ParentFile; + } + return false; + } + catch (Exception e) + { + Kp2aLog.LogUnexpectedError(e); + return false; + } + + } + + public static void Copy(IOConnectionInfo targetIoc, IOConnectionInfo sourceIoc, IKp2aApp app) + { + IFileStorage sourceStorage = app.GetFileStorage(sourceIoc, false); //don't cache source. file won't be used ever again + IFileStorage targetStorage = app.GetFileStorage(targetIoc); + + using ( + var writeTransaction = targetStorage.OpenWriteTransaction(targetIoc, + app.GetBooleanPreference( + PreferenceKey.UseFileTransactions))) + { + using (var writeStream = writeTransaction.OpenFile()) + { + sourceStorage.OpenFileForRead(sourceIoc).CopyTo(writeStream); + } + writeTransaction.CommitWrite(); + } + } + + public static Java.IO.File GetInternalDirectory(Context ctx) + { + if ((int)Android.OS.Build.VERSION.SdkInt >= 21) + return ctx.NoBackupFilesDir; + else + return ctx.FilesDir; + } + + //creates a local ioc where the sourceIoc can be stored to + public static IOConnectionInfo GetInternalIoc(IOConnectionInfo sourceIoc, Context ctx, IKp2aApp app) + { + Java.IO.File internalDirectory = IoUtil.GetInternalDirectory(ctx); + var filestorage = app.GetFileStorage(sourceIoc); + + string targetPath = filestorage.GetFilenameWithoutPathAndExt(sourceIoc); + targetPath = targetPath.Trim("|\\?*<\":>+[]/'".ToCharArray()); + if (targetPath == "") + targetPath = "internal"; + if (new Java.IO.File(internalDirectory, targetPath).Exists()) + { + int c = 1; + var ext = UrlUtil.GetExtension(targetPath); + var filenameWithoutExt = UrlUtil.StripExtension(targetPath); + do + { + c++; + targetPath = filenameWithoutExt + c; + if (!String.IsNullOrEmpty(ext)) + targetPath += "." + ext; + } while (new Java.IO.File(internalDirectory, targetPath).Exists()); + } + return IOConnectionInfo.FromPath(new Java.IO.File(internalDirectory, targetPath).CanonicalPath); + } + + public static IOConnectionInfo ImportFileToInternalDirectory(IOConnectionInfo sourceIoc, Context ctx, IKp2aApp app) + { + var targetIoc = GetInternalIoc(sourceIoc, ctx, app); + + + IoUtil.Copy(targetIoc, sourceIoc, app); + return targetIoc; + } + + public static string GetIocPrefKey(IOConnectionInfo ioc, string suffix) + { + var iocAsHexString = IocAsHexString(ioc); + + return "kp2a_ioc_key_" + iocAsHexString + suffix; + } + + + public static string IocAsHexString(IOConnectionInfo ioc) + { + SHA256Managed sha256 = new SHA256Managed(); + string iocAsHexString = + MemUtil.ByteArrayToHexString(sha256.ComputeHash(Encoding.Unicode.GetBytes(ioc.Path.ToCharArray()))); + return iocAsHexString; + } + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/JavaFileStorage.cs b/src/Kp2aBusinessLogicSdkStyle/Io/JavaFileStorage.cs new file mode 100644 index 00000000..7fab70ad --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/JavaFileStorage.cs @@ -0,0 +1,375 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Android.App; +using Android.Content; +using Android.Content.PM; +using Android.OS; +using KeePassLib.Serialization; +using KeePassLib.Utility; +#if !EXCLUDE_JAVAFILESTORAGE +using Keepass2android.Javafilestorage; +#endif +using Exception = System.Exception; +using FileNotFoundException = Java.IO.FileNotFoundException; + +namespace keepass2android.Io +{ + #if !EXCLUDE_JAVAFILESTORAGE + public abstract class JavaFileStorage: IFileStorage, IPermissionRequestingFileStorage + { + protected string Protocol { get { return _jfs.ProtocolId; } } + + public virtual IEnumerable SupportedProtocols { get { yield return Protocol; } } + public abstract bool UserShouldBackup { get; } + + + protected readonly IJavaFileStorage _jfs; + private readonly IKp2aApp _app; + + public JavaFileStorage(IJavaFileStorage jfs, IKp2aApp app) + { + _jfs = jfs; + _app = app; + } + + public void Delete(IOConnectionInfo ioc) + { + try + { + Jfs.Delete(IocToPath(ioc)); + } + catch (FileNotFoundException e) + { + throw new System.IO.FileNotFoundException(e.Message, e); + } + catch (Java.Lang.Exception e) + { + throw LogAndConvertJavaException(e); + } + } + + public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion) + { + return false; + + //commented because this currently might use the network which is not permitted here + /*try + { + return Jfs.CheckForFileChangeFast(ioc.Path, previousFileVersion); + } + catch (Java.Lang.Exception e) + { + throw LogAndConvertJavaException(e); + }*/ + + } + + public string GetCurrentFileVersionFast(IOConnectionInfo ioc) + { + try + { + return Jfs.GetCurrentFileVersionFast(IocToPath(ioc)); + } + catch (Java.Lang.Exception e) + { + throw LogAndConvertJavaException(e); + } + } + + public Stream OpenFileForRead(IOConnectionInfo ioc) + { + try + { + return Jfs.OpenFileForRead(IocToPath(ioc)); + } + catch (FileNotFoundException e) + { + throw new System.IO.FileNotFoundException(e.Message, e); + } + catch (Java.Lang.Exception e) + { + throw LogAndConvertJavaException(e); + } + } + + + private Exception LogAndConvertJavaException(Exception e) + { + Kp2aLog.Log(e.Message); + + if (e is UserInteractionRequiredException) + return e; + //seems like UserInteractionRequiredException is not propagated correctly into the C# world, we can't catch it + // -> rethrow correctly here + // Note: the Contains-check looks a bit broad, but it should be safe + if (e.ToString().Contains("keepass2android.javafilestorage.UserInteractionRequiredException")) + { + throw new UserInteractionRequiredException(); + } + + Java.Lang.Exception exception = e as Java.Lang.Exception; + if (exception != null) + { + var ex = new Exception(exception.LocalizedMessage ?? + e.Message ?? + _app.GetResourceString(UiStringKey.ErrorOcurred) + exception.GetType().Name, e); + return ex; + } + + return e; + + } + + public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) + { + return new JavaFileStorageWriteTransaction(IocToPath(ioc), useFileTransaction, this); + } + + internal IJavaFileStorage Jfs + { + get { return _jfs; } + } + + + class JavaFileStorageWriteTransaction: IWriteTransaction + { + private readonly string _path; + private readonly bool _useFileTransaction; + private readonly JavaFileStorage _javaFileStorage; + private MemoryStream _memoryStream; + + public JavaFileStorageWriteTransaction(string path, bool useFileTransaction, JavaFileStorage javaFileStorage) + { + _path = path; + _useFileTransaction = useFileTransaction; + _javaFileStorage = javaFileStorage; + } + + public void Dispose() + { + _memoryStream.Dispose(); + } + + public Stream OpenFile() + { + _memoryStream = new MemoryStream(); + return _memoryStream; + } + + public void CommitWrite() + { + try + { + _javaFileStorage.Jfs.UploadFile(_path, _memoryStream.ToArray(), _useFileTransaction); + } + catch (Java.Lang.Exception e) + { + throw _javaFileStorage.LogAndConvertJavaException(e); + } + } + } + + public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc) + { + return UrlUtil.StripExtension( + _jfs.GetFilename(IocToPath(ioc))); + } + + public string GetFileExtension(IOConnectionInfo ioc) + { + return UrlUtil.GetExtension(ioc.Path); + } + + public bool RequiresCredentials(IOConnectionInfo ioc) + { + return false; + } + + public void CreateDirectory(IOConnectionInfo ioc, string newDirName) + { + try + { + Jfs.CreateFolder(IocToPath(ioc), newDirName); + } + catch (FileNotFoundException e) + { + throw new System.IO.FileNotFoundException(e.Message, e); + } + catch (Java.Lang.Exception e) + { + throw LogAndConvertJavaException(e); + } + } + + public IEnumerable ListContents(IOConnectionInfo ioc) + { + try + { + IList entries = Jfs.ListFiles(IocToPath(ioc)); + + return entries.Select(ConvertToFileDescription); + + } + catch (FileNotFoundException e) + { + throw new System.IO.FileNotFoundException(e.Message, e); + } + catch (Java.Lang.Exception e) + { + throw LogAndConvertJavaException(e); + } + } + + private FileDescription ConvertToFileDescription(IJavaFileStorage.FileEntry e) + { + return new FileDescription + { + CanRead = e.CanRead, + CanWrite = e.CanWrite, + DisplayName = e.DisplayName, + IsDirectory = e.IsDirectory, + LastModified = JavaTimeToCSharp(e.LastModifiedTime), + Path = e.Path, + SizeInBytes = e.SizeInBytes + }; + } + + public FileDescription GetFileDescription(IOConnectionInfo ioc) + { + try + { + return ConvertToFileDescription(Jfs.GetFileEntry(IocToPath(ioc))); + } + catch (FileNotFoundException e) + { + throw new System.IO.FileNotFoundException(e.Message, e); + } + catch (Java.Lang.Exception e) + { + throw LogAndConvertJavaException(e); + } + } + + public bool RequiresSetup(IOConnectionInfo ioConnection) + { + return _jfs.RequiresSetup(IocToPath(ioConnection)); + } + + public virtual void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId) + { + Kp2aLog.Log("StartSelectFile " + protocolId); + _jfs.StartSelectFile((IJavaFileStorage.IFileStorageSetupInitiatorActivity) activity, isForSave, requestCode); + } + + public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode, Boolean alwaysReturnSuccess) + { + try + { + _jfs.PrepareFileUsage((IJavaFileStorage.IFileStorageSetupInitiatorActivity) activity, IocToPath(ioc), requestCode, + alwaysReturnSuccess); + } + catch (Exception e) + { + throw LogAndConvertJavaException(e); + } + } + + public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc) + { + try + { + _jfs.PrepareFileUsage(ctx, IocToPath(ioc)); + } + catch (Exception e) + { + throw LogAndConvertJavaException(e); + } + + } + + public bool IsPermanentLocation(IOConnectionInfo ioc) + { + return true; + } + + public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null) + { + return false; //TODO implement. note, however, that we MAY return false even if it's read-only + } + + public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState) + { + _jfs.OnCreate(((IJavaFileStorage.IFileStorageSetupActivity)activity), savedInstanceState); + } + + public void OnResume(IFileStorageSetupActivity activity) + { +#if DEBUG + Kp2aLog.Log("JFS/OnResume Ioc.Path=" +activity.Ioc.Path+". Path="+((IJavaFileStorageFileStorageSetupActivity)activity).Path); +#endif + _jfs.OnResume(((IJavaFileStorage.IFileStorageSetupActivity) activity)); + } + + public void OnStart(IFileStorageSetupActivity activity) + { + _jfs.OnStart(((IJavaFileStorage.IFileStorageSetupActivity) activity)); + } + + public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data) + { + _jfs.OnActivityResult(((IJavaFileStorage.IFileStorageSetupActivity) activity), requestCode, resultCode, data); + } + + public string GetDisplayName(IOConnectionInfo ioc) + { + return _jfs.GetDisplayName(ioc.Path); + } + + public string CreateFilePath(string parent, string newFilename) + { + return _jfs.CreateFilePath(parent, newFilename); + } + + public IOConnectionInfo GetParentPath(IOConnectionInfo ioc) + { + return IoUtil.GetParentPath(ioc); + } + + public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename) + { + try + { + return IOConnectionInfo.FromPath( + ListContents(folderPath).Where(desc => { return desc.DisplayName == filename; }) + .Single() + .Path); + } + catch (Exception e) + { + throw new Exception("Error finding " + filename + " in " + GetDisplayName(folderPath), e); + } + + } + + private DateTime JavaTimeToCSharp(long javatime) + { + return new DateTime(1970, 1, 1).AddMilliseconds(javatime); + + } + + public virtual string IocToPath(IOConnectionInfo ioc) + { + return ioc.Path; + } + + public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode, + string[] permissions, Permission[] grantResults) + { + _jfs.OnRequestPermissionsResult(((IJavaFileStorage.IFileStorageSetupActivity) fileStorageSetupActivity), requestCode, + permissions, grantResults.Select(p => (int)p).ToArray()); + } + } +#endif + } \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/MegaFileStorage.cs b/src/Kp2aBusinessLogicSdkStyle/Io/MegaFileStorage.cs new file mode 100644 index 00000000..fb8cb862 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/MegaFileStorage.cs @@ -0,0 +1,506 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Android.Content; +using Android.OS; +using Android.Preferences; +using Android.Util; +using CG.Web.MegaApiClient; +using Group.Pals.Android.Lib.UI.Filechooser.Utils; +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Serialization; +using KeePassLib.Utility; + +namespace keepass2android.Io +{ + public class MegaFileStorage : IFileStorage + { + private readonly Context _appContext; + public const string ProtocolId = "mega"; + private const string PreferenceKey = "KP2A-Mega-Accounts"; + + public MegaFileStorage(Context appContext) + { + _appContext = appContext; + } + + //we don't want to store passwords in plain text, encrypt them with this key at least: + public static readonly byte[] EncryptionKey = new byte[] { 86,239,128,218,160,22,245,114,193,92,151,10,134,104,121,170, + 183,110,60,38,179,181,24,206,169,43,125,193,142,156,47,45}; + + public class AccountSettings + { + public Dictionary PasswordByUsername { get; set; } = new Dictionary(); + + public static byte[] exclusiveOR(byte[] arr1, byte[] arr2) + { + byte[] result = new byte[arr1.Length]; + + for (int i = 0; i < arr1.Length; ++i) + result[i] = (byte)(arr1[i] ^ arr2[i % arr2.Length]); + + return result; + } + + static string Encrypt(string s) + { + var plainTextBytes = exclusiveOR(System.Text.Encoding.UTF8.GetBytes(s), EncryptionKey); + return System.Convert.ToBase64String(plainTextBytes); + + } + + static string Decrypt(string s) + { + var base64EncodedBytes = System.Convert.FromBase64String(s); + return System.Text.Encoding.UTF8.GetString(exclusiveOR(base64EncodedBytes, EncryptionKey)); + + } + + public string Serialize() + { + Dictionary encryptedPasswordByUsername = PasswordByUsername + .Select(kvp => new KeyValuePair(kvp.Key, Encrypt(kvp.Value))) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + return Newtonsoft.Json.JsonConvert.SerializeObject(encryptedPasswordByUsername); + } + + public void Deserialize(string data) + { + if (string.IsNullOrEmpty(data)) + { + PasswordByUsername = new Dictionary(); + return; + } + Dictionary encryptedPasswordByUsername = + Newtonsoft.Json.JsonConvert.DeserializeObject>(data); + PasswordByUsername = encryptedPasswordByUsername + .Select(kvp => new KeyValuePair(kvp.Key, Decrypt(kvp.Value))) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + } + + public IEnumerable SupportedProtocols + { + get { yield return ProtocolId; } + } + + public bool UserShouldBackup + { + get { return false; } + } + + + class MegaFileStorageWriteTransaction : IWriteTransaction + { + public bool UseFileTransaction { get; } + private readonly string _path; + private readonly MegaFileStorage _filestorage; + private MemoryStream _memoryStream; + + public MegaFileStorageWriteTransaction(string path, MegaFileStorage filestorage, bool useFileTransaction) + { + UseFileTransaction = useFileTransaction; + _path = path; + _filestorage = filestorage; + } + + public void Dispose() + { + _memoryStream.Dispose(); + } + + public Stream OpenFile() + { + _memoryStream = new MemoryStream(); + return _memoryStream; + } + + public void CommitWrite() + { + _filestorage.UploadFile(_path, new MemoryStream(_memoryStream.ToArray()), UseFileTransaction); + + } + } + + private void UploadFile(string path, MemoryStream memoryStream, bool useTransaction) + { + var accountData = GetAccountData(path); + + if (accountData.TryGetNode(path, out var node)) + { + if (useTransaction) + { + string temporaryName = node.Name + "." + new Guid().ToString() + ".tmp"; + var newNode = accountData.Client.Upload(memoryStream, temporaryName, accountData.GetParentNode(node)); + accountData.Client.Delete(node); + newNode = accountData.Client.Rename(newNode, node.Name); + accountData._nodes.Remove(node); + accountData._nodes.Add(newNode); + } + else + { + var newNode = accountData.Client.Upload(memoryStream, node.Name, accountData.GetParentNode(node)); + //we now have two nodes with the same name. Delete the old one: + accountData.Client.Delete(node); + accountData._nodes.Remove(node); + accountData._nodes.Add(newNode); + + } + } + else + { + //file did not exist yet + string parentPath = GetParentPath(new IOConnectionInfo() { Path = path }).Path; + string name = path.Substring(parentPath.Length + 1); + var newNode = accountData.Client.Upload(memoryStream, name, accountData.GetNode(parentPath)); + accountData._nodes.Add(newNode); + } + + } + + public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, + string protocolId) + { + activity.PerformManualFileSelect(isForSave, requestCode, protocolId); + } + + public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState) + { + } + + public void OnResume(IFileStorageSetupActivity activity) + { + } + + public void OnStart(IFileStorageSetupActivity activity) + { + } + + public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data) + { + } + + public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) + { + return new MegaFileStorageWriteTransaction(ioc.Path, this, useFileTransaction); + } + + public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc) + { + return UrlUtil.StripExtension( + UrlUtil.GetFileName(ioc.Path)); + } + + public string GetFileExtension(IOConnectionInfo ioc) + { + return UrlUtil.GetExtension(ioc.Path); + } + + public string CreateFilePath(string parent, string newFilename) + { + if (!parent.EndsWith("/")) + parent += "/"; + return parent + newFilename; + } + + public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null) + { + return false; + } + + public bool IsPermanentLocation(IOConnectionInfo ioc) + { + return true; + } + + public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename) + { + IOConnectionInfo res = folderPath.CloneDeep(); + if (!res.Path.EndsWith("/")) + res.Path += "/"; + res.Path += filename; + return res; + } + + public IOConnectionInfo GetParentPath(IOConnectionInfo ioc) + { + return IoUtil.GetParentPath(ioc); + } + + public string GetDisplayName(IOConnectionInfo ioc) + { + return ioc.GetDisplayName(); + } + + public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc) + { + //nothing to do + } + + public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode, + bool alwaysReturnSuccess) + { + Intent intent = new Intent(); + activity.IocToIntent(intent, ioc); + activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileUsagePrepared, intent); + } + + public string IocToPath(IOConnectionInfo ioc) + { + return ioc.Path; + } + + public bool RequiresSetup(IOConnectionInfo ioConnection) + { + return false; + } + + public FileDescription GetFileDescription(IOConnectionInfo ioc) + { + var accountData = GetAccountData(ioc); + return MakeFileDescription(accountData, accountData.GetNode(ioc)); + } + + class AccountData + { + public string Account { get; set; } + public IMegaApiClient Client { get; set; } + + public void RefreshMetadata() + { + //make sure we refresh meta data after one minute: + if (DateTime.Now.Subtract(_nodesLoadingTime).TotalMinutes > 1.0) + { + _nodes.Clear(); + EnsureMetadataLoaded(); + } + } + + public List _nodes = new List(); + private DateTime _nodesLoadingTime; + private INode _rootNode; + + public INode GetNode(IOConnectionInfo ioc) + { + return GetNode(ioc.Path); + } + + public bool TryGetNode(string path, out INode node) + { + try + { + node = GetNode(path); + return true; + } + catch (Exception e) + { + node = null; + return false; + } + } + + public INode GetNode(string path) + { + EnsureMetadataLoaded(); + if (!path.StartsWith(ProtocolId + "://")) + throw new Exception("Invalid Mega URL: " + path); + path = path.Substring(ProtocolId.Length + 3); + var parts = path.Split('/'); + if (parts.Length < 1 || parts[0] == "") + throw new Exception("Invalid Mega URL: " + path); + + INode node = _rootNode; + for (int i = 1; i < parts.Length; i++) + { + if (parts[i] == "") + continue; + var matchingChildren = _nodes.Where(n => n.ParentId == node.Id && n.Name == parts[i]).ToList(); + if (matchingChildren.Count == 0) + throw new FileNotFoundException("Did not find " + path); + if (matchingChildren.Count > 1) + throw new Java.IO.FileNotFoundException( + $"Found more than one child with name {parts[i]} while trying to get node for {path}"); + node = matchingChildren.Single(); + } + + return node; + + } + + private void EnsureMetadataLoaded() + { + if (_nodes.Any() == false) + { + _nodes = Client.GetNodes().ToList(); + + _rootNode = _nodes.Single(n => n.Type == NodeType.Root); + _nodesLoadingTime = DateTime.Now; + } + } + + public INode GetParentNode(INode node) + { + return _nodes.Single(n => n.Id == node.ParentId); + } + + internal void InvalidateMetaData() + { + _nodes.Clear(); + } + + public IEnumerable GetChildNodes(INode node) + { + EnsureMetadataLoaded(); + return _nodes.Where(n => n.ParentId == node.Id); + } + + public string GetPath(INode node) + { + if (node.Type == NodeType.Root) + return ProtocolId + "://" + this.Account; + var parent = _nodes.Single(n => n.Id == node.ParentId); + return GetPath(parent) + "/" + node.Name; + } + } + + + readonly Dictionary _allAccountData = new Dictionary(); + + public string GetAccount(IOConnectionInfo ioc) + { + return GetAccount(ioc.Path); + } + + public static string GetAccount(string path) + { + if (!path.StartsWith(ProtocolId + "://")) + throw new Exception("Invalid Mega URL: " + path); + path = path.Substring(ProtocolId.Length + 3); + var parts = path.Split('/'); + if (parts.Length < 1 || parts[0] == "") + throw new Exception("Invalid Mega URL: " + path); + return parts[0]; + + + } + + private AccountData GetAccountData(IOConnectionInfo ioc) + { + return GetAccountData(ioc.Path); + } + + public static AccountSettings GetAccountSettings(Context ctx) + { + string accountSettingsString = PreferenceManager.GetDefaultSharedPreferences(ctx).GetString(PreferenceKey, null); + AccountSettings settings = new AccountSettings(); + settings.Deserialize(accountSettingsString); + return settings; + } + + public static void UpdateAccountSettings(AccountSettings settings, Context ctx) + { + PreferenceManager.GetDefaultSharedPreferences(ctx).Edit().PutString(PreferenceKey, settings.Serialize()) + .Commit(); + } + + private AccountData GetAccountData(string path) + { + string account = GetAccount(path); + if (_allAccountData.TryGetValue(account, out var accountData)) + { + return accountData; + } + + AccountData newAccountData = new AccountData() + { + Account = account, + Client = new MegaApiClient() + }; + + var settings = GetAccountSettings(_appContext); + if (!settings.PasswordByUsername.TryGetValue(account, out string password)) + { + throw new Exception("No account configured with username = " + account); + } + + try + { + newAccountData.Client.Login(account, password); + } + catch (CG.Web.MegaApiClient.ApiException e) + { + if (e.ApiResultCode == CG.Web.MegaApiClient.ApiResultCode.ResourceNotExists) + { + throw new Exception("Failed to login to MEGA account. Please check username and password!"); + } + + } + + + _allAccountData[account] = newAccountData; + return newAccountData; + + } + + public IEnumerable ListContents(IOConnectionInfo ioc) + { + AccountData accountData = GetAccountData(ioc); + accountData.RefreshMetadata(); + return accountData.GetChildNodes(accountData.GetNode(ioc)).Select(n => MakeFileDescription(accountData, n)); + + + } + + private FileDescription MakeFileDescription(AccountData account, INode n) + { + return new FileDescription() + { + CanRead = true, + CanWrite = true, + DisplayName = n.Name ?? (n.Type == NodeType.Root ? "root" : ""), + IsDirectory = n.Type != NodeType.File, + LastModified = n.ModificationDate ?? n.CreationDate ?? DateTime.MinValue, + Path = account.GetPath(n), + SizeInBytes = n.Size + }; + } + + public void CreateDirectory(IOConnectionInfo ioc, string newDirName) + { + var accountData = GetAccountData(ioc); + var newNode = accountData.Client.CreateFolder(newDirName, accountData.GetNode(ioc)); + accountData._nodes.Add(newNode); + } + + public bool RequiresCredentials(IOConnectionInfo ioc) + { + return false; + } + + + public Stream OpenFileForRead(IOConnectionInfo ioc) + { + var accountData = GetAccountData(ioc); + return accountData.Client.Download(accountData.GetNode(ioc)); + } + + public string GetCurrentFileVersionFast(IOConnectionInfo ioc) + { + return null; + } + + public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion) + { + return false; + } + + public void Delete(IOConnectionInfo ioc) + { + var accountData = GetAccountData(ioc); + accountData.Client.Delete(accountData.GetNode(ioc)); + } + + + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/NetFtpFileStorage.cs b/src/Kp2aBusinessLogicSdkStyle/Io/NetFtpFileStorage.cs new file mode 100644 index 00000000..06a2b115 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/NetFtpFileStorage.cs @@ -0,0 +1,612 @@ +#if !NoNet +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using Android.Content; +using Android.OS; +using FluentFTP; +using FluentFTP.Exceptions; +using KeePassLib; +using KeePassLib.Serialization; +using KeePassLib.Utility; + + +namespace keepass2android.Io +{ + public class NetFtpFileStorage: IFileStorage + { + public struct ConnectionSettings + { + public FtpEncryptionMode EncryptionMode {get; set; } + + public string Username + { + get;set; + } + public string Password + { + get; + set; + } + + public static ConnectionSettings FromIoc(IOConnectionInfo ioc) + { + if (!string.IsNullOrEmpty(ioc.UserName)) + { + //legacy support + return new ConnectionSettings() + { + EncryptionMode = FtpEncryptionMode.None, + Username = ioc.UserName, + Password = ioc.Password + }; + } + + string path = ioc.Path; + int schemeLength = path.IndexOf("://", StringComparison.Ordinal); + path = path.Substring(schemeLength + 3); + string settings = path.Substring(0, path.IndexOf(SettingsPostFix, StringComparison.Ordinal)); + if (!settings.StartsWith(SettingsPrefix)) + throw new Exception("unexpected settings in path"); + settings = settings.Substring(SettingsPrefix.Length); + var tokens = settings.Split(Separator); + return new ConnectionSettings() + { + EncryptionMode = (FtpEncryptionMode) int.Parse(tokens[2]), + Username = WebUtility.UrlDecode(tokens[0]), + Password = WebUtility.UrlDecode(tokens[1]) + }; + + } + + public const string SettingsPrefix = "SET"; + public const string SettingsPostFix = "#"; + public const char Separator = ':'; + public override string ToString() + { + return SettingsPrefix + + System.Net.WebUtility.UrlEncode(Username) + Separator + + WebUtility.UrlEncode(Password) + Separator + + (int) EncryptionMode; + ; + } + } + + private readonly ICertificateValidationHandler _app; + private readonly Func _debugLogPrefGetter; + + public MemoryStream traceStream; + + public NetFtpFileStorage(Context context, ICertificateValidationHandler app, Func debugLogPrefGetter) + { + _app = app; + _debugLogPrefGetter = debugLogPrefGetter; + traceStream = new MemoryStream(); + } + + public IEnumerable SupportedProtocols + { + get + { + yield return "ftp"; + } + } + + public bool UserShouldBackup + { + get { return true; } + } + + public void Delete(IOConnectionInfo ioc) + { + try + { + using (FtpClient client = GetClient(ioc)) + { + string localPath = IocToLocalPath(ioc); + if (client.DirectoryExists(localPath)) + client.DeleteDirectory(localPath); + else + client.DeleteFile(localPath); + } + + } + catch (FtpCommandException ex) + { + throw ConvertException(ex); + } + + } + + public static Exception ConvertException(Exception exception) + { + if (exception is FtpCommandException) + { + var ftpEx = (FtpCommandException) exception; + + if (ftpEx.CompletionCode == "550") + throw new FileNotFoundException(exception.Message, exception); + } + + return exception; + } + + + internal FtpClient GetClient(IOConnectionInfo ioc, bool enableCloneClient = true) + { + var settings = ConnectionSettings.FromIoc(ioc); + + FtpClient client = new FtpClient(); + client.Config.RetryAttempts = 3; + if ((settings.Username.Length > 0) || (settings.Password.Length > 0)) + client.Credentials = new NetworkCredential(settings.Username, settings.Password); + else + client.Credentials = new NetworkCredential("anonymous", ""); //TODO TEST + + Uri uri = IocToUri(ioc); + client.Host = uri.Host; + if (!uri.IsDefaultPort) //TODO test + client.Port = uri.Port; + + client.ValidateCertificate += (control, args) => + { + args.Accept = _app.CertificateValidationCallback(control, args.Certificate, args.Chain, args.PolicyErrors); + }; + + client.Config.EncryptionMode = settings.EncryptionMode; + + if (_debugLogPrefGetter()) + client.Logger = new Kp2aLogFTPLogger(); + + client.Connect(); + return client; + + } + + + + + public static Uri IocToUri(IOConnectionInfo ioc) + { + if (!string.IsNullOrEmpty(ioc.UserName)) + { + //legacy support. + return new Uri(ioc.Path); + } + string path = ioc.Path; + //remove additional stuff like TLS param + int schemeLength = path.IndexOf("://", StringComparison.Ordinal); + string scheme = path.Substring(0, schemeLength); + path = path.Substring(schemeLength + 3); + if (path.StartsWith(ConnectionSettings.SettingsPrefix)) + { + //this should always be the case. However, in rare cases we might get an ioc with legacy path but no username set (if they only want to get a display name) + string settings = path.Substring(0, path.IndexOf(ConnectionSettings.SettingsPostFix, StringComparison.Ordinal)); + path = path.Substring(settings.Length + 1); + + } + Kp2aLog.Log("FTP: IocToUri out = " + scheme + "://" + path); + return new Uri(scheme + "://" + path); + } + + private string IocPathFromUri(IOConnectionInfo baseIoc, string uri) + { + string basePath = baseIoc.Path; + int schemeLength = basePath.IndexOf("://", StringComparison.Ordinal); + string scheme = basePath.Substring(0, schemeLength); + basePath = basePath.Substring(schemeLength + 3); + string baseSettings = basePath.Substring(0, basePath.IndexOf(ConnectionSettings.SettingsPostFix, StringComparison.Ordinal)); + basePath = basePath.Substring(baseSettings.Length+1); + string baseHost = basePath.Substring(0, basePath.IndexOf("/", StringComparison.Ordinal)); + string result = scheme + "://" + baseSettings + ConnectionSettings.SettingsPostFix + baseHost + uri; //TODO does this contain Query? + return result; + } + + + public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion) + { + return false; + } + + public string GetCurrentFileVersionFast(IOConnectionInfo ioc) + { + return null; + } + + public Stream OpenFileForRead(IOConnectionInfo ioc) + { + try + { + using (var cl = GetClient(ioc)) + { + var memStream = new MemoryStream(); + cl.OpenRead(IocToLocalPath(ioc), FtpDataType.Binary, 0).CopyTo(memStream); + memStream.Seek(0, SeekOrigin.Begin); + return memStream; + } + } + catch (FtpCommandException ex) + { + throw ConvertException(ex); + } + } + + public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) + { + try + { + + + if (!useFileTransaction) + return new UntransactedWrite(ioc, this); + else + return new TransactedWrite(ioc, this); + } + catch (FtpCommandException ex) + { + throw ConvertException(ex); + } + } + + public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc) + { + return UrlUtil.StripExtension( + UrlUtil.GetFileName(ioc.Path)); + } + + public string GetFileExtension(IOConnectionInfo ioc) + { + return UrlUtil.GetExtension(ioc.Path); + } + + public bool RequiresCredentials(IOConnectionInfo ioc) + { + return false; + } + + public void CreateDirectory(IOConnectionInfo ioc, string newDirName) + { + try + { + using (var client = GetClient(ioc)) + { + client.CreateDirectory(IocToLocalPath(GetFilePath(ioc, newDirName))); + } + } + catch (FtpCommandException ex) + { + throw ConvertException(ex); + } + } + + public static string IocToLocalPath(IOConnectionInfo ioc) + { + return WebUtility.UrlDecode(IocToUri(ioc).PathAndQuery); + } + + public IEnumerable ListContents(IOConnectionInfo ioc) + { + try + { + using (var client = GetClient(ioc)) + { + /* + * For some reason GetListing(path) does not always return the contents of the directory. + * However, calling SetWorkingDirectory(path) followed by GetListing(null, options) to + * list the contents of the working directory does consistently work. + * + * Similar behavior was confirmed using ncftp client. I suspect this is a strange + * bug/nuance in the server's implementation of the LIST command? + * + * [bug #2423] + */ + client.SetWorkingDirectory(IocToLocalPath(ioc)); + + List files = new List(); + foreach (FtpListItem item in client.GetListing(null, + FtpListOption.SizeModify | FtpListOption.AllFiles)) + { + switch (item.Type) + { + case FtpObjectType.Directory: + files.Add(new FileDescription() + { + CanRead = true, + CanWrite = true, + DisplayName = item.Name, + IsDirectory = true, + LastModified = item.Modified, + Path = IocPathFromUri(ioc, item.FullName) + }); + break; + case FtpObjectType.File: + files.Add(new FileDescription() + { + CanRead = true, + CanWrite = true, + DisplayName = item.Name, + IsDirectory = false, + LastModified = item.Modified, + Path = IocPathFromUri(ioc, item.FullName), + SizeInBytes = item.Size + }); + break; + default: + Kp2aLog.Log("FTP: ListContents item skipped: " + IocToUri(ioc) + ": " + item.FullName + ", type=" + item.Type); + break; + } + } + return files; + } + } + catch (FtpCommandException ex) + { + throw ConvertException(ex); + } + } + + public FileDescription GetFileDescription(IOConnectionInfo ioc) + { + try + { + //TODO when is this called? + //is it very inefficient to connect for each description? + + using (FtpClient client = GetClient(ioc)) + { + + + string path = IocToLocalPath(ioc); + if (!client.FileExists(path) && (!client.DirectoryExists(path))) + throw new FileNotFoundException(); + var fileDesc = new FileDescription() + { + CanRead = true, + CanWrite = true, + Path = ioc.Path, + LastModified = client.GetModifiedTime(path), + SizeInBytes = client.GetFileSize(path), + DisplayName = UrlUtil.GetFileName(path) + }; + fileDesc.IsDirectory = fileDesc.Path.EndsWith("/"); + return fileDesc; + } + } + catch (FtpCommandException ex) + { + throw ConvertException(ex); + } + } + + public bool RequiresSetup(IOConnectionInfo ioConnection) + { + return false; + } + + public string IocToPath(IOConnectionInfo ioc) + { + return ioc.Path; + } + + public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId) + { + activity.PerformManualFileSelect(isForSave, requestCode, "ftp"); + } + + public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode, + bool alwaysReturnSuccess) + { + Intent intent = new Intent(); + activity.IocToIntent(intent, ioc); + activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileUsagePrepared, intent); + } + + public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc) + { + + } + + public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState) + { + + } + + public void OnResume(IFileStorageSetupActivity activity) + { + + } + + public void OnStart(IFileStorageSetupActivity activity) + { + + } + + public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data) + { + + } + + public string GetDisplayName(IOConnectionInfo ioc) + { + var uri = IocToUri(ioc); + return uri.ToString(); //TODO is this good? + } + + public string CreateFilePath(string parent, string newFilename) + { + if (!parent.EndsWith("/")) + parent += "/"; + return parent + newFilename; + } + + public IOConnectionInfo GetParentPath(IOConnectionInfo ioc) + { + return IoUtil.GetParentPath(ioc); + } + + public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename) + { + IOConnectionInfo res = folderPath.CloneDeep(); + if (!res.Path.EndsWith("/")) + res.Path += "/"; + res.Path += filename; + return res; + } + + public bool IsPermanentLocation(IOConnectionInfo ioc) + { + return true; + } + + public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null) + { + return false; + } + public Stream OpenWrite(IOConnectionInfo ioc) + { + try + { + using (var client = GetClient(ioc)) + { + return client.OpenWrite(IocToLocalPath(ioc)); + + } + } + catch (FtpCommandException ex) + { + throw ConvertException(ex); + } + } + + public static int GetDefaultPort(FtpEncryptionMode encryption) + { + var client = new FtpClient(); + client.Config.EncryptionMode = encryption; + return client.Port; + } + + public string BuildFullPath(string host, int port, string initialPath, string user, string password, FtpEncryptionMode encryption) + { + var connectionSettings = new ConnectionSettings() + { + EncryptionMode = encryption, + Username = user, + Password = password + }; + + string scheme = "ftp"; + + string fullPath = scheme + "://" + connectionSettings.ToString() + ConnectionSettings.SettingsPostFix + host; + if (port != GetDefaultPort(encryption)) + fullPath += ":" + port; + + if (!initialPath.StartsWith("/")) + initialPath = "/" + initialPath; + fullPath += initialPath; + + return fullPath; + } + + } + + public class TransactedWrite : IWriteTransaction + { + private readonly IOConnectionInfo _ioc; + private readonly NetFtpFileStorage _fileStorage; + private readonly IOConnectionInfo _iocTemp; + private FtpClient _client; + private Stream _stream; + + public TransactedWrite(IOConnectionInfo ioc, NetFtpFileStorage fileStorage) + { + _ioc = ioc; + _iocTemp = _ioc.CloneDeep(); + _iocTemp.Path += "." + new PwUuid(true).ToHexString().Substring(0, 6) + ".tmp"; + + _fileStorage = fileStorage; + } + + public void Dispose() + { + if (_stream != null) + _stream.Dispose(); + _stream = null; + } + + public Stream OpenFile() + { + try + { + + _client = _fileStorage.GetClient(_ioc, false); + _stream = _client.OpenWrite(NetFtpFileStorage.IocToLocalPath(_iocTemp)); + return _stream; + } + catch (FtpCommandException ex) + { + throw NetFtpFileStorage.ConvertException(ex); + } + } + + public void CommitWrite() + { + try + { + Android.Util.Log.Debug("NETFTP","connected: " + _client.IsConnected.ToString()); + _stream.Close(); + _stream.Dispose(); + _client.GetReply(); + + _client.MoveFile(NetFtpFileStorage.IocToLocalPath(_iocTemp), + NetFtpFileStorage.IocToLocalPath(_ioc)); + + } + catch (FtpCommandException ex) + { + throw NetFtpFileStorage.ConvertException(ex); + } + } + } + + public class UntransactedWrite : IWriteTransaction + { + private readonly IOConnectionInfo _ioc; + private readonly NetFtpFileStorage _fileStorage; + private Stream _stream; + + public UntransactedWrite(IOConnectionInfo ioc, NetFtpFileStorage fileStorage) + { + _ioc = ioc; + _fileStorage = fileStorage; + } + + public void Dispose() + { + if (_stream != null) + _stream.Dispose(); + _stream = null; + } + + public Stream OpenFile() + { + _stream = _fileStorage.OpenWrite(_ioc); + return _stream; + } + + public void CommitWrite() + { + _stream.Close(); + } + } + + class Kp2aLogFTPLogger : IFtpLogger + { + public void Log(FtpLogEntry entry) + { + Kp2aLog.Log("[FluentFTP] " + entry.Message); + } + } +} +#endif \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/OfflineSwitchableFileStorage.cs b/src/Kp2aBusinessLogicSdkStyle/Io/OfflineSwitchableFileStorage.cs new file mode 100644 index 00000000..4d82ad26 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/OfflineSwitchableFileStorage.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Android.Content; +using Android.Content.PM; +using Android.OS; +using KeePassLib.Serialization; + +namespace keepass2android.Io +{ + public interface IOfflineSwitchable + { + bool IsOffline { get; set; } + } + +/// + /// Encapsulates another IFileStorage. Allows to switch to offline mode by throwing + /// an exception when trying to read or write a file. + /// + public class OfflineSwitchableFileStorage : IFileStorage, IOfflineSwitchable, IPermissionRequestingFileStorage + { + private readonly IFileStorage _baseStorage; + public bool IsOffline { get; set; } + + public OfflineSwitchableFileStorage(IFileStorage baseStorage) + { + _baseStorage = baseStorage; + } + + public IEnumerable SupportedProtocols + { + get { return _baseStorage.SupportedProtocols; } + } + + public bool UserShouldBackup + { + get { return _baseStorage.UserShouldBackup; } + } + + public void Delete(IOConnectionInfo ioc) + { + _baseStorage.Delete(ioc); + } + + public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion) + { + return _baseStorage.CheckForFileChangeFast(ioc, previousFileVersion); + } + + public string GetCurrentFileVersionFast(IOConnectionInfo ioc) + { + return _baseStorage.GetCurrentFileVersionFast(ioc); + } + + public Stream OpenFileForRead(IOConnectionInfo ioc) + { + AssertOnline(); + return _baseStorage.OpenFileForRead(ioc); + } + + private void AssertOnline() + { + if (IsOffline) + { + //throw new Exception(_app.GetResourceString(UiStringKey.InOfflineMode)); + throw new OfflineModeException(); + } + } + + public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) + { + AssertOnline(); + return _baseStorage.OpenWriteTransaction(ioc, useFileTransaction); + } + + public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc) + { + return _baseStorage.GetFilenameWithoutPathAndExt(ioc); + } + + public string GetFileExtension(IOConnectionInfo ioc) + { + return _baseStorage.GetFileExtension(ioc); + } + + public bool RequiresCredentials(IOConnectionInfo ioc) + { + return _baseStorage.RequiresCredentials(ioc); + } + + public void CreateDirectory(IOConnectionInfo ioc, string newDirName) + { + _baseStorage.CreateDirectory(ioc, newDirName); + } + + public IEnumerable ListContents(IOConnectionInfo ioc) + { + return _baseStorage.ListContents(ioc); + } + + public FileDescription GetFileDescription(IOConnectionInfo ioc) + { + return _baseStorage.GetFileDescription(ioc); + } + + public bool RequiresSetup(IOConnectionInfo ioConnection) + { + if (IsOffline) + return false; + return _baseStorage.RequiresSetup(ioConnection); + } + + public string IocToPath(IOConnectionInfo ioc) + { + return _baseStorage.IocToPath(ioc); + } + + public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId) + { + _baseStorage.StartSelectFile(activity, isForSave, requestCode, protocolId); + } + + public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode, + bool alwaysReturnSuccess) + { + if (IsOffline) + { + Intent intent = new Intent(); + activity.IocToIntent(intent, ioc); + activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileUsagePrepared, intent); + return; + } + + _baseStorage.PrepareFileUsage(activity, ioc, requestCode, alwaysReturnSuccess); + } + + public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc) + { + if (IsOffline) + return; + _baseStorage.PrepareFileUsage(ctx, ioc); + } + + public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState) + { + _baseStorage.OnCreate(activity, savedInstanceState); + } + + public void OnResume(IFileStorageSetupActivity activity) + { + _baseStorage.OnResume(activity); + } + + public void OnStart(IFileStorageSetupActivity activity) + { + _baseStorage.OnStart(activity); + } + + public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data) + { + _baseStorage.OnActivityResult(activity, requestCode, resultCode, data); + } + + public string GetDisplayName(IOConnectionInfo ioc) + { + return _baseStorage.GetDisplayName(ioc); + } + + public string CreateFilePath(string parent, string newFilename) + { + return _baseStorage.CreateFilePath(parent, newFilename); + } + + public IOConnectionInfo GetParentPath(IOConnectionInfo ioc) + { + return _baseStorage.GetParentPath(ioc); + } + + public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename) + { + return _baseStorage.GetFilePath(folderPath, filename); + } + + public bool IsPermanentLocation(IOConnectionInfo ioc) + { + return _baseStorage.IsPermanentLocation(ioc); + } + + public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null) + { + return _baseStorage.IsReadOnly(ioc, reason); + } + + public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode, + string[] permissions, Permission[] grantResults) + { + if (_baseStorage is IPermissionRequestingFileStorage) + { + ((IPermissionRequestingFileStorage)_baseStorage).OnRequestPermissionsResult(fileStorageSetupActivity, requestCode, permissions, grantResults); + } + } + } + + public class OfflineModeException : Exception + { + public override string Message + { + get { return "Working offline."; } + } + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/OneDrive2FileStorage.cs b/src/Kp2aBusinessLogicSdkStyle/Io/OneDrive2FileStorage.cs new file mode 100644 index 00000000..b57a62e7 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/OneDrive2FileStorage.cs @@ -0,0 +1,1216 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Android.App; +using Android.Content; +using Android.OS; +using Android.Util; +using Java.Net; +using keepass2android.Io.ItemLocation; +using KeePassLib.Serialization; +using KeePassLib.Utility; +using Microsoft.Graph; +using Microsoft.Graph.Auth; +using Microsoft.Identity.Client; +using Newtonsoft.Json; +using Exception = System.Exception; +using File = Microsoft.Graph.File; +using String = System.String; + +namespace keepass2android.Io +{ + namespace ItemLocation + { + public class User + { + public string Name { get; set; } + public string Id { get; set; } + } + public class Share + { + public string Name { get; set; } + public string Id { get; set; } + public string WebUrl { get; set; } + } + + public class Item + { + public string Name { get; set; } + public string Id { get; set; } + + } + + public class OneDrive2ItemLocation where OneDrive2PrefixContainerType: OneDrive2PrefixContainer, new() + { + + public User User { get; set; } = new User(); + public Share Share { get; set; } = new Share(); + public string DriveId { get; set; } + + public List LocalPath { get; set; } = new List(); + public string LocalPathString { get { return string.Join("/", LocalPath.Select(i => i.Name)); } } + + public OneDrive2ItemLocation Parent + { + get + { + OneDrive2ItemLocation< OneDrive2PrefixContainerType> copy = OneDrive2ItemLocation< OneDrive2PrefixContainerType>.FromString(this.ToString()); + if (copy.LocalPath.Any()) + { + //pop last: + copy.LocalPath.RemoveAt(copy.LocalPath.Count - 1); + } + else if (copy.Share.Id != null) + { + copy.Share = new Share(); + } + else copy.User = new User(); + return copy; + } + } + + public override string ToString() + { + string path = (new OneDrive2PrefixContainerType()).Onedrive2Prefix + string.Join("\\", (new List { User.Id, User.Name, + Share.Id, Share.Name,Share.WebUrl, + string.Join("/", LocalPath.Select(i => Encode(i.Id)+":"+Encode(i.Name))), + DriveId + }).Select(Encode)); + path += "?" + path.Length; + return path; + } + + private string Encode(string s) + { + return WebUtility.UrlEncode(s); + } + + public static OneDrive2ItemLocation FromString(string p) + { + if ((p == null) || (p == (new OneDrive2PrefixContainerType()).Onedrive2Prefix)) + return new OneDrive2ItemLocation(); + + if (!p.StartsWith((new OneDrive2PrefixContainerType()).Onedrive2Prefix)) + throw new Exception("path not starting with prefix!"); + if (!p.Contains("?")) + throw new Exception("not found postfix"); + var lengthParts = p.Split("?"); + p = lengthParts[0]; + if (int.Parse(lengthParts[1]) != p.Length) + throw new Exception("Invalid length postfix in " + p); + + p = p.Substring((new OneDrive2PrefixContainerType()).Onedrive2Prefix.Length); + if (p == "") + return new OneDrive2ItemLocation(); + OneDrive2ItemLocation result = new OneDrive2ItemLocation(); + var parts = p.Split("\\"); + if (parts.Length != 7) + { + throw new Exception("Wrong number of parts in path " + p + " (" + parts.Length + ")"); + } + result.User.Id = Decode(parts[0]); + result.User.Name = Decode(parts[1]); + result.Share.Id = Decode(parts[2]); + result.Share.Name = Decode(parts[3]); + result.Share.WebUrl = Decode(parts[4]); + string localPath = Decode(parts[5]); + if (localPath != "") + { + var localPathParts = localPath.Split("/"); + foreach (var lpp in localPathParts) + { + var lppsubParts = lpp.Split(":"); + if (lppsubParts.Length != 2) + throw new Exception("Wrong number of subparts in in path " + p + ", " + lppsubParts); + result.LocalPath.Add(new Item { Id = Decode(lppsubParts[0]), Name = Decode(lppsubParts[1]) }); + } + } + result.DriveId = Decode(parts[6]); + + return result; + } + + private static string Decode(string p0) + { + return WebUtility.UrlDecode(p0); + } + + public OneDrive2ItemLocation BuildLocalChildLocation(string name, string id, string parentReferenceDriveId) + { + //copy this: + OneDrive2ItemLocation copy = OneDrive2ItemLocation.FromString(this.ToString()); + copy.LocalPath.Add(new Item { Name = name, Id = id }); + copy.DriveId = parentReferenceDriveId; + return copy; + } + + public static OneDrive2ItemLocation RootForUser(string accountUsername, string accountHomeAccountId) + { + OneDrive2ItemLocation loc = new OneDrive2ItemLocation + { + User = + { + Id = accountHomeAccountId, + Name = accountUsername + } + }; + + return loc; + } + + public OneDrive2ItemLocation BuildShare(string id, string name, string webUrl, string driveId) + { + OneDrive2ItemLocation copy = OneDrive2ItemLocation.FromString(this.ToString()); + copy.Share.Id = id; + copy.Share.Name = name; + copy.Share.WebUrl = webUrl; + copy.DriveId = driveId; + + return copy; + } + + + } + } + + + + public abstract class OneDrive2FileStorage : IFileStorage where OneDrive2PrefixContainerType: OneDrive2PrefixContainer, new() + { + + public static IPublicClientApplication _publicClientApp = null; + private string ClientID = "8374f801-0f55-407d-80cc-9a04fe86d9b2"; + + + public abstract IEnumerable Scopes + { + get; + } + + public OneDrive2FileStorage() + { + _publicClientApp = PublicClientApplicationBuilder.Create(ClientID) + .WithRedirectUri($"msal{ClientID}://auth") + .Build(); + } + + class PathItemBuilder + { + private readonly string _specialFolder; + public IGraphServiceClient client; + public OneDrive2ItemLocation itemLocation; + public bool verbose; + + public PathItemBuilder(string specialFolder) + { + _specialFolder = specialFolder; + } + + + public IDriveItemRequestBuilder getPathItem() + { + Kp2aLog.Log("getPathItem for " + itemLocation.ToString()); + IDriveItemRequestBuilder pathItem; + if (!hasShare()) + { + throw new Exception("Cannot get path item without share"); + } + if ("me".Equals(itemLocation.Share.Id)) + { + if (verbose) Kp2aLog.Log("Path share is me"); + + + if (_specialFolder == null) + { + if (verbose) Kp2aLog.Log("No special folder. Use drive root."); + pathItem = client.Me.Drive.Root; + } + else + { + if (verbose) Kp2aLog.Log("Special folder = " + _specialFolder); + pathItem = client.Me.Drive.Special[_specialFolder]; + } + + if (itemLocation.LocalPath.Any()) + { + if (verbose) Kp2aLog.Log("LocalPath = " + itemLocation.LocalPathString); + pathItem = pathItem.ItemWithPath(itemLocation.LocalPathString); + } + } + else + { + if (verbose) Kp2aLog.Log("Path share is not me"); + if (!itemLocation.LocalPath.Any()) + { + String webUrl = itemLocation.Share.WebUrl; + if (verbose) Kp2aLog.Log("Share WebUrl = " + webUrl); + var encodedShareId = CalculateEncodedShareId(webUrl); + return client.Shares[encodedShareId].Root; + } + /*String webUrl = itemLocation.Share.WebUrl; + if ("".Equals(itemLocation.LocalPath) == false) + { + if (!webUrl.EndsWith("/")) webUrl += "/"; + webUrl += itemLocation.LocalPath; + } + Android.Util.Log.Debug("KP2A","webUrl = " + Encoding.UTF8.GetBytes(webUrl)); + //calculate shareid according to https://docs.microsoft.com/en-us/graph/api/shares-get?view=graph-rest-1.0&tabs=java + var encodedShareId = CalculateEncodedShareId(webUrl); + Android.Util.Log.Debug("KP2A", "encodedShareId = " + encodedShareId); + pathItem = client.Shares[encodedShareId].Root; + */ + if (verbose) Kp2aLog.Log("Using driveId=" + itemLocation.DriveId + " and item id=" + itemLocation.LocalPath.Last().Id); + return client.Drives[itemLocation.DriveId].Items[itemLocation.LocalPath.Last().Id]; + } + + + return pathItem; + } + + private static string CalculateEncodedShareId(string webUrl) + { + String encodedShareId = "u!" + Base64.EncodeToString(Encoding.UTF8.GetBytes(webUrl), + Base64Flags.NoPadding).Replace('/', '_').Replace('+', '_') + .Replace("\n", ""); //encodeToString adds a newline character add the end - remove + return encodedShareId; + } + + public bool hasShare() + { + return !string.IsNullOrEmpty(itemLocation?.Share?.Id); + } + + public bool hasOneDrivePath() + { + return itemLocation.LocalPath.Any(); + } + } + + private string protocolId; + + protected string ProtocolId + { + get + { + if (protocolId == null) + { + protocolId = (new OneDrive2PrefixContainerType()).Onedrive2ProtocolId; + } + return protocolId; + } + } + + public IEnumerable SupportedProtocols + { + get { yield return ProtocolId; } + } + + class GraphServiceClientWithState + { + public IGraphServiceClient Client { get; set; } + public DateTime TokenExpiryDate { get; set; } + public bool RequiresUserInteraction { get; set; } + } + + readonly Dictionary mClientByUser = + new Dictionary(); + + private async Task TryGetMsGraphClient(String path, bool tryConnect) + { + + String userId = OneDrive2ItemLocation.FromString(path).User.Id; + + logDebug("TryGetMsGraphClient for " + userId); + if (mClientByUser.ContainsKey(userId)) + { + logDebug("TryGetMsGraphClient found user " + userId); + GraphServiceClientWithState clientWithState = mClientByUser[userId]; + if (!(clientWithState.RequiresUserInteraction || (clientWithState.TokenExpiryDate < DateTime.Now) || + (clientWithState.Client == null))) + { + logDebug("TryGetMsGraphClient returning client"); + return clientWithState.Client; + } + else + { + logDebug("not returning client because " + clientWithState.RequiresUserInteraction + " " + + (clientWithState.TokenExpiryDate < DateTime.Now) + " " + (clientWithState.Client == null)); + } + } + if (tryConnect) + { + logDebug("trying to connect..."); + if (await TryLoginSilent(path) != null) + { + logDebug("trying to connect ok"); + return mClientByUser[userId].Client; + } + logDebug("trying to connect failed"); + } + logDebug("TryGetMsGraphClient for " + userId + " returns null"); + return null; + } + + + private IGraphServiceClient BuildClient(AuthenticationResult authenticationResult) + { + + logDebug("buildClient..."); + + + //DeviceCodeProvider authenticationProvider = new DeviceCodeProvider(_publicClientApp, Scopes); + var authenticationProvider = new DelegateAuthenticationProvider( + (requestMessage) => + { + var access_token = authenticationResult.AccessToken; + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", access_token); + return Task.FromResult(0); + }); + + GraphServiceClientWithState clientWithState = new GraphServiceClientWithState() + { + Client = new GraphServiceClient(authenticationProvider), + RequiresUserInteraction = false, + TokenExpiryDate = authenticationResult.ExpiresOn.LocalDateTime + }; + + + + if (authenticationResult.Account == null) + throw new Exception("authenticationResult.Account == null!"); + mClientByUser[authenticationResult.Account.HomeAccountId.Identifier] = clientWithState; + logDebug("buildClient ok."); + return clientWithState.Client; + } + + + + private void logDebug(string str) + { +#if DEBUG + Log.Debug("KP2A", "OneDrive2: " + str); +#endif + } + + + protected abstract string SpecialFolder { get; } + + private async Task GetPathItemBuilder(String path) + { + PathItemBuilder result = new PathItemBuilder(SpecialFolder); + + + result.itemLocation = OneDrive2ItemLocation.FromString(path); + if (string.IsNullOrEmpty(result.itemLocation.User?.Name)) + { + throw new Exception("path does not contain user"); + } + + result.client = await TryGetMsGraphClient(path, true); + + if (result.client == null) + throw new Exception("Failed to connect or authenticate to OneDrive!"); + + + return result; + + } + + + private Exception convertException(ClientException e) + { + + if (e.IsMatch(GraphErrorCode.ItemNotFound.ToString())) + return new FileNotFoundException(e.Message); + if (e.Message.Contains("\n\n404 : ") + ) //hacky solution to check for not found. errorCode was null in my tests so I had to find a workaround. + return new FileNotFoundException(e.Message); + return e; + } + + + private Exception convertException(Exception e) + { + if (e is ClientException) + return convertException((ClientException)e); + if (e is AggregateException aggregateException) + { + foreach (var inner in aggregateException.InnerExceptions) + { + return convertException(inner); + } + } + + return e; + } + + + + public bool UserShouldBackup + { + get { return false; } + } + + public void Delete(IOConnectionInfo ioc) + { + try + { + + Task.Run(async () => + { + PathItemBuilder pathItemBuilder = await GetPathItemBuilder(ioc.Path); + await pathItemBuilder.getPathItem() + .Request() + .DeleteAsync(); + }).Wait(); + } + catch (Exception e) + { + throw convertException(e); + } + } + + public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion) + { + return false; + } + + public string GetCurrentFileVersionFast(IOConnectionInfo ioc) + { + return null; + } + + public Stream OpenFileForRead(IOConnectionInfo ioc) + { + try + { + string path = ioc.Path; + + logDebug("openFileForRead. Path=" + path); + Stream result = Task.Run(async () => + { + PathItemBuilder clientAndpath = await GetPathItemBuilder(path); + return await clientAndpath + .getPathItem() + .Content + .Request() + .GetAsync(); + }).Result; + logDebug("ok"); + return result; + + } + catch (Exception e) + { + throw convertException(e); + } + } + + + + class OneDrive2FileStorageWriteTransaction : IWriteTransaction + { + private readonly string _path; + private readonly OneDrive2FileStorage _filestorage; + private MemoryStream _memoryStream; + + public OneDrive2FileStorageWriteTransaction(string path, OneDrive2FileStorage filestorage) + { + _path = path; + _filestorage = filestorage; + } + + public void Dispose() + { + _memoryStream.Dispose(); + } + + public Stream OpenFile() + { + _memoryStream = new MemoryStream(); + return _memoryStream; + } + + public void CommitWrite() + { + _filestorage.UploadFile(_path, new MemoryStream(_memoryStream.ToArray())); + + } + } + + private void UploadFile(string path, MemoryStream stream) + { + try + { + Task.Run(async () => + { + PathItemBuilder pathItemBuilder = await GetPathItemBuilder(path); + //for small files <2MB use the direct upload: + if (stream.Length < 2* 1024 * 1024) + { + return await + pathItemBuilder + .getPathItem() + .Content + .Request() + .PutAsync(stream); + } + + //for larger files use an upload session. This is required for 4MB and beyond, but as the docs are not very clear about this + //limit, let's use it a bit more often to be safe. + + var uploadProps = new DriveItemUploadableProperties + { + ODataType = null, + AdditionalData = new Dictionary + { + { "@microsoft.graph.conflictBehavior", "replace" } + } + }; + + + var uploadSession = await pathItemBuilder + .getPathItem() + .CreateUploadSession(uploadProps) + .Request() + .PostAsync(); + + // Max slice size must be a multiple of 320 KiB + int maxSliceSize = 320 * 1024; + var fileUploadTask = new LargeFileUploadTask(uploadSession, stream, maxSliceSize); + var uploadResult = await fileUploadTask.UploadAsync(); + + if (!uploadResult.UploadSucceeded) + { + throw new Exception("Failed to upload data!"); + } + + return uploadResult.ItemResponse; + + + + }).Wait(); + + } + catch (Exception e) + { + Task.Run(async () => + { + PathItemBuilder pathItemBuilder = await GetPathItemBuilder(path); + pathItemBuilder.verbose = true; + pathItemBuilder.getPathItem(); + }).Wait(); + throw convertException(e); + } + } + + public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) + { + return new OneDrive2FileStorageWriteTransaction(ioc.Path, this); + } + + public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc) + { + return UrlUtil.StripExtension( + GetFilename(IocToPath(ioc))); + } + + public string GetFileExtension(IOConnectionInfo ioc) + { + return UrlUtil.GetExtension(OneDrive2ItemLocation.FromString(ioc.Path).LocalPathString); + } + + private string GetFilename(string path) + { + string localPath = "/"+OneDrive2ItemLocation.FromString(path).LocalPathString; + return localPath.Substring(localPath.LastIndexOf("/", StringComparison.Ordinal) + 1); + } + + public bool RequiresCredentials(IOConnectionInfo ioc) + { + return false; + } + + public void CreateDirectory(IOConnectionInfo parentIoc, string newDirName) + { + try + { + DriveItem driveItem = new DriveItem(); + driveItem.Name = newDirName; + driveItem.Folder = new Folder(); + + DriveItem res = Task.Run(async () => + { + + PathItemBuilder pathItemBuilder = await GetPathItemBuilder(parentIoc.Path); + + return await pathItemBuilder.getPathItem() + .Children + .Request() + .AddAsync(driveItem); + }).Result; + + + } + catch (Exception e) + { + throw convertException(e); + } + } + + public IEnumerable ListContents(IOConnectionInfo ioc) + { + try + { + + return Task.Run(async () => await ListContentsAsync(ioc)).Result; + } + catch (Exception e) + { + throw convertException(e); + } + } + + private async Task> ListContentsAsync(IOConnectionInfo ioc) + { + PathItemBuilder pathItemBuilder = await GetPathItemBuilder(ioc.Path); + + logDebug("listing files for " + ioc.Path); + if (!pathItemBuilder.hasShare() && !pathItemBuilder.hasOneDrivePath()) + { + logDebug("listing shares."); + return await ListShares(pathItemBuilder.itemLocation, pathItemBuilder.client); + } + + logDebug("listing regular children."); + List result = new List(); + /*logDebug("parent before:" + parentPath); + parentPath = parentPath.substring(getProtocolPrefix().length()); + logDebug("parent after: " + parentPath);*/ + + IDriveItemChildrenCollectionPage itemsPage = await pathItemBuilder.getPathItem() + .Children + .Request() + .GetAsync(); + while (true) + { + IList items = itemsPage.CurrentPage; + if (!items.Any()) + return result; + + foreach (DriveItem i in items) + { + var e = GetFileDescription(pathItemBuilder.itemLocation.BuildLocalChildLocation(i.Name, i.Id, i.ParentReference?.DriveId), i); + result.Add(e); + } + var nextPageReqBuilder = itemsPage.NextPageRequest; + if (nextPageReqBuilder == null) + return result; + itemsPage = Task.Run(async () => await nextPageReqBuilder.GetAsync()).Result; + + } + } + + + private FileDescription GetFileDescription(OneDrive2ItemLocation path, DriveItem i) + { + FileDescription e = new FileDescription(); + if (i.Size != null) + e.SizeInBytes = (long)i.Size; + else if ((i.RemoteItem != null) && (i.RemoteItem.Size != null)) + e.SizeInBytes = (long)i.RemoteItem.Size; + + e.DisplayName = i.Name; + e.CanRead = e.CanWrite = true; + e.Path = path.ToString(); + if (i.LastModifiedDateTime != null) + e.LastModified = i.LastModifiedDateTime.Value.LocalDateTime; + else if ((i.RemoteItem != null) && (i.RemoteItem.LastModifiedDateTime != null)) + e.LastModified = i.RemoteItem.LastModifiedDateTime.Value.LocalDateTime; + e.IsDirectory = (i.Folder != null) || ((i.RemoteItem != null) && (i.RemoteItem.Folder != null)); + return e; + } + + public FileDescription GetFileDescription(IOConnectionInfo ioc) + { + try + { + return Task.Run(async() => await GetFileDescriptionAsync(ioc)).Result; + } + catch (Exception e) + { + throw convertException(e); + } + } + + private async Task GetFileDescriptionAsync(IOConnectionInfo ioc) + { + string filename = ioc.Path; + PathItemBuilder pathItemBuilder = await GetPathItemBuilder(filename); + + if (!pathItemBuilder.itemLocation.LocalPath.Any() + && !pathItemBuilder.hasShare()) + { + FileDescription rootEntry = new FileDescription(); + rootEntry.CanRead = rootEntry.CanWrite = true; + rootEntry.Path = filename; + rootEntry.DisplayName = pathItemBuilder.itemLocation.User.Name; + rootEntry.IsDirectory = true; + return rootEntry; + } + + IDriveItemRequestBuilder pathItem = pathItemBuilder.getPathItem(); + + DriveItem item = await pathItem.Request().GetAsync(); + return GetFileDescription(pathItemBuilder.itemLocation, item); + } + + public bool RequiresSetup(IOConnectionInfo ioConnection) + { + return false; + } + + public string IocToPath(IOConnectionInfo ioc) + { + return ioc.Path; + } + + public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, + string protocolId) + { + String path = ProtocolId+ "://"; + activity.StartSelectFileProcess(IOConnectionInfo.FromPath(path), isForSave, requestCode); + } + + + private async Task IsConnectedAsync(string path, bool tryConnect) + { + try + { + logDebug("isConnected? " + path); + + return (await TryGetMsGraphClient(path, tryConnect)) != null; + } + catch (Exception e) + { + logDebug("exception in isConnected: " + e); + return false; + } + + } + + public bool IsConnected(string path) + { + return Task.Run(async () => await IsConnectedAsync(path, false)).Result; + } + + public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode, + bool alwaysReturnSuccess) + { + if (IsConnected(ioc.Path)) + { + Intent intent = new Intent(); + intent.PutExtra(FileStorageSetupDefs.ExtraPath, ioc.Path); + activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileUsagePrepared, intent); + } + else + { + activity.StartFileUsageProcess(ioc, requestCode, alwaysReturnSuccess); + } + + } + + public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc) + { + if (!Task.Run(async() => await IsConnectedAsync(ioc.Path, true)).Result) + { + throw new Exception("MsGraph login required"); + } + } + + public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState) + { + + } + + public void OnResume(IFileStorageSetupActivity activity) + { + + } + + protected void FinishActivityWithSuccess( + IFileStorageSetupActivity setupActivity) + { + //Log.d("KP2AJ", "Success with authenticating!"); + Activity activity = (Activity)setupActivity; + + if (setupActivity.ProcessName + .Equals(FileStorageSetupDefs.ProcessNameFileUsageSetup)) + { + Intent data = new Intent(); + data.PutExtra(FileStorageSetupDefs.ExtraIsForSave, setupActivity.IsForSave); + data.PutExtra(FileStorageSetupDefs.ExtraPath, setupActivity.Ioc.Path); + activity.SetResult((Result)FileStorageResults.FileUsagePrepared, data); + activity.Finish(); + return; + } + if (setupActivity.ProcessName.Equals(FileStorageSetupDefs.ProcessNameSelectfile)) + { + Intent data = new Intent(); + + String path = setupActivity.State.GetString(FileStorageSetupDefs.ExtraPath); + if (path != null) + data.PutExtra(FileStorageSetupDefs.ExtraPath, path); + activity.SetResult((Result)FileStorageResults.FileChooserPrepared, data); + activity.Finish(); + return; + } + + logDebug("Unknown process: " + setupActivity.ProcessName); + + } + + public async void OnStart(IFileStorageSetupActivity activity) + { + logDebug("OneDrive2.OnStart"); + if (activity.ProcessName.Equals(FileStorageSetupDefs.ProcessNameFileUsageSetup)) + activity.State.PutString(FileStorageSetupDefs.ExtraPath, activity.Ioc.Path); + string rootPathForUser = await TryLoginSilent(activity.Ioc.Path); + if (rootPathForUser != null) + { + logDebug("rootPathForUser not null"); + FinishActivityWithSuccess(activity, rootPathForUser); + return; + } + logDebug("rootPathForUser null"); + + try + { + + logDebug("try interactive"); + AuthenticationResult res = await _publicClientApp.AcquireTokenInteractive(Scopes) + .WithParentActivityOrWindow((Activity)activity) + .ExecuteAsync(); + logDebug("ok interactive"); + BuildClient(res); + FinishActivityWithSuccess(activity, BuildRootPathForUser(res)); + + + } + catch (Exception e) + { + logDebug("authenticating not successful: " + e); + Intent data = new Intent(); + data.PutExtra(FileStorageSetupDefs.ExtraErrorMessage, "authenticating not successful"); + ((Activity)activity).SetResult(Result.Canceled, data); + ((Activity)activity).Finish(); + } + + } + + private async Task TryLoginSilent(string iocPath) + { + logDebug("Login Silent for " + iocPath); + IAccount account = null; + try + { + + if (IsConnected(iocPath)) + { + logDebug("Login Silent ok, connected"); + return iocPath; + } + String userId = OneDrive2ItemLocation.FromString(iocPath).User?.Id; + logDebug("needs acquire token"); + logDebug("trying silent login " + iocPath); + + account = Task.Run(async () => await _publicClientApp.GetAccountAsync(userId)).Result; + logDebug("getting user ok."); + + } + catch (Exception e) + { + logDebug(e.ToString()); + } + if (account != null) + { + try + { + + logDebug("AcquireTokenSilent..."); + AuthenticationResult authResult = await _publicClientApp.AcquireTokenSilent(Scopes, account) + .ExecuteAsync(); + + logDebug("AcquireTokenSilent ok."); + BuildClient(authResult); + /*User me = await graphClient.Me.Request().WithForceRefresh(true).GetAsync(); + logDebug("received name " + me.DisplayName);*/ + + var rootFolder = BuildRootPathForUser(authResult); + logDebug("Found RootPath for user"); + return rootFolder; + + } + catch (MsalUiRequiredException ex) + { + + GraphServiceClientWithState clientWithState = new GraphServiceClientWithState() + { + Client = null, + RequiresUserInteraction = true + }; + + + mClientByUser[account.HomeAccountId.Identifier] = clientWithState; + logDebug("ui required"); + return null; + } + catch (Exception ex) + { + logDebug("silent login failed: " + ex.ToString()); + return null; + } + } + return null; + } + + string BuildRootPathForUser(AuthenticationResult res) + { + return OneDrive2ItemLocation.RootForUser(res.Account.Username, res.Account.HomeAccountId.Identifier).ToString(); + } + + + private void FinishActivityWithSuccess(IFileStorageSetupActivity activity, string rootPathForUser) + { + activity.State.PutString(FileStorageSetupDefs.ExtraPath, rootPathForUser); + FinishActivityWithSuccess(activity); + } + + public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data) + { + AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(requestCode, (Result)resultCode, + data); + } + + public string GetDisplayName(IOConnectionInfo ioc) + { + try + { + var itemLocation = OneDrive2ItemLocation.FromString(ioc.Path); + string result = ProtocolId+ "://"; + if (!string.IsNullOrEmpty(itemLocation.User?.Id)) + { + result += itemLocation.User?.Name; + if (itemLocation.Share != null) + { + result += "/" + (itemLocation.Share?.Name ?? itemLocation.Share?.Id); + + if (itemLocation.LocalPath.Any()) + { + result += "/" + itemLocation.LocalPathString; + } + } + + } + return result; + } + catch (Exception e) + { + Kp2aLog.Log("Invalid OneDrive location " + ioc.Path + + ". Note that SprEnging expressions like {DB_PATH} are not supported with OneDrive!"); + return ProtocolId + "://(invalid)"; + } + + } + + + private async Task> ListShares(OneDrive2ItemLocation parentPath, IGraphServiceClient client) + { + + List result = new List(); + + + DriveItem root = await client.Me.Drive.Root.Request().GetAsync(); + FileDescription myEntry = GetFileDescription(parentPath.BuildShare("me","me","me", root.ParentReference?.DriveId), root); + myEntry.DisplayName = MyOneDriveDisplayName; + + result.Add(myEntry); + + if (!CanListShares) + return result; + + + + IDriveSharedWithMeCollectionPage sharedWithMeCollectionPage = await client.Me.Drive.SharedWithMe().Request().GetAsync(); + + while (true) + { + IList sharedWithMeItems = sharedWithMeCollectionPage.CurrentPage; + if (!sharedWithMeItems.Any()) + break; + + foreach (DriveItem i in sharedWithMeItems) + { + FileDescription sharedFileEntry = GetFileDescription(parentPath.BuildShare(i.Id, i.Name, i.WebUrl, i.ParentReference?.DriveId), i); + result.Add(sharedFileEntry); + + } + var b = sharedWithMeCollectionPage.NextPageRequest; + if (b == null) break; + sharedWithMeCollectionPage = await b.GetAsync(); + } + return result; + } + + protected virtual string MyOneDriveDisplayName { get { return "My OneDrive"; } } + + public abstract bool CanListShares { get; } + + DriveItem TryFindFile(PathItemBuilder parent, string filename) + { + IDriveItemChildrenCollectionPage itemsPage = Task.Run(async () => await parent.getPathItem() + .Children + .Request() + .GetAsync()).Result; + while (true) + { + IList items = itemsPage.CurrentPage; + if (!items.Any()) + return null; + + foreach (DriveItem i in items) + { + if (i.Name == filename) + return i; + } + var nextPageReqBuilder = itemsPage.NextPageRequest; + if (nextPageReqBuilder == null) + return null; + itemsPage = Task.Run(async () => await nextPageReqBuilder.GetAsync()).Result; + + } + + } + + + public string CreateFilePath(string parent, string newFilename) + { + try + { + return Task.Run(async() => await CreateFilePathAsync(parent, newFilename)).Result; + } + catch (Exception e) + { + throw convertException(e); + } + + } + + private async Task CreateFilePathAsync(string parent, string newFilename) + { + PathItemBuilder pathItemBuilder = await GetPathItemBuilder(parent); + + //see if such a file exists already: + var item = TryFindFile(pathItemBuilder, newFilename); + if (item != null) + { + return pathItemBuilder.itemLocation.BuildLocalChildLocation(item.Name, item.Id, item.ParentReference?.DriveId) + .ToString(); + } + //doesn't exist. Create: + logDebug("building request for " + pathItemBuilder.itemLocation); + + DriveItem res = await pathItemBuilder.getPathItem() + + .ItemWithPath(newFilename) + .Content + .Request() + .PutAsync(new MemoryStream()); + + return pathItemBuilder.itemLocation.BuildLocalChildLocation(res.Name, res.Id, res.ParentReference?.DriveId) + .ToString(); + } + + public IOConnectionInfo GetParentPath(IOConnectionInfo ioc) + { + return IOConnectionInfo.FromPath(OneDrive2ItemLocation.FromString(ioc.Path).Parent.ToString()); + } + + public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename) + { + return IOConnectionInfo.FromPath(CreateFilePath(folderPath.Path, filename)); + } + + public bool IsPermanentLocation(IOConnectionInfo ioc) + { + return true; + } + + public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null) + { + return false; + } + } + + public class OneDrive2FullFileStorage: OneDrive2FileStorage + { + public override IEnumerable Scopes + { + get + { + return new List + { + "https://graph.microsoft.com/Files.ReadWrite", + "https://graph.microsoft.com/Files.ReadWrite.All" + }; + + } + } + + public override bool CanListShares { get { return true; } } + protected override string SpecialFolder { get { return null; } } + } + + + public class OneDrive2MyFilesFileStorage : OneDrive2FileStorage + { + public override IEnumerable Scopes + { + get + { + return new List + { + "https://graph.microsoft.com/Files.ReadWrite" + }; + + } + } + public override bool CanListShares { get { return false; } } + protected override string SpecialFolder { get { return null; } } + } + + + public class OneDrive2AppFolderFileStorage : OneDrive2FileStorage + { + public override IEnumerable Scopes + { + get + { + return new List + { + "https://graph.microsoft.com/Files.ReadWrite.AppFolder" + }; + + } + } + + protected override string SpecialFolder { get { return "approot"; } } + public override bool CanListShares { get { return false; } } + protected override string MyOneDriveDisplayName { get { return "Keepass2Android App Folder"; } } + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/OneDrive2PrefixContainer.cs b/src/Kp2aBusinessLogicSdkStyle/Io/OneDrive2PrefixContainer.cs new file mode 100644 index 00000000..234e9a86 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/OneDrive2PrefixContainer.cs @@ -0,0 +1,26 @@ +namespace keepass2android.Io.ItemLocation +{ + public abstract class OneDrive2PrefixContainer + { + public abstract string Onedrive2ProtocolId { get; } + public string Onedrive2Prefix { get { return Onedrive2ProtocolId + "://"; } } + } + + //for permissions including all my files and all shared files + public class OneDrive2FullPrefixContainer : OneDrive2PrefixContainer + { + public override string Onedrive2ProtocolId { get { return "onedrive2_full"; }} + } + + //for permissions including all my files + public class OneDrive2MyFilesPrefixContainer : OneDrive2PrefixContainer + { + public override string Onedrive2ProtocolId { get { return "onedrive2_myfiles"; } } + } + + //for permissions to app folder only + public class OneDrive2AppFolderPrefixContainer : OneDrive2PrefixContainer + { + public override string Onedrive2ProtocolId { get { return "onedrive2_appfolder"; } } + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/OneDriveFileStorage.cs b/src/Kp2aBusinessLogicSdkStyle/Io/OneDriveFileStorage.cs new file mode 100644 index 00000000..a6da1c7d --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/OneDriveFileStorage.cs @@ -0,0 +1,171 @@ +using System.Collections.Generic; +using System.IO; +using Android.Content; +using Android.OS; +using KeePassLib.Serialization; +using Exception = Java.Lang.Exception; + +namespace keepass2android.Io +{ + /// + /// This IFileStorage implementation becomes picked if a user is using a skydrive:// or onedrive:// file. + /// These refer to an old (Java) implementation which was replaced starting in 2019. The successor uses onedrive2:// (see OneDrive2FileStorage) + /// The Java implementation was removed in 2024 when the jar files became unavailable. We are keeping this file to notify any user who haven't updated their + /// file storage within 5 years. + /// This file should be removed around mid 2025. + /// + public class OneDriveFileStorage: IFileStorage + { + + public IEnumerable SupportedProtocols + { + get + { + yield return "skydrive"; + yield return "onedrive"; + } + } + + private Exception GetDeprecatedMessage() + { + return new Exception( + "You have opened your file through a deprecated Microsoft API. Please select Change database, Open Database and then select One Drive again."); + } + + public bool UserShouldBackup + { + get { return false; } + } + + public void Delete(IOConnectionInfo ioc) + { + throw GetDeprecatedMessage(); + } + + public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion) + { + throw GetDeprecatedMessage(); + } + + public string GetCurrentFileVersionFast(IOConnectionInfo ioc) + { + throw GetDeprecatedMessage(); + } + + public Stream OpenFileForRead(IOConnectionInfo ioc) + { + throw GetDeprecatedMessage(); + } + + public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) + { + throw GetDeprecatedMessage(); + } + + public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc) + { + throw GetDeprecatedMessage(); + } + + public string GetFileExtension(IOConnectionInfo ioc) + { + throw GetDeprecatedMessage(); + } + + public bool RequiresCredentials(IOConnectionInfo ioc) + { + throw GetDeprecatedMessage(); + } + + public void CreateDirectory(IOConnectionInfo ioc, string newDirName) + { + throw GetDeprecatedMessage(); + } + + public IEnumerable ListContents(IOConnectionInfo ioc) + { + throw GetDeprecatedMessage(); + } + + public FileDescription GetFileDescription(IOConnectionInfo ioc) + { + throw GetDeprecatedMessage(); + } + + public bool RequiresSetup(IOConnectionInfo ioConnection) + { + throw GetDeprecatedMessage(); + } + + public string IocToPath(IOConnectionInfo ioc) + { + throw GetDeprecatedMessage(); + } + + public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId) + { + throw GetDeprecatedMessage(); + } + + public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode, + bool alwaysReturnSuccess) + { + throw GetDeprecatedMessage(); + } + + public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc) + { + throw GetDeprecatedMessage(); + } + + public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState) + { + throw GetDeprecatedMessage(); + } + + public void OnResume(IFileStorageSetupActivity activity) + { + throw GetDeprecatedMessage(); + } + + public void OnStart(IFileStorageSetupActivity activity) + { + throw GetDeprecatedMessage(); + } + + public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data) + { + throw GetDeprecatedMessage(); + } + + public string GetDisplayName(IOConnectionInfo ioc) + { + throw GetDeprecatedMessage(); + } + + public string CreateFilePath(string parent, string newFilename) + { + throw GetDeprecatedMessage(); + } + + public IOConnectionInfo GetParentPath(IOConnectionInfo ioc) + { + throw GetDeprecatedMessage(); + } + + public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename) + { + throw GetDeprecatedMessage(); + } + + public bool IsPermanentLocation(IOConnectionInfo ioc) + { + throw GetDeprecatedMessage(); + } + + public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null) + { + throw GetDeprecatedMessage(); + } + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/PCloudFileStorage.cs b/src/Kp2aBusinessLogicSdkStyle/Io/PCloudFileStorage.cs new file mode 100644 index 00000000..6e169283 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/PCloudFileStorage.cs @@ -0,0 +1,41 @@ +using Android.Content; +#if !EXCLUDE_JAVAFILESTORAGE + +namespace keepass2android.Io +{ + public class PCloudFileStorage: JavaFileStorage + { + private const string ClientId = "yCeH59Ffgtm"; + + public PCloudFileStorage(Context ctx, IKp2aApp app) : + base(new Keepass2android.Javafilestorage.PCloudFileStorage(ctx, ClientId, "pcloud", ""), app) + { + + } + + + public override bool UserShouldBackup + { + get { return false; } + } + } + public class PCloudFileStorageAll : JavaFileStorage + { + private const string ClientId = "FLm22de7bdS"; + + public PCloudFileStorageAll(Context ctx, IKp2aApp app) : + base(new Keepass2android.Javafilestorage.PCloudFileStorage(ctx, ClientId, "pcloudall", "PCLOUDALL_"), app) + { + + + } + + + public override bool UserShouldBackup + { + get { return false; } + } + } + +} +#endif \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/SftpFileStorage.cs b/src/Kp2aBusinessLogicSdkStyle/Io/SftpFileStorage.cs new file mode 100644 index 00000000..b2d43db7 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/SftpFileStorage.cs @@ -0,0 +1,44 @@ +using Android.Content; +using Java.Nio.FileNio; +#if !EXCLUDE_JAVAFILESTORAGE + +namespace keepass2android.Io +{ + public class SftpFileStorage: JavaFileStorage + { + public SftpFileStorage(Context ctx, IKp2aApp app, bool debugEnabled) : + base(new Keepass2android.Javafilestorage.SftpStorage(ctx.ApplicationContext), app) + { + var storage = BaseStorage; + if (debugEnabled) + { + string? logFilename = null; + if (Kp2aLog.LogToFile) + { + logFilename = Kp2aLog.LogFilename; + } + storage.SetJschLogging(true, logFilename); + } + else + { + storage.SetJschLogging(false, null); + } + } + + private Keepass2android.Javafilestorage.SftpStorage BaseStorage + { + get + { + return _jfs as Keepass2android.Javafilestorage.SftpStorage; + } + } + + public override bool UserShouldBackup + { + get { return true; } + } + } + + +} +#endif \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/Io/WebDavFileStorage.cs b/src/Kp2aBusinessLogicSdkStyle/Io/WebDavFileStorage.cs new file mode 100644 index 00000000..6cce6119 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Io/WebDavFileStorage.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; +#if !NoNet && !EXCLUDE_JAVAFILESTORAGE +using Keepass2android.Javafilestorage; +#endif +using KeePassLib.Serialization; + +namespace keepass2android.Io +{ +#if !NoNet && !EXCLUDE_JAVAFILESTORAGE + public class WebDavFileStorage: JavaFileStorage + { + public WebDavFileStorage(IKp2aApp app) : base(new Keepass2android.Javafilestorage.WebDavStorage(app.CertificateErrorHandler), app) + { + } + + public override IEnumerable SupportedProtocols + { + get + { + yield return "http"; + yield return "https"; + yield return "owncloud"; + yield return "nextcloud"; + } + } + + public override bool UserShouldBackup + { + get { return true; } + } + + public static string owncloudPrefix = "owncloud://"; + public static string nextcloudPrefix = "nextcloud://"; + + public static string Owncloud2Webdav(string owncloudUrl, string prefix) + { + + if (owncloudUrl.StartsWith(prefix)) + { + owncloudUrl = owncloudUrl.Substring(prefix.Length); + } + if (!owncloudUrl.Contains("://")) + owncloudUrl = "https://" + owncloudUrl; + if (!owncloudUrl.EndsWith("/")) + owncloudUrl += "/"; + owncloudUrl += "remote.php/webdav/"; + return owncloudUrl; + } + + public override void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, + string protocolId) + { + //need to override so we can loop the protocolId through + activity.PerformManualFileSelect(isForSave, requestCode, protocolId); + } + + public override string IocToPath(IOConnectionInfo ioc) + { + if (ioc.Path.StartsWith("owncloud")) + throw new Exception("owncloud-URIs must be converted to https:// after credential input!"); + if (!String.IsNullOrEmpty(ioc.UserName)) + { + //legacy support. Some users may have stored IOCs with UserName inside. + return ((WebDavStorage)Jfs).BuildFullPath(ioc.Path, ioc.UserName, ioc.Password); + } + return base.IocToPath(ioc); + } + } +#endif +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/KeyFileException.cs b/src/Kp2aBusinessLogicSdkStyle/KeyFileException.cs new file mode 100644 index 00000000..1eb4d824 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/KeyFileException.cs @@ -0,0 +1,63 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System; + +namespace keepass2android +{ + /// + /// Thrown when there is an error adding the keyfie to the user key + /// + [Serializable] + public class KeyFileException : Exception + { + /// + /// Initializes a new instance of the class + /// + public KeyFileException () + { + } + + /// + /// Initializes a new instance of the class + /// + /// A that describes the exception. + public KeyFileException (string message) : base (message) + { + } + + /// + /// Initializes a new instance of the class + /// + /// A that describes the exception. + /// The exception that is the cause of the current exception. + public KeyFileException (string message, Exception inner) : base (message, inner) + { + } + + /// + /// Initializes a new instance of the class + /// + /// The contextual information about the source or destination. + /// The object that holds the serialized object data. + protected KeyFileException (System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base (info, context) + { + } + } + +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/Kp2aBusinessLogicSdkStyle.csproj b/src/Kp2aBusinessLogicSdkStyle/Kp2aBusinessLogicSdkStyle.csproj new file mode 100644 index 00000000..9825a3c4 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Kp2aBusinessLogicSdkStyle.csproj @@ -0,0 +1,32 @@ + + + net8.0-android + 21 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/PreferenceKey.cs b/src/Kp2aBusinessLogicSdkStyle/PreferenceKey.cs new file mode 100644 index 00000000..83a99b4e --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/PreferenceKey.cs @@ -0,0 +1,12 @@ +namespace keepass2android +{ + /// + /// Keys which can be used to get a preference setting + /// + public enum PreferenceKey + { + remember_keyfile, + UseFileTransactions, + CheckForFileChangesOnSave + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/ProgressDialogStatusLogger.cs b/src/Kp2aBusinessLogicSdkStyle/ProgressDialogStatusLogger.cs new file mode 100644 index 00000000..dd1cc6d1 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/ProgressDialogStatusLogger.cs @@ -0,0 +1,137 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System; +using Android.App; +using Android.OS; +using KeePassLib.Interfaces; + +namespace keepass2android +{ + /// + /// StatusLogger implementation which shows the progress in a progress dialog + /// + public class ProgressDialogStatusLogger: IStatusLogger { + private readonly IProgressDialog _progressDialog; + readonly IKp2aApp _app; + private readonly Handler _handler; + private string _message = ""; + private string _submessage; + + public String SubMessage => _submessage; + public String Message => _message; + + public ProgressDialogStatusLogger() { + + } + + public ProgressDialogStatusLogger(IKp2aApp app, Handler handler, IProgressDialog pd) { + _app = app; + _progressDialog = pd; + _handler = handler; + } + + public void UpdateMessage(UiStringKey stringKey) { + if (_app != null) + UpdateMessage(_app.GetResourceString(stringKey)); + } + + public void UpdateMessage (String message) + { + Kp2aLog.Log("status message: " + message); + _message = message; + if ( _app!= null && _progressDialog != null && _handler != null ) { + _handler.Post(() => {_progressDialog.SetMessage(message); } ); + } + } + + public void UpdateSubMessage(String submessage) + { + Kp2aLog.Log("status submessage: " + submessage); + _submessage = submessage; + if (_app != null && _progressDialog != null && _handler != null) + { + _handler.Post(() => + { + if (!String.IsNullOrEmpty(submessage)) + { + _progressDialog.SetMessage(_message + " (" + submessage + ")"); + } + else + { + _progressDialog.SetMessage(_message); + } + } + ); + } + } + + #region IStatusLogger implementation + + public void StartLogging (string strOperation, bool bWriteOperationToLog) + { + + } + + public void EndLogging () + { + + } + + public bool SetProgress (uint uPercent) + { + return true; + } + + public bool SetText (string strNewText, LogStatusType lsType) + { + if (strNewText.StartsWith("KP2AKEY_")) + { + UiStringKey key; + if (Enum.TryParse(strNewText.Substring("KP2AKEY_".Length), true, out key)) + { + UpdateMessage(_app.GetResourceString(key), lsType); + return true; + } + } + UpdateMessage(strNewText, lsType); + + return true; + } + + private void UpdateMessage(string message, LogStatusType lsType) + { + if (lsType == LogStatusType.AdditionalInfo) + { + UpdateSubMessage(message); + } + else + { + UpdateMessage(message); + } + } + + public bool ContinueWork () + { + return true; + } + + #endregion + + } +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/ProgressTask.cs b/src/Kp2aBusinessLogicSdkStyle/ProgressTask.cs new file mode 100644 index 00000000..62d4b6ce --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/ProgressTask.cs @@ -0,0 +1,179 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using Android.App; +using Android.Content; +using Android.OS; +using Java.Lang; + +namespace keepass2android +{ + /// + /// Class to run a task while a progress dialog is shown + /// + public class ProgressTask + { + //for handling Activity recreation situations, we need access to the currently active task. It must hold that there is no more than one active task. + private static ProgressTask _currentTask = null; + + public static void SetNewActiveActivity(Activity activeActivity) + { + if (_currentTask != null) + { + _currentTask.ActiveActivity = activeActivity; + } + } + public static void RemoveActiveActivity(Activity activity) + { + if ((_currentTask != null) && (_currentTask._activeActivity == activity)) + _currentTask.ActiveActivity = null; + + } + + public Activity ActiveActivity + { + get { return _activeActivity; } + private set + { + if (_activeActivity != null && _activeActivity != _previouslyActiveActivity) + { + _previouslyActiveActivity = _activeActivity; + + } + _activeActivity = value; + if (_task != null) + _task.ActiveActivity = _activeActivity; + if (_activeActivity != null) + { + SetupProgressDialog(_app); + _progressDialog.Show(); + } + } + } + + public Activity PreviouslyActiveActivity + { + get { return _previouslyActiveActivity; } + + } + + private readonly Handler _handler; + private readonly RunnableOnFinish _task; + private IProgressDialog _progressDialog; + private readonly IKp2aApp _app; + private Java.Lang.Thread _thread; + private Activity _activeActivity, _previouslyActiveActivity; + private ProgressDialogStatusLogger _progressDialogStatusLogger; + + public ProgressTask(IKp2aApp app, Activity activity, RunnableOnFinish task) + { + _activeActivity = activity; + _task = task; + _handler = app.UiThreadHandler; + _app = app; + + SetupProgressDialog(app); + + // Set code to run when this is finished + _task.OnFinishToRun = new AfterTask(activity, task.OnFinishToRun, _handler, this); + + _task.SetStatusLogger(_progressDialogStatusLogger); + + + } + + private void SetupProgressDialog(IKp2aApp app) + { + string currentMessage = "Initializing..."; + string currentSubmessage = ""; + + if (_progressDialogStatusLogger != null) + { + currentMessage = _progressDialogStatusLogger.Message; + currentSubmessage = _progressDialogStatusLogger.SubMessage; + } + + if (_progressDialog != null) + { + var pd = _progressDialog; + app.UiThreadHandler.Post(() => + { + pd.Dismiss(); + }); + } + + // Show process dialog + _progressDialog = app.CreateProgressDialog(_activeActivity); + _progressDialog.SetTitle(_app.GetResourceString(UiStringKey.progress_title)); + _progressDialogStatusLogger = new ProgressDialogStatusLogger(_app, _handler, _progressDialog); + _progressDialogStatusLogger.UpdateMessage(currentMessage); + _progressDialogStatusLogger.UpdateSubMessage(currentSubmessage); + } + + public void Run(bool allowOverwriteCurrentTask = false) + { + if ((!allowOverwriteCurrentTask) && (_currentTask != null)) + throw new System.Exception("Cannot start another ProgressTask while ProgressTask is already running! " + _task.GetType().Name + "/" + _currentTask._task.GetType().Name); + _currentTask = this; + + // Show process dialog + _progressDialog.Show(); + + + // Start Thread to Run task + _thread = new Java.Lang.Thread(_task.Run); + _thread.Start(); + } + + public void JoinWorkerThread() + { + _thread.Join(); + } + + private class AfterTask : OnFinish { + readonly ProgressTask _progressTask; + + public AfterTask (Activity activity, OnFinish finish, Handler handler, ProgressTask pt): base(activity, finish, handler) + { + _progressTask = pt; + } + + public override void Run() { + base.Run(); + + if (Handler != null) //can be null in tests + { + // Remove the progress dialog + Handler.Post(delegate + { + _progressTask._progressDialog.Dismiss(); + }); + } + else + { + _progressTask._progressDialog.Dismiss(); + } + _currentTask = null; + + } + + } + + + } +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/PwGroupEqualityFromIdComparer.cs b/src/Kp2aBusinessLogicSdkStyle/PwGroupEqualityFromIdComparer.cs new file mode 100644 index 00000000..010dd5b0 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/PwGroupEqualityFromIdComparer.cs @@ -0,0 +1,40 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System.Collections.Generic; +using KeePassLib; + +namespace keepass2android +{ + /// + /// EqualityComparer implementation to compare PwGroups based on their Id + /// + public class PwGroupEqualityFromIdComparer: IEqualityComparer + { + #region IEqualityComparer implementation + public bool Equals (PwGroup x, PwGroup y) + { + return x.Uuid.Equals(y.Uuid); + } + public int GetHashCode (PwGroup obj) + { + return obj.Uuid.ToHexString().GetHashCode(); + } +#endregion + } +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/PwUuidEqualityComparer.cs b/src/Kp2aBusinessLogicSdkStyle/PwUuidEqualityComparer.cs new file mode 100644 index 00000000..ed03a57a --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/PwUuidEqualityComparer.cs @@ -0,0 +1,41 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System.Collections.Generic; +using KeePassLib; + +namespace keepass2android +{ + /// + /// EqualityComparer for PwUuid based on their value (instead of reference) + /// + public class PwUuidEqualityComparer: IEqualityComparer + { + #region IEqualityComparer implementation + public bool Equals (PwUuid x, PwUuid y) + { + return x.Equals(y); + } + public int GetHashCode (PwUuid obj) + { + return obj.ToHexString().GetHashCode(); + } +#endregion + } + + +} diff --git a/src/Kp2aBusinessLogicSdkStyle/SearchDbHelper.cs b/src/Kp2aBusinessLogicSdkStyle/SearchDbHelper.cs new file mode 100644 index 00000000..a51e0b7a --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/SearchDbHelper.cs @@ -0,0 +1,151 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using KeePass.Util.Spr; +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Interfaces; +using KeePassLib.Utility; + +namespace keepass2android +{ + /// + /// Helper class providing methods to search a given database for specific things + /// + public class SearchDbHelper + { + private readonly IKp2aApp _app; + + + public SearchDbHelper(IKp2aApp app) { + _app = app; + } + + + public PwGroup SearchForText (Database database, string str) + { + SearchParameters sp = new SearchParameters {SearchString = str}; + + return Search(database, sp, null); + } + public PwGroup Search(Database database, SearchParameters sp, IDictionary> resultContexts) + { + + if(sp.RegularExpression) // Validate regular expression + { + new Regex(sp.SearchString); + } + + string strGroupName = _app.GetResourceString(UiStringKey.search_results) + " (\"" + sp.SearchString + "\")"; + PwGroup pgResults = new PwGroup(true, true, strGroupName, PwIcon.EMailSearch) {IsVirtual = true}; + + PwObjectList listResults = pgResults.Entries; + + + database.Root.SearchEntries(sp, listResults, resultContexts, new NullStatusLogger()); + + + return pgResults; + + + } + + + public PwGroup SearchForExactUrl (Database database, string url) + { + SearchParameters sp = SearchParameters.None; + sp.SearchInUrls = true; + sp.SearchString = url; + + if(sp.RegularExpression) // Validate regular expression + { + new Regex(sp.SearchString); + } + + string strGroupName = _app.GetResourceString(UiStringKey.search_results) + " (\"" + sp.SearchString + "\")"; + PwGroup pgResults = new PwGroup(true, true, strGroupName, PwIcon.EMailSearch) {IsVirtual = true}; + + PwObjectList listResults = pgResults.Entries; + + + database.Root.SearchEntries(sp, listResults, new NullStatusLogger()); + + + return pgResults; + + } + + public PwGroup SearchForUuid(Database database, string uuid) + { + SearchParameters sp = SearchParameters.None; + sp.SearchInUuids = true; + sp.SearchString = uuid; + + if (sp.RegularExpression) // Validate regular expression + { + new Regex(sp.SearchString); + } + + string strGroupName = _app.GetResourceString(UiStringKey.search_results); + PwGroup pgResults = new PwGroup(true, true, strGroupName, PwIcon.EMailSearch) { IsVirtual = true }; + + PwObjectList listResults = pgResults.Entries; + + database.Root.SearchEntries(sp, listResults, new NullStatusLogger()); + + return pgResults; + + } + + private static String ExtractHost(String url) + { + return UrlUtil.GetHost(url.Trim()); + } + + public PwGroup SearchForHost(Database database, String url, bool allowSubdomains) + { + String host = ExtractHost(url); + string strGroupName = _app.GetResourceString(UiStringKey.search_results) + " (\"" + host + "\")"; + PwGroup pgResults = new PwGroup(true, true, strGroupName, PwIcon.EMailSearch) {IsVirtual = true}; + if (String.IsNullOrWhiteSpace(host)) + return pgResults; + foreach (PwEntry entry in database.EntriesById.Values) + { + if (!entry.GetSearchingEnabled()) + continue; + string otherUrl = entry.Strings.ReadSafe(PwDefs.UrlField); + otherUrl = SprEngine.Compile(otherUrl, new SprContext(entry, database.KpDatabase, SprCompileFlags.References)); + String otherHost = ExtractHost(otherUrl); + if ((allowSubdomains) && (otherHost.StartsWith("www."))) + otherHost = otherHost.Substring(4); //remove "www." + if (String.IsNullOrWhiteSpace(otherHost)) + { + continue; + } + if (host.IndexOf(otherHost, StringComparison.InvariantCultureIgnoreCase) > -1) + { + pgResults.AddEntry(entry, false); + } + } + return pgResults; + } + + } +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/SelectStorageLocationActivityBase.cs b/src/Kp2aBusinessLogicSdkStyle/SelectStorageLocationActivityBase.cs new file mode 100644 index 00000000..df1277bb --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/SelectStorageLocationActivityBase.cs @@ -0,0 +1,289 @@ +using System; +using Android.App; +using Android.Content; +using Android.OS; +using Android.Widget; +using Java.Net; +using KeePassLib.Serialization; +using keepass2android.Io; + +namespace keepass2android +{ + /// + /// base class for SelectStorageLocationActivity containing testable (non-UI) code + /// + public abstract class SelectStorageLocationActivityBase: Activity + { + public enum WritableRequirements + { + ReadOnly = 0, + WriteDesired = 1, + WriteDemanded = 2 + } + + protected const int RequestCodeFileStorageSelectionForPrimarySelect = 33713; + private const int RequestCodeFileStorageSelectionForCopyToWritableLocation = 33714; + private const int RequestCodeFileFileBrowseForWritableLocation = 33715; + private const int RequestCodeFileBrowseForOpen = 33716; + + + protected IOConnectionInfo _selectedIoc; + private IKp2aApp _app; + + public SelectStorageLocationActivityBase(IKp2aApp app) + { + _app = app; + } + + protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) + { + Kp2aLog.Log("base.onAR"); + base.OnActivityResult(requestCode, resultCode, data); + if ((requestCode == RequestCodeFileStorageSelectionForPrimarySelect) || ((requestCode == RequestCodeFileStorageSelectionForCopyToWritableLocation))) + { + int browseRequestCode = RequestCodeFileBrowseForOpen; + if (requestCode == RequestCodeFileStorageSelectionForCopyToWritableLocation) + { + browseRequestCode = RequestCodeFileFileBrowseForWritableLocation; + } + + if (resultCode == ExitFileStorageSelectionOk) + { + + string protocolId = data.GetStringExtra("protocolId"); + + if (protocolId == "androidget") + { + ShowAndroidBrowseDialog(browseRequestCode, false, false); + } + else if (protocolId == "content") + { + ShowAndroidBrowseDialog(browseRequestCode, browseRequestCode == RequestCodeFileFileBrowseForWritableLocation, true); + } + else + { + bool isForSave = (requestCode != RequestCodeFileStorageSelectionForPrimarySelect) + || IsStorageSelectionForSave; + + + StartSelectFile(isForSave, browseRequestCode, protocolId); + + } + + + } + else + { + ReturnCancel(); + } + + } + + if ((requestCode == RequestCodeFileBrowseForOpen) || (requestCode == RequestCodeFileFileBrowseForWritableLocation)) + { + if (resultCode == (Result)FileStorageResults.FileChooserPrepared) + { + IOConnectionInfo ioc = new IOConnectionInfo(); + SetIoConnectionFromIntent(ioc, data); + bool isForSave = (requestCode == RequestCodeFileFileBrowseForWritableLocation) + || IsStorageSelectionForSave; + + StartFileChooser(ioc.Path, requestCode, isForSave); + + return; + } + if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE"))) + { + ShowToast(data.GetStringExtra("EXTRA_ERROR_MESSAGE")); + } + + if (resultCode == Result.Ok) + { + Kp2aLog.Log("FileSelection returned "+data.DataString); + //TODO: don't try to extract filename if content URI + string filename = IntentToFilename(data); + Kp2aLog.Log("FileSelection returned filename " + filename); + if (filename != null) + { + if (filename.StartsWith("file://")) + { + filename = filename.Substring(7); + filename = URLDecoder.Decode(filename); + } + + IOConnectionInfo ioc = new IOConnectionInfo + { + Path = filename + }; + + IocSelected(ioc, requestCode); + } + else + { + if (data.Data.Scheme == "content") + { + IoUtil.TryTakePersistablePermissions(this.ContentResolver, data.Data); + + IocSelected(IOConnectionInfo.FromPath(data.DataString), requestCode); + + } + else + { + ShowInvalidSchemeMessage(data.DataString); + ReturnCancel(); + } + + } + } + else + { + ReturnCancel(); + } + + + } + + + + + } + + protected abstract void StartFileChooser(string path, int requestCode, bool isForSave); + + protected abstract void ShowToast(string text); + + protected abstract void ShowInvalidSchemeMessage(string dataString); + + protected abstract string IntentToFilename(Intent data); + + protected abstract void SetIoConnectionFromIntent(IOConnectionInfo ioc, Intent data); + + protected abstract Result ExitFileStorageSelectionOk { get; } + + /// + /// Starts the appropriate file selection process (either manual file select or prepare filechooser + filechooser) + /// + /// + /// + /// + protected abstract void StartSelectFile(bool isForSave, int browseRequestCode, string protocolId); + + protected abstract void ShowAndroidBrowseDialog(int requestCode, bool isForSave, bool tryGetPermanentAccess); + + protected abstract bool IsStorageSelectionForSave { get; } + + + protected void IocSelected(IOConnectionInfo ioc, int requestCode) + { + if (requestCode == RequestCodeFileFileBrowseForWritableLocation) + { + IocForCopySelected(ioc); + } + else if (requestCode == RequestCodeFileBrowseForOpen) + { + PrimaryIocSelected(ioc); + } + else + { +#if DEBUG + throw new Exception("invalid request code!"); +#endif + } + + + + } + + private void IocForCopySelected(IOConnectionInfo targetIoc) + { + PerformCopy(() => + { + IOConnectionInfo sourceIoc = _selectedIoc; + + try + { + CopyFile(targetIoc, sourceIoc); + } + catch (Exception e) + { + return () => + { + ShowToast(_app.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message); + ReturnCancel(); + }; + } + + + return () => { ReturnOk(targetIoc); }; + }); + } + + protected abstract void PerformCopy(Func copyAndReturnPostExecute); + + private void MoveToWritableLocation(IOConnectionInfo ioc) + { + _selectedIoc = ioc; + + StartFileStorageSelection(RequestCodeFileStorageSelectionForCopyToWritableLocation, false, false); + + } + + protected abstract void StartFileStorageSelection(int requestCode, + bool allowThirdPartyGet, bool allowThirdPartySend); + + + protected virtual void CopyFile(IOConnectionInfo targetIoc, IOConnectionInfo sourceIoc) + { + IoUtil.Copy(targetIoc, sourceIoc, _app); + } + + private void PrimaryIocSelected(IOConnectionInfo ioc) + { + var filestorage = _app.GetFileStorage(ioc, false); + if (!filestorage.IsPermanentLocation(ioc)) + { + string message = _app.GetResourceString(UiStringKey.FileIsTemporarilyAvailable) + " " + _app.GetResourceString(UiStringKey.CopyFileRequired) + " " + _app.GetResourceString(UiStringKey.ClickOkToSelectLocation); + EventHandler onOk = (sender, args) => { MoveToWritableLocation(ioc); }; + EventHandler onCancel = (sender, args) => { ReturnCancel(); }; + ShowAlertDialog(message, onOk, onCancel); + return; + } + + + if ((RequestedWritableRequirements != WritableRequirements.ReadOnly) && (filestorage.IsReadOnly(ioc))) + { + string readOnlyExplanation = _app.GetResourceString(UiStringKey.FileIsReadOnly); + BuiltInFileStorage builtInFileStorage = filestorage as BuiltInFileStorage; + if (builtInFileStorage != null) + { + if (builtInFileStorage.IsReadOnlyBecauseKitkatRestrictions(ioc)) + readOnlyExplanation = _app.GetResourceString(UiStringKey.FileIsReadOnlyOnKitkat); + } + EventHandler onOk = (sender, args) => { MoveToWritableLocation(ioc); }; + EventHandler onCancel = (sender, args) => + { + if (RequestedWritableRequirements == WritableRequirements.WriteDemanded) + ReturnCancel(); + else + ReturnOk(ioc); + }; + ShowAlertDialog(readOnlyExplanation + " " + + (RequestedWritableRequirements == WritableRequirements.WriteDemanded ? + _app.GetResourceString(UiStringKey.CopyFileRequired) + : _app.GetResourceString(UiStringKey.CopyFileRequiredForEditing)) + + " " + + _app.GetResourceString(UiStringKey.ClickOkToSelectLocation), onOk, onCancel); + return; + } + ReturnOk(ioc); + } + + protected abstract void ShowAlertDialog(string message, EventHandler onOk, EventHandler onCancel); + + protected abstract WritableRequirements RequestedWritableRequirements { get; } + + protected abstract void ReturnOk(IOConnectionInfo ioc); + + protected abstract void ReturnCancel(); + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/UiStringKey.cs b/src/Kp2aBusinessLogicSdkStyle/UiStringKey.cs new file mode 100644 index 00000000..5b762493 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/UiStringKey.cs @@ -0,0 +1,94 @@ +namespace keepass2android +{ + /// + /// Keys to identify user-displayable strings. + /// + /// Do not rename the keys here unless you rename the corresponding keys in the resource file of KP2A. + /// The keys are resolved by reflection to the static Resource class. This kind of duplication is necessary + /// in order to use the Resource mechanism of Android but still decouple the logic layer from the UI. + public enum UiStringKey + { + AskDeletePermanentlyGroup, + AskDeletePermanentlyGroupNoRecycle, + progress_title, + AskDeletePermanentlyEntry, + AskDeletePermanentlyEntryNoRecycle, + search_results, + AskDeletePermanently_title, + saving_database, + keyfile_does_not_exist, + RecycleBin, + progress_create, + loading_database, + AddingEntry, + AddingGroup, + DeletingEntry, + DeletingGroup, + SettingPassword, + UndoingChanges, + TransformingKey, + DecodingDatabase, + ParsingDatabase, + CheckingTargetFileForChanges, + TitleSyncQuestion, + MessageSyncQuestion, + SynchronizingDatabase, + yes, + no, + YesSynchronize, + NoOverwrite, + SynchronizingCachedDatabase, + DownloadingRemoteFile, + UploadingFile, + FilesInSync, + SynchronizedDatabaseSuccessfully, + RestoringRemoteFile, + CheckingDatabaseForChanges, + RemoteDatabaseUnchanged, + CannotMoveGroupHere, + ErrorOcurred, + SynchronizingOtpAuxFile, + SavingOtpAuxFile, + CertificateFailure, + exporting_database, + FileIsTemporarilyAvailable, + CopyFileRequired, + ClickOkToSelectLocation, + FileIsReadOnly, + FileIsReadOnlyOnKitkat, + CopyFileRequiredForEditing, + DuplicateUuidsError, + DuplicateUuidsErrorAdditional, + DeletingItems, + AskDeletePermanentlyItems, + AskDeletePermanentlyItemsNoRecycle, + InOfflineMode, + DocumentAccessRevoked, + DuplicateTitle, + TemplateTitle_IdCard, + TemplateField_IdCard_Name, + TemplateField_IdCard_PlaceOfIssue, + TemplateField_IdCard_IssueDate, + TemplateTitle_EMail, + TemplateField_EMail_EMail, + TemplateTitle_WLan, + TemplateTitle_Notes, + TemplateField_WLan_SSID, + TemplateField_Number, + TemplateField_CreditCard_CVV, + TemplateField_CreditCard_PIN, + TemplateField_CreditCard_Owner, + TemplateTitle_CreditCard, + TemplateTitle_Membership, + TemplateGroupName, + AskAddTemplatesTitle, + AskAddTemplatesMessage, + ReadOnlyReason_PreKitKat, + ReadOnlyReason_ReadOnlyFlag, + ReadOnlyReason_ReadOnlyKitKat, + ReadOnlyReason_LocalBackup, + Ok, + cancel, + FileNotFound + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/Utils/EntryUtil.cs b/src/Kp2aBusinessLogicSdkStyle/Utils/EntryUtil.cs new file mode 100644 index 00000000..068821a0 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Utils/EntryUtil.cs @@ -0,0 +1,187 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2013 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; +using KeePass.Util.Spr; + +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Delegates; +using KeePassLib.Utility; + +namespace KeePass.Util +{ + /// + /// This class contains various static functions for entry operations. + /// + public static class EntryUtil + { + + // Old format name (<= 2.14): "KeePassEntriesCF" + public const string ClipFormatEntries = "KeePassEntriesCX"; + + public static string FillPlaceholders(string strText, SprContext ctx) + { + if((ctx == null) || (ctx.Entry == null)) return strText; + + string str = strText; + + /*NOT SUPPORTED CURRENTLY if((ctx.Flags & SprCompileFlags.NewPassword) != SprCompileFlags.None) + str = ReplaceNewPasswordPlaceholder(str, ctx); + + if((ctx.Flags & SprCompileFlags.HmacOtp) != SprCompileFlags.None) + str = ReplaceHmacOtpPlaceholder(str, ctx); + */ + return str; + } + +/* private static string ReplaceNewPasswordPlaceholder(string strText, + SprContext ctx) + { + PwEntry pe = ctx.Entry; + PwDatabase pd = ctx.Database; + if((pe == null) || (pd == null)) return strText; + + string str = strText; + + const string strNewPwPlh = @"{NEWPASSWORD}"; + if(str.IndexOf(strNewPwPlh, StrUtil.CaseIgnoreCmp) >= 0) + { + ProtectedString psAutoGen; + PwgError e = PwGenerator.Generate(out psAutoGen, + Program.Config.PasswordGenerator.AutoGeneratedPasswordsProfile, + null, Program.PwGeneratorPool); + psAutoGen = psAutoGen.WithProtection(pd.MemoryProtection.ProtectPassword); + + if(e == PwgError.Success) + { + pe.CreateBackup(pd); + pe.Strings.Set(PwDefs.PasswordField, psAutoGen); + pe.Touch(true, false); + pd.Modified = true; + + string strIns = SprEngine.TransformContent(psAutoGen.ReadString(), ctx); + str = StrUtil.ReplaceCaseInsensitive(str, strNewPwPlh, strIns); + } + } + + return str; + } +*/ + + public static bool EntriesHaveSameParent(PwObjectList v) + { + if(v == null) { Debug.Assert(false); return true; } + if(v.UCount == 0) return true; + + PwGroup pg = v.GetAt(0).ParentGroup; + foreach(PwEntry pe in v) + { + if(pe.ParentGroup != pg) return false; + } + + return true; + } + + public static void ReorderEntriesAsInDatabase(PwObjectList v, + PwDatabase pd) + { + if((v == null) || (pd == null)) { Debug.Assert(false); return; } + if(pd.RootGroup == null) { Debug.Assert(false); return; } // DB must be open + + PwObjectList vRem = v.CloneShallow(); + v.Clear(); + + EntryHandler eh = delegate(PwEntry pe) + { + int p = vRem.IndexOf(pe); + if(p >= 0) + { + v.Add(pe); + vRem.RemoveAt((uint)p); + } + + return true; + }; + + pd.RootGroup.TraverseTree(TraversalMethod.PreOrder, null, eh); + + foreach(PwEntry peRem in vRem) v.Add(peRem); // Entries not found + } + + + public static string CreateSummaryList(PwGroup pgItems, bool bStartWithNewPar) + { + List l = pgItems.GetEntries(true).CloneShallowToList(); + string str = CreateSummaryList(pgItems, l.ToArray()); + + if((str.Length == 0) || !bStartWithNewPar) return str; + return (MessageService.NewParagraph + str); + } + + public static string CreateSummaryList(PwGroup pgSubGroups, PwEntry[] vEntries) + { + int nMaxEntries = 10; + string strSummary = string.Empty; + + if(pgSubGroups != null) + { + PwObjectList vGroups = pgSubGroups.GetGroups(true); + if(vGroups.UCount > 0) + { + StringBuilder sbGroups = new StringBuilder(); + sbGroups.Append("- "); + uint uToList = Math.Min(3U, vGroups.UCount); + for(uint u = 0; u < uToList; ++u) + { + if(sbGroups.Length > 2) sbGroups.Append(", "); + sbGroups.Append(vGroups.GetAt(u).Name); + } + if(uToList < vGroups.UCount) sbGroups.Append(", ..."); + strSummary += sbGroups.ToString(); // New line below + + nMaxEntries -= 2; + } + } + + int nSummaryShow = Math.Min(nMaxEntries, vEntries.Length); + if(nSummaryShow == (vEntries.Length - 1)) --nSummaryShow; // Plural msg + + for(int iSumEnum = 0; iSumEnum < nSummaryShow; ++iSumEnum) + { + if(strSummary.Length > 0) strSummary += MessageService.NewLine; + + PwEntry pe = vEntries[iSumEnum]; + strSummary += ("- " + StrUtil.CompactString3Dots( + pe.Strings.ReadSafe(PwDefs.TitleField), 39)); + if(PwDefs.IsTanEntry(pe)) + { + string strTanIdx = pe.Strings.ReadSafe(PwDefs.UserNameField); + if(!string.IsNullOrEmpty(strTanIdx)) + strSummary += (@" (#" + strTanIdx + @")"); + } + } + + return strSummary; + } + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/Utils/Spr/SprContext.cs b/src/Kp2aBusinessLogicSdkStyle/Utils/Spr/SprContext.cs new file mode 100644 index 00000000..c7d11b69 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Utils/Spr/SprContext.cs @@ -0,0 +1,209 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2013 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +using KeePassLib; +using KeePassLib.Interfaces; + +using SprRefsCache = System.Collections.Generic.Dictionary; + +namespace KeePass.Util.Spr +{ + [Flags] + public enum SprCompileFlags + { + None = 0, + + AppPaths = 0x1, // Paths to IE, Firefox, Opera, ... + PickChars = 0x2, + EntryStrings = 0x4, + EntryStringsSpecial = 0x8, // {URL:RMVSCM}, ... + PasswordEnc = 0x10, + Group = 0x20, + Paths = 0x40, // App-dir, doc-dir, path sep, ... + AutoType = 0x80, // Replacements like {CLEARFIELD}, ... + DateTime = 0x100, + References = 0x200, + EnvVars = 0x400, + NewPassword = 0x800, + HmacOtp = 0x1000, + Comments = 0x2000, + + ExtActive = 0x4000, // Active transformations provided by plugins + ExtNonActive = 0x8000, // Non-active transformations provided by plugins + + // Next free: 0x10000 + All = 0xFFFF, + + // Internal: + UIInteractive = SprCompileFlags.PickChars, + StateChanging = (SprCompileFlags.NewPassword | SprCompileFlags.HmacOtp), + + Active = (SprCompileFlags.UIInteractive | SprCompileFlags.StateChanging | + SprCompileFlags.ExtActive), + NonActive = (SprCompileFlags.All & ~SprCompileFlags.Active), + + Deref = (SprCompileFlags.EntryStrings | SprCompileFlags.EntryStringsSpecial | + SprCompileFlags.References) + } + + public sealed class SprContext + { + private PwEntry m_pe = null; + public PwEntry Entry + { + get { return m_pe; } + set { m_pe = value; } + } + + private PwDatabase m_pd = null; + public PwDatabase Database + { + get { return m_pd; } + set { m_pd = value; } + } + + private bool m_bMakeAT = false; + public bool EncodeAsAutoTypeSequence + { + get { return m_bMakeAT; } + set { m_bMakeAT = value; } + } + + private bool m_bMakeCmdQuotes = false; + public bool EncodeQuotesForCommandLine + { + get { return m_bMakeCmdQuotes; } + set { m_bMakeCmdQuotes = value; } + } + + private bool m_bForcePlainTextPasswords = true; + public bool ForcePlainTextPasswords + { + get { return m_bForcePlainTextPasswords; } + set { m_bForcePlainTextPasswords = value; } + } + + private SprCompileFlags m_flags = SprCompileFlags.All; + public SprCompileFlags Flags + { + get { return m_flags; } + set { m_flags = value; } + } + + private SprRefsCache m_refsCache = new SprRefsCache(); + /// + /// Used internally by SprEngine; don't modify it. + /// + internal SprRefsCache RefsCache + { + get { return m_refsCache; } + } + + // private bool m_bNoUrlSchemeOnce = false; + // /// + // /// Used internally by SprEngine; don't modify it. + // /// + // internal bool UrlRemoveSchemeOnce + // { + // get { return m_bNoUrlSchemeOnce; } + // set { m_bNoUrlSchemeOnce = value; } + // } + + public SprContext() { } + + public SprContext(PwEntry pe, PwDatabase pd, SprCompileFlags fl) + { + Init(pe, pd, false, false, fl); + } + + public SprContext(PwEntry pe, PwDatabase pd, SprCompileFlags fl, + bool bEncodeAsAutoTypeSequence, bool bEncodeQuotesForCommandLine) + { + Init(pe, pd, bEncodeAsAutoTypeSequence, bEncodeQuotesForCommandLine, fl); + } + + private void Init(PwEntry pe, PwDatabase pd, bool bAT, bool bCmdQuotes, + SprCompileFlags fl) + { + m_pe = pe; + m_pd = pd; + m_bMakeAT = bAT; + m_bMakeCmdQuotes = bCmdQuotes; + m_flags = fl; + } + + public SprContext Clone() + { + return (SprContext)this.MemberwiseClone(); + } + + /// + /// Used by SprEngine internally; do not use. + /// + internal SprContext WithoutContentTransformations() + { + SprContext ctx = Clone(); + + ctx.m_bMakeAT = false; + ctx.m_bMakeCmdQuotes = false; + // ctx.m_bNoUrlSchemeOnce = false; + + Debug.Assert(object.ReferenceEquals(m_pe, ctx.m_pe)); + Debug.Assert(object.ReferenceEquals(m_pd, ctx.m_pd)); + Debug.Assert(object.ReferenceEquals(m_refsCache, ctx.m_refsCache)); + return ctx; + } + } + + public sealed class SprEventArgs : EventArgs + { + private string m_str = string.Empty; + public string Text + { + get { return m_str; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_str = value; + } + } + + private SprContext m_ctx = null; + public SprContext Context + { + get { return m_ctx; } + } + + public SprEventArgs() { } + + public SprEventArgs(string strText, SprContext ctx) + { + if(strText == null) throw new ArgumentNullException("strText"); + // ctx == null is allowed + + m_str = strText; + m_ctx = ctx; + } + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/Utils/Spr/SprEncoding.cs b/src/Kp2aBusinessLogicSdkStyle/Utils/Spr/SprEncoding.cs new file mode 100644 index 00000000..4d603d66 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Utils/Spr/SprEncoding.cs @@ -0,0 +1,75 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2013 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace KeePass.Util.Spr +{ + internal static class SprEncoding + { + internal static string MakeAutoTypeSequence(string str) + { + if(str == null) { Debug.Assert(false); return string.Empty; } + + str = SprEncoding.EscapeAutoTypeBrackets(str); + + str = str.Replace(@"[", @"{[}"); + str = str.Replace(@"]", @"{]}"); + + str = str.Replace(@"+", @"{+}"); + str = str.Replace(@"%", @"{%}"); + str = str.Replace(@"~", @"{~}"); + str = str.Replace(@"(", @"{(}"); + str = str.Replace(@")", @"{)}"); + + str = str.Replace(@"^", @"{^}"); + + return str; + } + + private static string EscapeAutoTypeBrackets(string str) + { + char chOpen = '\u25A1'; + while(str.IndexOf(chOpen) >= 0) ++chOpen; + + char chClose = chOpen; + ++chClose; + while(str.IndexOf(chClose) >= 0) ++chClose; + + str = str.Replace('{', chOpen); + str = str.Replace('}', chClose); + + str = str.Replace(new string(chOpen, 1), @"{{}"); + str = str.Replace(new string(chClose, 1), @"{}}"); + + return str; + } + + internal static string MakeCommandQuotes(string str) + { + if(str == null) { Debug.Assert(false); return string.Empty; } + + // See SHELLEXECUTEINFO structure documentation + return str.Replace("\"", "\"\"\""); + } + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/Utils/Spr/SprEngine.PickChars.cs b/src/Kp2aBusinessLogicSdkStyle/Utils/Spr/SprEngine.PickChars.cs new file mode 100644 index 00000000..67a035d9 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Utils/Spr/SprEngine.PickChars.cs @@ -0,0 +1,153 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2013 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + + +using KeePassLib; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePass.Util.Spr +{ + /// + /// String placeholders and field reference replacement engine. + /// + public static partial class SprEngine + { + // Legacy, for backward compatibility only; see PickChars + private static string ReplacePickPw(string strText, SprContext ctx, + uint uRecursionLevel) + { + if(ctx.Entry == null) { Debug.Assert(false); return strText; } + + string str = strText; + + while(true) + { + const string strStart = @"{PICKPASSWORDCHARS"; + + int iStart = str.IndexOf(strStart, StrUtil.CaseIgnoreCmp); + if(iStart < 0) break; + + int iEnd = str.IndexOf('}', iStart); + if(iEnd < 0) break; + + string strPlaceholder = str.Substring(iStart, iEnd - iStart + 1); + + string strParam = str.Substring(iStart + strStart.Length, + iEnd - (iStart + strStart.Length)); + string[] vParams = strParam.Split(new char[] { ':' }); + + uint uCharCount = 0; + if(vParams.Length >= 2) uint.TryParse(vParams[1], out uCharCount); + + str = ReplacePickPwPlaceholder(str, strPlaceholder, uCharCount, + ctx, uRecursionLevel); + } + + return str; + } + + private static string ReplacePickPwPlaceholder(string str, + string strPlaceholder, uint uCharCount, SprContext ctx, + uint uRecursionLevel) + { + if(str.IndexOf(strPlaceholder, StrUtil.CaseIgnoreCmp) < 0) return str; + + ProtectedString ps = ctx.Entry.Strings.Get(PwDefs.PasswordField); + if(ps != null) + { + string strPassword = ps.ReadString(); + + string strPick = SprEngine.CompileInternal(strPassword, + ctx.WithoutContentTransformations(), uRecursionLevel + 1); + + if(!string.IsNullOrEmpty(strPick)) + { + ProtectedString psPick = new ProtectedString(false, strPick); + string strPicked = string.Empty; + + str = StrUtil.ReplaceCaseInsensitive(str, strPlaceholder, + SprEngine.TransformContent(strPicked, ctx)); + } + } + + return StrUtil.ReplaceCaseInsensitive(str, strPlaceholder, string.Empty); + } + + + private static string ConvertToDownArrows(string str, int iOffset, + string strLayout) + { + if(string.IsNullOrEmpty(str)) return string.Empty; + + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < str.Length; ++i) + { + // if((sb.Length > 0) && !string.IsNullOrEmpty(strSep)) sb.Append(strSep); + + char ch = str[i]; + + int? iDowns = null; + if(strLayout.Length == 0) + { + if((ch >= '0') && (ch <= '9')) iDowns = (int)ch - '0'; + else if((ch >= 'a') && (ch <= 'z')) iDowns = (int)ch - 'a'; + else if((ch >= 'A') && (ch <= 'Z')) iDowns = (int)ch - 'A'; + } + else if(strLayout.Equals("0a", StrUtil.CaseIgnoreCmp)) + { + if((ch >= '0') && (ch <= '9')) iDowns = (int)ch - '0'; + else if((ch >= 'a') && (ch <= 'z')) iDowns = (int)ch - 'a' + 10; + else if((ch >= 'A') && (ch <= 'Z')) iDowns = (int)ch - 'A' + 10; + } + else if(strLayout.Equals("a0", StrUtil.CaseIgnoreCmp)) + { + if((ch >= '0') && (ch <= '9')) iDowns = (int)ch - '0' + 26; + else if((ch >= 'a') && (ch <= 'z')) iDowns = (int)ch - 'a'; + else if((ch >= 'A') && (ch <= 'Z')) iDowns = (int)ch - 'A'; + } + else if(strLayout.Equals("1a", StrUtil.CaseIgnoreCmp)) + { + if((ch >= '1') && (ch <= '9')) iDowns = (int)ch - '1'; + else if(ch == '0') iDowns = 9; + else if((ch >= 'a') && (ch <= 'z')) iDowns = (int)ch - 'a' + 10; + else if((ch >= 'A') && (ch <= 'Z')) iDowns = (int)ch - 'A' + 10; + } + else if(strLayout.Equals("a1", StrUtil.CaseIgnoreCmp)) + { + if((ch >= '1') && (ch <= '9')) iDowns = (int)ch - '1' + 26; + else if(ch == '0') iDowns = 9 + 26; + else if((ch >= 'a') && (ch <= 'z')) iDowns = (int)ch - 'a'; + else if((ch >= 'A') && (ch <= 'Z')) iDowns = (int)ch - 'A'; + } + + if(!iDowns.HasValue) continue; + + for(int j = 0; j < (iOffset + iDowns); ++j) sb.Append(@"{DOWN}"); + } + + return sb.ToString(); + } + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/Utils/Spr/SprEngine.cs b/src/Kp2aBusinessLogicSdkStyle/Utils/Spr/SprEngine.cs new file mode 100644 index 00000000..854b520f --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/Utils/Spr/SprEngine.cs @@ -0,0 +1,597 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2013 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Diagnostics; + +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Security; +using KeePassLib.Utility; +using keepass2android; + +namespace KeePass.Util.Spr +{ + /// + /// String placeholders and field reference replacement engine. + /// + public static partial class SprEngine + { + private const uint MaxRecursionDepth = 12; + private const StringComparison ScMethod = StringComparison.OrdinalIgnoreCase; + + // private static readonly char[] m_vPlhEscapes = new char[] { '{', '}', '%' }; + + // Important notes for plugin developers subscribing to the following events: + // * If possible, prefer subscribing to FilterCompile instead of + // FilterCompilePre. + // * If your plugin provides an active transformation (e.g. replacing a + // placeholder that changes some state or requires UI interaction), you + // must only perform the transformation if the ExtActive bit is set in + // args.Context.Flags of the event arguments object args provided to the + // event handler. + // * Non-active transformations should only be performed if the ExtNonActive + // bit is set in args.Context.Flags. + // * If your plugin provides a placeholder (like e.g. {EXAMPLE}), you + // should add this placeholder to the FilterPlaceholderHints list + // (e.g. add the string "{EXAMPLE}"). Please remove your strings from + // the list when your plugin is terminated. + public static event EventHandler FilterCompilePre; + public static event EventHandler FilterCompile; + + private static List m_lFilterPlh = new List(); + // See the events above + public static List FilterPlaceholderHints + { + get { return m_lFilterPlh; } + } + + private static void InitializeStatic() + { + + } + + [Obsolete] + public static string Compile(string strText, bool bIsAutoTypeSequence, + PwEntry pwEntry, PwDatabase pwDatabase, bool bEscapeForAutoType, + bool bEscapeQuotesForCommandLine) + { + SprContext ctx = new SprContext(pwEntry, pwDatabase, SprCompileFlags.All, + bEscapeForAutoType, bEscapeQuotesForCommandLine); + return Compile(strText, ctx); + } + + public static string Compile(string strText, SprContext ctx) + { + if(strText == null) { Debug.Assert(false); return string.Empty; } + if(strText.Length == 0) return string.Empty; + + SprEngine.InitializeStatic(); + + if(ctx == null) ctx = new SprContext(); + ctx.RefsCache.Clear(); + + string str = SprEngine.CompileInternal(strText, ctx, 0); + + // if(bEscapeForAutoType && !bIsAutoTypeSequence) + // str = SprEncoding.MakeAutoTypeSequence(str); + + return str; + } + + private static string CompileInternal(string strText, SprContext ctx, + uint uRecursionLevel) + { + if(strText == null) { Debug.Assert(false); return string.Empty; } + if(ctx == null) { Debug.Assert(false); ctx = new SprContext(); } + + if(uRecursionLevel >= SprEngine.MaxRecursionDepth) + { + Debug.Assert(false); // Most likely a recursive reference + return string.Empty; // Do not return strText (endless loop) + } + + string str = strText; + + bool bExt = ((ctx.Flags & (SprCompileFlags.ExtActive | + SprCompileFlags.ExtNonActive)) != SprCompileFlags.None); + if(bExt && (SprEngine.FilterCompilePre != null)) + { + SprEventArgs args = new SprEventArgs(str, ctx.Clone()); + SprEngine.FilterCompilePre(null, args); + str = args.Text; + } + + if((ctx.Flags & SprCompileFlags.Comments) != SprCompileFlags.None) + str = RemoveComments(str); + + if(ctx.Entry != null) + { + if((ctx.Flags & SprCompileFlags.PickChars) != SprCompileFlags.None) + str = ReplacePickPw(str, ctx, uRecursionLevel); + + if((ctx.Flags & SprCompileFlags.EntryStrings) != SprCompileFlags.None) + str = FillEntryStrings(str, ctx, uRecursionLevel); + + if((ctx.Flags & SprCompileFlags.EntryStringsSpecial) != SprCompileFlags.None) + { + // ctx.UrlRemoveSchemeOnce = true; + // str = SprEngine.FillIfExists(str, @"{URL:RMVSCM}", + // ctx.Entry.Strings.GetSafe(PwDefs.UrlField), ctx, uRecursionLevel); + // Debug.Assert(!ctx.UrlRemoveSchemeOnce); + + str = FillEntryStringsSpecial(str, ctx, uRecursionLevel); + } + + if(((ctx.Flags & SprCompileFlags.PasswordEnc) != SprCompileFlags.None) && + (str.IndexOf(@"{PASSWORD_ENC}", SprEngine.ScMethod) >= 0)) + str = SprEngine.FillIfExists(str, @"{PASSWORD_ENC}", new ProtectedString(false, + StrUtil.EncryptString(ctx.Entry.Strings.ReadSafe(PwDefs.PasswordField))), + ctx, uRecursionLevel); + + if(((ctx.Flags & SprCompileFlags.Group) != SprCompileFlags.None) && + (ctx.Entry.ParentGroup != null)) + { + str = SprEngine.FillIfExists(str, @"{GROUP}", new ProtectedString( + false, ctx.Entry.ParentGroup.Name), ctx, uRecursionLevel); + + str = SprEngine.FillIfExists(str, @"{GROUPPATH}", new ProtectedString( + false, ctx.Entry.ParentGroup.GetFullPath()), ctx, uRecursionLevel); + } + } + + + if(ctx.Database != null) + { + if((ctx.Flags & SprCompileFlags.Paths) != SprCompileFlags.None) + { + // For backward compatibility only + str = SprEngine.FillIfExists(str, @"{DOCDIR}", new ProtectedString( + false, UrlUtil.GetFileDirectory(ctx.Database.IOConnectionInfo.Path, + false, false)), ctx, uRecursionLevel); + + str = SprEngine.FillIfExists(str, @"{DB_PATH}", new ProtectedString( + false, ctx.Database.IOConnectionInfo.Path), ctx, uRecursionLevel); + str = SprEngine.FillIfExists(str, @"{DB_DIR}", new ProtectedString( + false, UrlUtil.GetFileDirectory(ctx.Database.IOConnectionInfo.Path, + false, false)), ctx, uRecursionLevel); + str = SprEngine.FillIfExists(str, @"{DB_NAME}", new ProtectedString( + false, UrlUtil.GetFileName(ctx.Database.IOConnectionInfo.Path)), + ctx, uRecursionLevel); + str = SprEngine.FillIfExists(str, @"{DB_BASENAME}", new ProtectedString( + false, UrlUtil.StripExtension(UrlUtil.GetFileName( + ctx.Database.IOConnectionInfo.Path))), ctx, uRecursionLevel); + str = SprEngine.FillIfExists(str, @"{DB_EXT}", new ProtectedString( + false, UrlUtil.GetExtension(ctx.Database.IOConnectionInfo.Path)), + ctx, uRecursionLevel); + } + } + + if((ctx.Flags & SprCompileFlags.Paths) != SprCompileFlags.None) + { + str = SprEngine.FillIfExists(str, @"{ENV_DIRSEP}", new ProtectedString( + false, Path.DirectorySeparatorChar.ToString()), ctx, uRecursionLevel); + + string strPF86 = Environment.GetEnvironmentVariable("ProgramFiles(x86)"); + if(string.IsNullOrEmpty(strPF86)) + strPF86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + if(strPF86 != null) + str = SprEngine.FillIfExists(str, @"{ENV_PROGRAMFILES_X86}", + new ProtectedString(false, strPF86), ctx, uRecursionLevel); + else { Debug.Assert(false); } + } + + if((ctx.Flags & SprCompileFlags.AutoType) != SprCompileFlags.None) + { + str = StrUtil.ReplaceCaseInsensitive(str, @"{CLEARFIELD}", + @"{HOME}+({END}){DEL}{DELAY 50}"); + str = StrUtil.ReplaceCaseInsensitive(str, @"{WIN}", @"{VKEY 91}"); + str = StrUtil.ReplaceCaseInsensitive(str, @"{LWIN}", @"{VKEY 91}"); + str = StrUtil.ReplaceCaseInsensitive(str, @"{RWIN}", @"{VKEY 92}"); + str = StrUtil.ReplaceCaseInsensitive(str, @"{APPS}", @"{VKEY 93}"); + + for(int np = 0; np < 10; ++np) + str = StrUtil.ReplaceCaseInsensitive(str, @"{NUMPAD" + + Convert.ToString(np, 10) + @"}", @"{VKEY " + + Convert.ToString(np + 0x60, 10) + @"}"); + } + + if((ctx.Flags & SprCompileFlags.DateTime) != SprCompileFlags.None) + { + DateTime dtNow = DateTime.Now; // Local time + str = SprEngine.FillIfExists(str, @"{DT_YEAR}", new ProtectedString( + false, dtNow.Year.ToString("D4")), ctx, uRecursionLevel); + str = SprEngine.FillIfExists(str, @"{DT_MONTH}", new ProtectedString( + false, dtNow.Month.ToString("D2")), ctx, uRecursionLevel); + str = SprEngine.FillIfExists(str, @"{DT_DAY}", new ProtectedString( + false, dtNow.Day.ToString("D2")), ctx, uRecursionLevel); + str = SprEngine.FillIfExists(str, @"{DT_HOUR}", new ProtectedString( + false, dtNow.Hour.ToString("D2")), ctx, uRecursionLevel); + str = SprEngine.FillIfExists(str, @"{DT_MINUTE}", new ProtectedString( + false, dtNow.Minute.ToString("D2")), ctx, uRecursionLevel); + str = SprEngine.FillIfExists(str, @"{DT_SECOND}", new ProtectedString( + false, dtNow.Second.ToString("D2")), ctx, uRecursionLevel); + str = SprEngine.FillIfExists(str, @"{DT_SIMPLE}", new ProtectedString( + false, dtNow.ToString("yyyyMMddHHmmss")), ctx, uRecursionLevel); + + dtNow = dtNow.ToUniversalTime(); + str = SprEngine.FillIfExists(str, @"{DT_UTC_YEAR}", new ProtectedString( + false, dtNow.Year.ToString("D4")), ctx, uRecursionLevel); + str = SprEngine.FillIfExists(str, @"{DT_UTC_MONTH}", new ProtectedString( + false, dtNow.Month.ToString("D2")), ctx, uRecursionLevel); + str = SprEngine.FillIfExists(str, @"{DT_UTC_DAY}", new ProtectedString( + false, dtNow.Day.ToString("D2")), ctx, uRecursionLevel); + str = SprEngine.FillIfExists(str, @"{DT_UTC_HOUR}", new ProtectedString( + false, dtNow.Hour.ToString("D2")), ctx, uRecursionLevel); + str = SprEngine.FillIfExists(str, @"{DT_UTC_MINUTE}", new ProtectedString( + false, dtNow.Minute.ToString("D2")), ctx, uRecursionLevel); + str = SprEngine.FillIfExists(str, @"{DT_UTC_SECOND}", new ProtectedString( + false, dtNow.Second.ToString("D2")), ctx, uRecursionLevel); + str = SprEngine.FillIfExists(str, @"{DT_UTC_SIMPLE}", new ProtectedString( + false, dtNow.ToString("yyyyMMddHHmmss")), ctx, uRecursionLevel); + } + + if((ctx.Flags & SprCompileFlags.References) != SprCompileFlags.None) + str = SprEngine.FillRefPlaceholders(str, ctx, uRecursionLevel); + + if(((ctx.Flags & SprCompileFlags.EnvVars) != SprCompileFlags.None) && + (str.IndexOf('%') >= 0)) + { + // Replace environment variables + foreach(DictionaryEntry de in Environment.GetEnvironmentVariables()) + { + string strKey = (de.Key as string); + string strValue = (de.Value as string); + + if((strKey != null) && (strValue != null)) + str = SprEngine.FillIfExists(str, @"%" + strKey + @"%", + new ProtectedString(false, strValue), ctx, uRecursionLevel); + else { Debug.Assert(false); } + } + } + + str = EntryUtil.FillPlaceholders(str, ctx); + + if(bExt && (SprEngine.FilterCompile != null)) + { + SprEventArgs args = new SprEventArgs(str, ctx.Clone()); + SprEngine.FilterCompile(null, args); + str = args.Text; + } + + if(ctx.EncodeAsAutoTypeSequence) + { + str = StrUtil.NormalizeNewLines(str, false); + str = str.Replace("\n", @"{ENTER}"); + } + + return str; + } + + private static string FillIfExists(string strData, string strPlaceholder, + ProtectedString psParsable, SprContext ctx, uint uRecursionLevel) + { + // // The UrlRemoveSchemeOnce property of ctx must be cleared + // // before this method returns and before any recursive call + // bool bRemoveScheme = false; + // if(ctx != null) + // { + // bRemoveScheme = ctx.UrlRemoveSchemeOnce; + // ctx.UrlRemoveSchemeOnce = false; + // } + + if(strData == null) { Debug.Assert(false); return string.Empty; } + if(strPlaceholder == null) { Debug.Assert(false); return strData; } + if(strPlaceholder.Length == 0) { Debug.Assert(false); return strData; } + if(psParsable == null) { Debug.Assert(false); return strData; } + + if(strData.IndexOf(strPlaceholder, SprEngine.ScMethod) >= 0) + { + string strReplacement = SprEngine.CompileInternal( + psParsable.ReadString(), ctx.WithoutContentTransformations(), + uRecursionLevel + 1); + + // if(bRemoveScheme) + // strReplacement = UrlUtil.RemoveScheme(strReplacement); + + return SprEngine.FillPlaceholder(strData, strPlaceholder, + strReplacement, ctx); + } + + return strData; + } + + private static string FillPlaceholder(string strData, string strPlaceholder, + string strReplaceWith, SprContext ctx) + { + if(strData == null) { Debug.Assert(false); return string.Empty; } + if(strPlaceholder == null) { Debug.Assert(false); return strData; } + if(strPlaceholder.Length == 0) { Debug.Assert(false); return strData; } + if(strReplaceWith == null) { Debug.Assert(false); return strData; } + + return StrUtil.ReplaceCaseInsensitive(strData, strPlaceholder, + SprEngine.TransformContent(strReplaceWith, ctx)); + } + + public static string TransformContent(string strContent, SprContext ctx) + { + if(strContent == null) { Debug.Assert(false); return string.Empty; } + + string str = strContent; + + if(ctx != null) + { + if(ctx.EncodeQuotesForCommandLine) + str = SprEncoding.MakeCommandQuotes(str); + + if(ctx.EncodeAsAutoTypeSequence) + str = SprEncoding.MakeAutoTypeSequence(str); + } + + return str; + } + + private static string FillEntryStrings(string str, SprContext ctx, + uint uRecursionLevel) + { + List vKeys = ctx.Entry.Strings.GetKeys(); + + // Ensure that all standard field names are in the list + // (this is required in order to replace the standard placeholders + // even if the corresponding standard field isn't present in + // the entry) + List vStdNames = PwDefs.GetStandardFields(); + foreach(string strStdField in vStdNames) + { + if(!vKeys.Contains(strStdField)) vKeys.Add(strStdField); + } + + // Do not directly enumerate the strings in ctx.Entry.Strings, + // because strings might change during the Spr compilation + foreach(string strField in vKeys) + { + string strKey = (PwDefs.IsStandardField(strField) ? + (@"{" + strField + @"}") : + (@"{" + PwDefs.AutoTypeStringPrefix + strField + @"}")); + + if(!ctx.ForcePlainTextPasswords && strKey.Equals(@"{" + + PwDefs.PasswordField + @"}", StrUtil.CaseIgnoreCmp)) + { + str = SprEngine.FillIfExists(str, strKey, new ProtectedString( + false, PwDefs.HiddenPassword), ctx, uRecursionLevel); + continue; + } + + // Use GetSafe because the field doesn't necessarily exist + // (might be a standard field that has been added above) + str = SprEngine.FillIfExists(str, strKey, ctx.Entry.Strings.GetSafe( + strField), ctx, uRecursionLevel); + } + + return str; + } + + private const string UrlSpecialRmvScm = @"{URL:RMVSCM}"; + private const string UrlSpecialScm = @"{URL:SCM}"; + private const string UrlSpecialHost = @"{URL:HOST}"; + private const string UrlSpecialPort = @"{URL:PORT}"; + private const string UrlSpecialPath = @"{URL:PATH}"; + private const string UrlSpecialQuery = @"{URL:QUERY}"; + private static string FillEntryStringsSpecial(string str, SprContext ctx, + uint uRecursionLevel) + { + if((str.IndexOf(UrlSpecialRmvScm, SprEngine.ScMethod) >= 0) || + (str.IndexOf(UrlSpecialScm, SprEngine.ScMethod) >= 0) || + (str.IndexOf(UrlSpecialHost, SprEngine.ScMethod) >= 0) || + (str.IndexOf(UrlSpecialPort, SprEngine.ScMethod) >= 0) || + (str.IndexOf(UrlSpecialPath, SprEngine.ScMethod) >= 0) || + (str.IndexOf(UrlSpecialQuery, SprEngine.ScMethod) >= 0)) + { + string strUrl = SprEngine.FillIfExists(@"{URL}", @"{URL}", + ctx.Entry.Strings.GetSafe(PwDefs.UrlField), ctx, uRecursionLevel); + + str = StrUtil.ReplaceCaseInsensitive(str, UrlSpecialRmvScm, + UrlUtil.RemoveScheme(strUrl)); + + try + { + Uri uri = new Uri(strUrl); + + str = StrUtil.ReplaceCaseInsensitive(str, UrlSpecialScm, + uri.Scheme); + str = StrUtil.ReplaceCaseInsensitive(str, UrlSpecialHost, + uri.Host); + str = StrUtil.ReplaceCaseInsensitive(str, UrlSpecialPort, + uri.Port.ToString()); + str = StrUtil.ReplaceCaseInsensitive(str, UrlSpecialPath, + uri.AbsolutePath); + str = StrUtil.ReplaceCaseInsensitive(str, UrlSpecialQuery, + uri.Query); + } + catch(Exception) { } // Invalid URI + } + + return str; + } + + private const string StrRemStart = @"{C:"; + private const string StrRemEnd = @"}"; + private static string RemoveComments(string strSeq) + { + string str = strSeq; + + while(true) + { + int iStart = str.IndexOf(StrRemStart, SprEngine.ScMethod); + if(iStart < 0) break; + int iEnd = str.IndexOf(StrRemEnd, iStart + 1, SprEngine.ScMethod); + if(iEnd <= iStart) break; + + str = (str.Substring(0, iStart) + str.Substring(iEnd + StrRemEnd.Length)); + } + + return str; + } + + internal const string StrRefStart = @"{REF:"; + internal const string StrRefEnd = @"}"; + private static string FillRefPlaceholders(string strSeq, SprContext ctx, + uint uRecursionLevel) + { + if(ctx.Database == null) return strSeq; + + string str = strSeq; + + int nOffset = 0; + for(int iLoop = 0; iLoop < 20; ++iLoop) + { + str = SprEngine.FillRefsUsingCache(str, ctx); + + int nStart = str.IndexOf(StrRefStart, nOffset, SprEngine.ScMethod); + if(nStart < 0) break; + int nEnd = str.IndexOf(StrRefEnd, nStart + 1, SprEngine.ScMethod); + if(nEnd <= nStart) break; + + string strFullRef = str.Substring(nStart, nEnd - nStart + 1); + char chScan, chWanted; + PwEntry peFound = FindRefTarget(strFullRef, ctx, out chScan, out chWanted); + + if(peFound != null) + { + string strInsData; + if(chWanted == 'T') + strInsData = peFound.Strings.ReadSafe(PwDefs.TitleField); + else if(chWanted == 'U') + strInsData = peFound.Strings.ReadSafe(PwDefs.UserNameField); + else if(chWanted == 'A') + strInsData = peFound.Strings.ReadSafe(PwDefs.UrlField); + else if(chWanted == 'P') + strInsData = peFound.Strings.ReadSafe(PwDefs.PasswordField); + else if(chWanted == 'N') + strInsData = peFound.Strings.ReadSafe(PwDefs.NotesField); + else if(chWanted == 'I') + strInsData = peFound.Uuid.ToHexString(); + else { nOffset = nStart + 1; continue; } + + if((chWanted == 'P') && !ctx.ForcePlainTextPasswords) + strInsData = PwDefs.HiddenPassword; + + SprContext sprSub = ctx.WithoutContentTransformations(); + sprSub.Entry = peFound; + + string strInnerContent = SprEngine.CompileInternal(strInsData, + sprSub, uRecursionLevel + 1); + strInnerContent = SprEngine.TransformContent(strInnerContent, ctx); + + // str = str.Substring(0, nStart) + strInnerContent + str.Substring(nEnd + 1); + SprEngine.AddRefToCache(strFullRef, strInnerContent, ctx); + str = SprEngine.FillRefsUsingCache(str, ctx); + } + else { nOffset = nStart + 1; continue; } + } + + return str; + } + + public static PwEntry FindRefTarget(string strFullRef, SprContext ctx, + out char chScan, out char chWanted) + { + chScan = char.MinValue; + chWanted = char.MinValue; + + if(strFullRef == null) { Debug.Assert(false); return null; } + if(!strFullRef.StartsWith(StrRefStart, SprEngine.ScMethod) || + !strFullRef.EndsWith(StrRefEnd, SprEngine.ScMethod)) + return null; + if((ctx == null) || (ctx.Database == null)) { Debug.Assert(false); return null; } + + string strRef = strFullRef.Substring(StrRefStart.Length, + strFullRef.Length - StrRefStart.Length - StrRefEnd.Length); + if(strRef.Length <= 4) return null; + if(strRef[1] != '@') return null; + if(strRef[3] != ':') return null; + + chScan = char.ToUpper(strRef[2]); + chWanted = char.ToUpper(strRef[0]); + + SearchParameters sp = SearchParameters.None; + sp.SearchString = strRef.Substring(4); + if(chScan == 'T') sp.SearchInTitles = true; + else if(chScan == 'U') sp.SearchInUserNames = true; + else if(chScan == 'A') sp.SearchInUrls = true; + else if(chScan == 'P') sp.SearchInPasswords = true; + else if(chScan == 'N') sp.SearchInNotes = true; + else if(chScan == 'I') sp.SearchInUuids = true; + else if(chScan == 'O') sp.SearchInOther = true; + else return null; + + PwObjectList lFound = new PwObjectList(); + ctx.Database.RootGroup.SearchEntries(sp, lFound); + + return ((lFound.UCount > 0) ? lFound.GetAt(0) : null); + } + + private static string FillRefsUsingCache(string strText, SprContext ctx) + { + string str = strText; + + foreach(KeyValuePair kvp in ctx.RefsCache) + { + // str = str.Replace(kvp.Key, kvp.Value); + str = StrUtil.ReplaceCaseInsensitive(str, kvp.Key, kvp.Value); + } + + return str; + } + + private static void AddRefToCache(string strRef, string strValue, + SprContext ctx) + { + if(strRef == null) { Debug.Assert(false); return; } + if(strValue == null) { Debug.Assert(false); return; } + if(ctx == null) { Debug.Assert(false); return; } + + // Only add if not exists, do not overwrite + if(!ctx.RefsCache.ContainsKey(strRef)) + ctx.RefsCache.Add(strRef, strValue); + } + + // internal static bool MightChange(string strText) + // { + // if(string.IsNullOrEmpty(strText)) return false; + // return (strText.IndexOfAny(m_vPlhEscapes) >= 0); + // } + + /// + /// Fast probabilistic test whether a string might be + /// changed when compiling with SprCompileFlags.Deref. + /// + internal static bool MightDeref(string strText) + { + if(strText == null) return false; + return (strText.IndexOf('{') >= 0); + } + + + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/app.config b/src/Kp2aBusinessLogicSdkStyle/app.config new file mode 100644 index 00000000..dd17539b --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/database/CheckDatabaseForChanges.cs b/src/Kp2aBusinessLogicSdkStyle/database/CheckDatabaseForChanges.cs new file mode 100644 index 00000000..0986dfea --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/CheckDatabaseForChanges.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using Android.App; +using Android.Content; +using KeePassLib.Cryptography; +using KeePassLib.Serialization; +using KeePassLib.Utility; +using keepass2android.Io; + +namespace keepass2android +{ + public class CheckDatabaseForChanges: RunnableOnFinish + { + private readonly Context _context; + private readonly IKp2aApp _app; + + + public CheckDatabaseForChanges(Activity context, IKp2aApp app, OnFinish finish) + : base(context, finish) + { + _context = context; + _app = app; + } + + public override void Run() + { + try + { + IOConnectionInfo ioc = _app.CurrentDb.Ioc; + IFileStorage fileStorage = _app.GetFileStorage(ioc); + if (fileStorage is CachingFileStorage) + { + throw new Exception("Cannot sync a cached database!"); + } + StatusLogger.UpdateMessage(UiStringKey.CheckingDatabaseForChanges); + + //download file from remote location and calculate hash: + StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile)); + + + MemoryStream remoteData = new MemoryStream(); + using ( + HashingStreamEx hashingRemoteStream = new HashingStreamEx(fileStorage.OpenFileForRead(ioc), false, + new SHA256Managed())) + { + hashingRemoteStream.CopyTo(remoteData); + hashingRemoteStream.Close(); + + if (!MemUtil.ArraysEqual(_app.CurrentDb.KpDatabase.HashOfFileOnDisk, hashingRemoteStream.Hash)) + { + _app.TriggerReload(_context, null); + Finish(true); + } + else + { + Finish(true, _app.GetResourceString(UiStringKey.RemoteDatabaseUnchanged)); + } + } + + + + } + catch (Exception e) + { + Finish(false, e.Message); + } + + } + + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/database/Database.cs b/src/Kp2aBusinessLogicSdkStyle/database/Database.cs new file mode 100644 index 00000000..f62572cd --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/Database.cs @@ -0,0 +1,290 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization; +using System.Security.Cryptography; +using System.Text; +using Java.Lang; +using KeePassLib; +using KeePassLib.Keys; +using KeePassLib.Serialization; +using keepass2android.Io; +using KeePassLib.Interfaces; +using KeePassLib.Utility; +using Exception = System.Exception; +using String = System.String; + +namespace keepass2android +{ + + public class Database + { + public HashSet Elements = new HashSet(); + public Dictionary GroupsById = new Dictionary(new PwUuidEqualityComparer()); + public Dictionary EntriesById = new Dictionary(new PwUuidEqualityComparer()); + public PwGroup Root; + public PwDatabase KpDatabase; + public IOConnectionInfo Ioc + { + get + { + + return KpDatabase?.IOConnectionInfo; + + } + } + + /// + /// if an OTP key was used, this property tells the location of the OTP auxiliary file. + /// Must be set after loading. + /// + public IOConnectionInfo OtpAuxFileIoc { get; set; } + + public string LastFileVersion; + public SearchDbHelper SearchHelper; + + public IDrawableFactory DrawableFactory; + + readonly IKp2aApp _app; + + public Database(IDrawableFactory drawableFactory, IKp2aApp app) + { + DrawableFactory = drawableFactory; + _app = app; + CanWrite = true; //default + } + + private IDatabaseFormat _databaseFormat = new KdbxDatabaseFormat(KdbxFormat.Default); + private bool? _hasTotpEntries; + + public bool ReloadRequested { get; set; } + + public bool DidOpenFileChange() + { + return _app.GetFileStorage(Ioc).CheckForFileChangeFast(Ioc, LastFileVersion); + } + + + /// + /// Do not call this method directly. Call App.Kp2a.LoadDatabase instead. + /// + public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, MemoryStream databaseData, CompositeKey compositeKey, ProgressDialogStatusLogger status, IDatabaseFormat databaseFormat) + { + PwDatabase pwDatabase = new PwDatabase(); + + IFileStorage fileStorage = _app.GetFileStorage(iocInfo); + Stream s = databaseData ?? fileStorage.OpenFileForRead(iocInfo); + var fileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo); + PopulateDatabaseFromStream(pwDatabase, s, iocInfo, compositeKey, status, databaseFormat); + LastFileVersion = fileVersion; + + status.UpdateSubMessage(""); + + Root = pwDatabase.RootGroup; + PopulateGlobals(Root); + + + KpDatabase = pwDatabase; + SearchHelper = new SearchDbHelper(app); + + _databaseFormat = databaseFormat; + _hasTotpEntries = null; + + CanWrite = databaseFormat.CanWrite && !fileStorage.IsReadOnly(iocInfo); + } + + /// + /// Indicates whether it is possible to make changes to this database + /// + public bool CanWrite { get; set; } + + public IDatabaseFormat DatabaseFormat + { + get { return _databaseFormat; } + set { _databaseFormat = value; } + } + + public string IocAsHexString() + { + return IoUtil.IocAsHexString(Ioc); + } + + public static string GetFingerprintPrefKey(IOConnectionInfo ioc) + { + var iocAsHexString = IoUtil.IocAsHexString(ioc); + + return "kp2a_ioc_" + iocAsHexString; + } + + + public static string GetFingerprintModePrefKey(IOConnectionInfo ioc) + { + return GetFingerprintPrefKey(ioc) + "_mode"; + } + + public string CurrentFingerprintPrefKey + { + get { return GetFingerprintPrefKey(Ioc); } + } + + public string CurrentFingerprintModePrefKey + { + get { return GetFingerprintModePrefKey(Ioc); } + } + + protected virtual void PopulateDatabaseFromStream(PwDatabase pwDatabase, Stream s, IOConnectionInfo iocInfo, CompositeKey compositeKey, ProgressDialogStatusLogger status, IDatabaseFormat databaseFormat) + { + IFileStorage fileStorage = _app.GetFileStorage(iocInfo); + var filename = fileStorage.GetFilenameWithoutPathAndExt(iocInfo); + pwDatabase.Open(s, filename, iocInfo, compositeKey, status, databaseFormat); + } + + + public PwGroup SearchForText(String str) { + PwGroup group = SearchHelper.SearchForText(this, str); + + return group; + + } + + public PwGroup Search(SearchParameters searchParams, IDictionary> resultContexts) + { + if (SearchHelper == null) + throw new Exception("SearchHelper is null"); + return SearchHelper.Search(this, searchParams, resultContexts); + } + + + public PwGroup SearchForExactUrl(String url) { + PwGroup group = SearchHelper.SearchForExactUrl(this, url); + + return group; + + } + public PwGroup SearchForUuid(String uuid) + { + PwGroup group = SearchHelper.SearchForUuid(this, uuid); + + return group; + + } + + public PwGroup SearchForHost(String url, bool allowSubdomains) { + PwGroup group = SearchHelper.SearchForHost(this, url, allowSubdomains); + + return group; + + } + + + public void SaveData() { + + using (IWriteTransaction trans = _app.GetFileStorage(Ioc).OpenWriteTransaction(Ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions))) + { + DatabaseFormat.Save(KpDatabase, trans.OpenFile()); + + trans.CommitWrite(); + } + _hasTotpEntries = null; + + } + + public bool HasTotpEntries + { + get + { + if (_hasTotpEntries == null) + { + _hasTotpEntries = true; + } + return _hasTotpEntries.Value; + } + } + + private void PopulateGlobals(PwGroup currentGroup, bool checkForDuplicateUuids ) + { + + var childGroups = currentGroup.Groups; + var childEntries = currentGroup.Entries; + + foreach (PwEntry e in childEntries) + { + if (checkForDuplicateUuids) + { + if (EntriesById.ContainsKey(e.Uuid)) + { + throw new DuplicateUuidsException("Same UUID for entries '"+EntriesById[e.Uuid].Strings.ReadSafe(PwDefs.TitleField)+"' and '"+e.Strings.ReadSafe(PwDefs.TitleField)+"'."); + } + + } + EntriesById [e.Uuid] = e; + Elements.Add(e); + } + + GroupsById[currentGroup.Uuid] = currentGroup; + Elements.Add(currentGroup); + foreach (PwGroup g in childGroups) + { + if (checkForDuplicateUuids) + { + /*Disable check. Annoying for users, no problem for KP2A. + if (Groups.ContainsKey(g.Uuid)) + { + throw new DuplicateUuidsException(); + } + * */ + } + PopulateGlobals(g); + } + } + private void PopulateGlobals (PwGroup currentGroup) + { + PopulateGlobals(currentGroup, _app.CheckForDuplicateUuids); + } + + + public void UpdateGlobals() + { + EntriesById.Clear(); + GroupsById.Clear(); + Elements.Clear(); + PopulateGlobals(Root); + } + } + + [Serializable] + public class DuplicateUuidsException : Exception + { + public DuplicateUuidsException() + { + } + + public DuplicateUuidsException(string message) : base(message) + { + } + + protected DuplicateUuidsException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/database/KdbDatabaseFormat.cs b/src/Kp2aBusinessLogicSdkStyle/database/KdbDatabaseFormat.cs new file mode 100644 index 00000000..d8e0199d --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/KdbDatabaseFormat.cs @@ -0,0 +1,496 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +#if !EXCLUDE_KEYTRANSFORM +using Android.App; +using Com.Keepassdroid.Database; +using Com.Keepassdroid.Database.Exception; +#endif +using Com.Keepassdroid.Database.Save; +using Java.Util; +using KeePassLib; +using KeePassLib.Cryptography; +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.KeyDerivation; +using KeePassLib.Interfaces; +using KeePassLib.Keys; +using KeePassLib.Security; +using PwIcon = KeePassLib.PwIcon; +using Random = System.Random; + +namespace keepass2android +{ + public class KdbDatabaseFormat: IDatabaseFormat + { + private readonly IKp2aApp _app; + private Dictionary _groupData = new Dictionary(); + private static readonly DateTime _expireNever = new DateTime(2999,12,28,23,59,59); + private List _metaStreams; + + public KdbDatabaseFormat(IKp2aApp app) + { + _app = app; + } + + public void PopulateDatabaseFromStream(PwDatabase db, Stream s, IStatusLogger slLogger) + { + #if !EXCLUDE_KEYTRANSFORM + var importer = new Com.Keepassdroid.Database.Load.ImporterV3(); + + var hashingStream = new HashingStreamEx(s, false, new SHA256Managed()); + + _metaStreams = new List(); + + string password = "";//no need to distinguish between null and "" because empty passwords are invalid (and null is not allowed) + KcpPassword passwordKey = (KcpPassword)db.MasterKey.GetUserKey(typeof(KcpPassword)); + if (passwordKey != null) + { + password = passwordKey.Password.ReadString(); + } + + KcpKeyFile passwordKeyfile = (KcpKeyFile)db.MasterKey.GetUserKey(typeof(KcpKeyFile)); + MemoryStream keyfileStream = null; + if (passwordKeyfile != null) + { + keyfileStream = new MemoryStream(passwordKeyfile.RawFileData.ReadData()); + } + + + try + { + var dbv3 = importer.OpenDatabase(hashingStream, password, keyfileStream); + + db.Name = dbv3.Name; + db.KdfParameters = (new AesKdf()).GetDefaultParameters(); + db.KdfParameters.SetUInt64(AesKdf.ParamRounds, (ulong)dbv3.NumKeyEncRounds); + + + db.RootGroup = ConvertGroup(dbv3.RootGroup); + if (dbv3.Algorithm == PwEncryptionAlgorithm.Rjindal) + { + db.DataCipherUuid = StandardAesEngine.AesUuid; + } + else + { + db.DataCipherUuid = new PwUuid(TwofishCipher.TwofishCipherEngine.TwofishCipherUuidBytes); + } + + + } + catch (Java.IO.FileNotFoundException e) + { + throw new FileNotFoundException( + e.Message, e); + } + catch (Java.Lang.Exception e) + { + if (e.Message == "Invalid key!") + throw new InvalidCompositeKeyException(); + throw new Exception(e.LocalizedMessage ?? + e.Message ?? + e.GetType().Name, e); + } + + HashOfLastStream = hashingStream.Hash; + if (HashOfLastStream == null) + throw new Exception("hashing didn't work"); //todo remove +#else + throw new Exception("Kdb is excluded with Key transform!"); +#endif + } + + #if !EXCLUDE_KEYTRANSFORM + + private PwGroup ConvertGroup(PwGroupV3 groupV3) + { + PwGroup pwGroup = new PwGroup(true, false); + pwGroup.Uuid = CreateUuidFromGroupId(groupV3.Id.Id); + + //check if we have group data for this group already (from loading in a previous pass). + //then use the same UUID (important for merging) + var gdForGroup = _groupData.Where(g => g.Value.Id == groupV3.Id.Id).ToList(); + if (gdForGroup.Count == 1) + { + pwGroup.Uuid = gdForGroup.Single().Key; + } + + pwGroup.Name = groupV3.Name; + Android.Util.Log.Debug("KP2A", "load kdb: group " + groupV3.Name); + pwGroup.CreationTime = ConvertTime(groupV3.TCreation); + pwGroup.LastAccessTime = ConvertTime(groupV3.TLastAccess); + pwGroup.LastModificationTime = ConvertTime(groupV3.TLastMod); + pwGroup.ExpiryTime = ConvertTime(groupV3.TExpire); + pwGroup.Expires = !(Math.Abs((pwGroup.ExpiryTime - _expireNever).TotalMilliseconds) < 500); ; + + if (groupV3.Icon != null) + pwGroup.IconId = (PwIcon) groupV3.Icon.IconId; + _groupData[pwGroup.Uuid] = new AdditionalGroupData + { + Flags = groupV3.Flags, + Id = groupV3.Id.Id + }; + + + for (int i = 0; i < groupV3.ChildGroups.Count;i++) + { + pwGroup.AddGroup(ConvertGroup(groupV3.GetGroupAt(i)), true); + } + for (int i = 0; i < groupV3.ChildEntries.Count; i++) + { + var entry = groupV3.GetEntryAt(i); + if (entry.IsMetaStream) + { + _metaStreams.Add(entry); + continue; + } + + pwGroup.AddEntry(ConvertEntry(entry), true); + } + + return pwGroup; + } + + private PwUuid CreateUuidFromGroupId(int id) + { + byte[] template = new byte[] { 0xd2, 0x18, 0x22, 0x93, + 0x8e, 0xa4, 0x43, 0xf2, + 0xb4, 0xb5, 0x2a, 0x49, + 0x00, 0x00, 0x00, 0x00}; + byte[] idBytes = BitConverter.GetBytes(id); + for (int i = 0; i < 4; i++) + { + template[i + 12] = idBytes[i]; + } + return new PwUuid(template); + } + + private PwEntry ConvertEntry(PwEntryV3 fromEntry) + { + PwEntry toEntry = new PwEntry(false, false); + toEntry.Uuid = new PwUuid(fromEntry.Uuid.ToArray()); + String modTime = Android.Text.Format.DateFormat.GetTimeFormat(Application.Context).Format(fromEntry.TCreation.JDate); + Android.Util.Log.Debug("KP2A", modTime); + toEntry.CreationTime = ConvertTime(fromEntry.TCreation); + toEntry.LastAccessTime = ConvertTime(fromEntry.TLastAccess); + toEntry.LastModificationTime = ConvertTime(fromEntry.TLastMod); + + toEntry.ExpiryTime = ConvertTime(fromEntry.TExpire); + + toEntry.ExpiryTime = new DateTime(toEntry.ExpiryTime.Year, toEntry.ExpiryTime.Month, toEntry.ExpiryTime.Day, toEntry.ExpiryTime.Hour, toEntry.ExpiryTime.Minute, toEntry.ExpiryTime.Second); + toEntry.Expires = !(Math.Abs((toEntry.ExpiryTime - _expireNever).TotalMilliseconds) < 500); + + if (fromEntry.Icon != null) + toEntry.IconId = (PwIcon) fromEntry.Icon.IconId; + SetFieldIfAvailable(toEntry, PwDefs.TitleField, false, fromEntry.Title); + Android.Util.Log.Debug("KP2A", "load kdb: entry " + toEntry.Strings.ReadSafe(PwDefs.TitleField)); + SetFieldIfAvailable(toEntry, PwDefs.UserNameField, false, fromEntry.Username); + SetFieldIfAvailable(toEntry, PwDefs.UrlField, false, fromEntry.Url); + SetFieldIfAvailable(toEntry, PwDefs.PasswordField, true, fromEntry.Password); + SetFieldIfAvailable(toEntry, PwDefs.NotesField, true, fromEntry.Additional); + + if ((fromEntry.GetBinaryData() != null) && (fromEntry.GetBinaryData().Length > 0)) + { + toEntry.Binaries.Set(fromEntry.BinaryDesc, new ProtectedBinary(true, fromEntry.GetBinaryData())); + } + return toEntry; + } + + private void SetFieldIfAvailable(PwEntry pwEntry, string fieldName, bool makeProtected, string value) + { + if (value != null) + { + pwEntry.Strings.Set(fieldName, new ProtectedString(makeProtected, value)); + } + + } + + private DateTime ConvertTime(PwDate date) + { + if (date == null) + return PwDefs.DtDefaultNow; + + return JavaTimeToCSharp(date.JDate.Time); + } + + private DateTime JavaTimeToCSharp(long javatime) + { + try + { + var utcTime = new DateTime(1970, 1, 1).AddMilliseconds(javatime); + return TimeZoneInfo.ConvertTimeFromUtc(utcTime, TimeZoneInfo.Local); + } + catch (ArgumentOutOfRangeException) + { + return DateTime.MinValue; + } + + + } +#endif + public byte[] HashOfLastStream { get; private set; } + public bool CanWrite { get { return true; } } + public string SuccessMessage { get { return null; } } + + public void Save(PwDatabase kpDatabase, Stream stream) + { + PwDatabaseV3 db =new PwDatabaseV3(); + KcpPassword pwd = kpDatabase.MasterKey.GetUserKey(); + string password = pwd != null ? pwd.Password.ReadString() : ""; + KcpKeyFile keyfile = kpDatabase.MasterKey.GetUserKey(); + Stream keyfileContents = null; + if (keyfile != null) + { + keyfileContents = new MemoryStream(keyfile.RawFileData.ReadData()); + } + db.SetMasterKey(password, keyfileContents); + + AesKdf kdf = new AesKdf(); + if (!kdf.Uuid.Equals(kpDatabase.KdfParameters.KdfUuid)) + db.NumRounds = (uint)PwDefs.DefaultKeyEncryptionRounds; + else + { + ulong uRounds = kpDatabase.KdfParameters.GetUInt64( + AesKdf.ParamRounds, PwDefs.DefaultKeyEncryptionRounds); + uRounds = Math.Min(uRounds, 0xFFFFFFFEUL); + + db.NumRounds = (uint)uRounds; + } + + + db.Name = kpDatabase.Name; + if (kpDatabase.DataCipherUuid.Equals(StandardAesEngine.AesUuid)) + { + db.Algorithm = PwEncryptionAlgorithm.Rjindal; + } + else + { + db.Algorithm = PwEncryptionAlgorithm.Twofish; + } + + //create groups + db.Groups.Clear(); + var fromGroups = kpDatabase.RootGroup.GetGroups(true); + Dictionary groupV3s = new Dictionary(fromGroups.Count()); + foreach (PwGroup g in fromGroups) + { + if (g == kpDatabase.RootGroup) + continue; + PwGroupV3 groupV3 = ConvertGroup(g, db); + db.Groups.Add(groupV3); + groupV3s[groupV3.Id.Id] = groupV3; + } + + //traverse again and assign parents + db.RootGroup = ConvertGroup(kpDatabase.RootGroup, db); + db.RootGroup.Level = -1; + + AssignParent(kpDatabase.RootGroup, db, groupV3s); + + foreach (PwEntry e in kpDatabase.RootGroup.GetEntries(true)) + { + PwEntryV3 entryV3 = ConvertEntry(e, db); + entryV3.Parent = groupV3s[_groupData[e.ParentGroup.Uuid].Id]; + entryV3.Parent.ChildEntries.Add(entryV3); + entryV3.GroupId = entryV3.Parent.Id.Id; + db.Entries.Add(entryV3); + } + + //add meta stream entries: + if (db.Groups.Any()) + { + foreach (var metaEntry in _metaStreams) + { + metaEntry.GroupId = db.Groups.First().Id.Id; + db.Entries.Add(metaEntry); + } + + } + + + HashingStreamEx hashedStream = new HashingStreamEx(stream, true, null); + PwDbV3Output output = new PwDbV3Output(db, hashedStream); + output.Output(); + hashedStream.Close(); + HashOfLastStream = hashedStream.Hash; + + kpDatabase.HashOfLastIO = kpDatabase.HashOfFileOnDisk = HashOfLastStream; + stream.Close(); + } + + public bool CanHaveEntriesInRootGroup + { + get { return false; } + } + + public bool CanHaveMultipleAttachments + { + get { return false; } + } + + public bool CanHaveCustomFields + { + get { return false; } + } + + public bool HasDefaultUsername + { + get { return false; } + } + + public bool HasDatabaseName + { + get { return false; } + } + + public bool SupportsAttachmentKeys + { + get { return false; } + } + + public bool SupportsTags + { + get { return false; } + } + + public bool SupportsOverrideUrl + { + get { return false; } + } + + public bool CanRecycle + { + get { return false; } + } + + public bool SupportsTemplates + { + get { return false; } + } + + private void AssignParent(PwGroup kpParent, PwDatabaseV3 dbV3, Dictionary groupV3s) + { + PwGroupV3 parentV3; + if (kpParent.ParentGroup == null) + { + parentV3 = dbV3.RootGroup; + } + else + { + parentV3 = groupV3s[_groupData[kpParent.Uuid].Id]; + } + + foreach (PwGroup g in kpParent.Groups.OrderBy(g => g.Name)) + { + PwGroupV3 groupV3 = groupV3s[_groupData[g.Uuid].Id]; + + parentV3.ChildGroups.Add(groupV3); + groupV3.Parent = parentV3; + + AssignParent(g, dbV3, groupV3s); + } + } + + private PwGroupV3 ConvertGroup(PwGroup fromGroup, PwDatabaseV3 dbTo) + { + PwGroupV3 toGroup = new PwGroupV3(); + toGroup.Name = fromGroup.Name; + //todo remove + Android.Util.Log.Debug("KP2A", "save kdb: group " + fromGroup.Name); + + toGroup.TCreation = new PwDate(ConvertTime(fromGroup.CreationTime)); + toGroup.TLastAccess= new PwDate(ConvertTime(fromGroup.LastAccessTime)); + toGroup.TLastMod = new PwDate(ConvertTime(fromGroup.LastModificationTime)); + if (fromGroup.Expires) + { + toGroup.TExpire = new PwDate(ConvertTime(fromGroup.ExpiryTime)); + } + else + { + toGroup.TExpire = new PwDate(ConvertTime(_expireNever)); + } + + toGroup.Icon = dbTo.IconFactory.GetIcon((int) fromGroup.IconId); + AdditionalGroupData groupData; + if (_groupData.TryGetValue(fromGroup.Uuid, out groupData)) + { + toGroup.Id = new PwGroupIdV3(groupData.Id); + toGroup.Flags = groupData.Flags; + } + else + { + //group was added + do + { + toGroup.Id = new PwGroupIdV3(new Random().Next()); + } while (_groupData.Values.Any(gd => gd.Id == toGroup.Id.Id)); //retry if id already exists + //store to block new id and reuse when saving again (without loading in between) + _groupData[fromGroup.Uuid] = new AdditionalGroupData + { + Id = toGroup.Id.Id + }; + + } + + return toGroup; + } + + private PwEntryV3 ConvertEntry(PwEntry fromEntry, PwDatabaseV3 dbTo) + { + PwEntryV3 toEntry = new PwEntryV3(); + toEntry.Uuid = fromEntry.Uuid.UuidBytes; + toEntry.CreationTime = ConvertTime(fromEntry.CreationTime); + toEntry.LastAccessTime = ConvertTime(fromEntry.LastAccessTime); + toEntry.LastModificationTime = ConvertTime(fromEntry.LastModificationTime); + + if (fromEntry.Expires) + { + toEntry.ExpiryTime = ConvertTime(fromEntry.ExpiryTime); + } + else + { + toEntry.ExpiryTime = ConvertTime(_expireNever); + } + + + toEntry.Icon = dbTo.IconFactory.GetIcon((int) fromEntry.IconId); + toEntry.SetTitle(GetString(fromEntry, PwDefs.TitleField), dbTo); + Android.Util.Log.Debug("KP2A", "save kdb: entry " + fromEntry.Strings.ReadSafe(PwDefs.TitleField)); + toEntry.SetUsername(GetString(fromEntry, PwDefs.UserNameField), dbTo); + toEntry.SetUrl(GetString(fromEntry, PwDefs.UrlField), dbTo); + var pwd = GetString(fromEntry, PwDefs.PasswordField); + if (pwd != null) + toEntry.SetPassword(pwd, dbTo); + toEntry.SetNotes(GetString(fromEntry, PwDefs.NotesField), dbTo); + if (fromEntry.Binaries.Any()) + { + var binaryData = fromEntry.Binaries.First().Value.ReadData(); + toEntry.SetBinaryData(binaryData, 0, binaryData.Length); + } + + return toEntry; + } + + private string GetString(PwEntry fromEntry, string id) + { + ProtectedString protectedString = fromEntry.Strings.Get(id); + if (protectedString == null) + return ""; + return protectedString.ReadString(); + } + + private Date ConvertTime(DateTime dateTime) + { + long timestamp = (long)(dateTime.ToUniversalTime().Subtract(new DateTime(1970, 1, 1))).TotalMilliseconds; + return new Date(timestamp); + } + } + + + internal class AdditionalGroupData + { + public int Id { get; set; } + public int Flags { get; set; } + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/database/KdbxDatabaseFormat.cs b/src/Kp2aBusinessLogicSdkStyle/database/KdbxDatabaseFormat.cs new file mode 100644 index 00000000..0d9ac6fc --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/KdbxDatabaseFormat.cs @@ -0,0 +1,93 @@ +using System.IO; +using KeePassLib; +using KeePassLib.Interfaces; +using KeePassLib.Keys; +using KeePassLib.Serialization; + +namespace keepass2android +{ + public class KdbxDatabaseFormat : IDatabaseFormat + { + private readonly KdbxFormat _format; + + public static KdbxFormat GetFormatToUse(string fileExt) + { + return fileExt.Equals("kdbp", StringComparison.OrdinalIgnoreCase) ? KdbxFormat.ProtocolBuffers : + (fileExt.Equals("xml", StringComparison.OrdinalIgnoreCase) ? KdbxFormat.PlainXml : KdbxFormat.Default); + } + + public KdbxDatabaseFormat(KdbxFormat format) + { + _format = format; + } + + public void PopulateDatabaseFromStream(PwDatabase db, Stream s, IStatusLogger slLogger) + { + KdbxFile kdbx = new KdbxFile(db); + kdbx.DetachBinaries = db.DetachBinaries; + + kdbx.Load(s, _format, slLogger); + HashOfLastStream = kdbx.HashOfFileOnDisk; + s.Close(); + + } + + public byte[] HashOfLastStream { get; private set; } + public bool CanWrite { get { return _format != KdbxFormat.PlainXml; } } + public string SuccessMessage { get { return null; } } + public void Save(PwDatabase kpDatabase, Stream stream) + { + kpDatabase.Save(stream, null); + } + + public bool CanHaveEntriesInRootGroup + { + get { return true; } + } + + public bool CanHaveMultipleAttachments + { + get { return true; } + } + + public bool CanHaveCustomFields + { + get { return true; } + } + + public bool HasDefaultUsername + { + get { return true; } + } + + public bool HasDatabaseName + { + get { return true; } + } + + public bool SupportsAttachmentKeys + { + get { return true; } + } + + public bool SupportsTags + { + get { return true; } + } + + public bool SupportsOverrideUrl + { + get { return true; } + } + + public bool CanRecycle + { + get { return true; } + } + + public bool SupportsTemplates + { + get { return true; } + } + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/database/PwEntryOutput.cs b/src/Kp2aBusinessLogicSdkStyle/database/PwEntryOutput.cs new file mode 100644 index 00000000..295b9a0e --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/PwEntryOutput.cs @@ -0,0 +1,65 @@ +using System; +using KeePass.Util.Spr; +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Security; + +namespace keepass2android +{ + /// + /// Represents the strings which are output from a PwEntry. + /// + /// In contrast to the original PwEntry, this means that placeholders are replaced. Also, plugins may modify + /// or add fields. + public class PwEntryOutput + { + private readonly PwEntry _entry; + private readonly Database _db; + private readonly ProtectedStringDictionary _outputStrings = new ProtectedStringDictionary(); + + /// + /// Constructs the PwEntryOutput by replacing the placeholders + /// + public PwEntryOutput(PwEntry entry, Database db) + { + _entry = entry; + _db = db; + + foreach (var pair in entry.Strings) + { + _outputStrings.Set(pair.Key, new ProtectedString(entry.Strings.Get(pair.Key).IsProtected, GetStringAndReplacePlaceholders(pair.Key))); + } + } + + string GetStringAndReplacePlaceholders(string key) + { + String value = Entry.Strings.ReadSafe(key); + value = SprEngine.Compile(value, new SprContext(Entry, _db.KpDatabase, SprCompileFlags.All)); + return value; + } + + + /// + /// Returns the ID of the entry + /// + public PwUuid Uuid + { + get { return Entry.Uuid; } + } + + /// + /// The output strings for the represented entry + /// + public ProtectedStringDictionary OutputStrings { get { return _outputStrings; } } + + public PwEntry Entry + { + get { return _entry; } + } + + /// + /// if the entry was selected by searching for a URL, the query URL is returned here. + /// + public string SearchUrl { get; set; } + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/database/SynchronizeCachedDatabase.cs b/src/Kp2aBusinessLogicSdkStyle/database/SynchronizeCachedDatabase.cs new file mode 100644 index 00000000..ad58a493 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/SynchronizeCachedDatabase.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Android.App; +using Android.Content; +using KeePassLib.Serialization; +using keepass2android.Io; + +namespace keepass2android +{ + public class SynchronizeCachedDatabase: RunnableOnFinish + { + private readonly Activity _context; + private readonly IKp2aApp _app; + private SaveDb _saveDb; + + public SynchronizeCachedDatabase(Activity context, IKp2aApp app, OnFinish finish) + : base(context, finish) + { + _context = context; + _app = app; + } + + public override void Run() + { + try + { + IOConnectionInfo ioc = _app.CurrentDb.Ioc; + IFileStorage fileStorage = _app.GetFileStorage(ioc); + if (!(fileStorage is CachingFileStorage)) + { + throw new Exception("Cannot sync a non-cached database!"); + } + StatusLogger.UpdateMessage(UiStringKey.SynchronizingCachedDatabase); + CachingFileStorage cachingFileStorage = (CachingFileStorage) fileStorage; + + //download file from remote location and calculate hash: + StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile)); + string hash; + + MemoryStream remoteData; + try + { + remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash); + Kp2aLog.Log("Checking for file change. Current hash = " + hash); + } + catch (FileNotFoundException) + { + StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.RestoringRemoteFile)); + cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions)); + Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully)); + Kp2aLog.Log("Checking for file change: file not found"); + return; + } + + //check if remote file was modified: + var baseVersionHash = cachingFileStorage.GetBaseVersionHash(ioc); + Kp2aLog.Log("Checking for file change. baseVersionHash = " + baseVersionHash); + if (baseVersionHash != hash) + { + //remote file is modified + if (cachingFileStorage.HasLocalChanges(ioc)) + { + //conflict! need to merge + _saveDb = new SaveDb(_context, _app, new ActionOnFinish(ActiveActivity, (success, result, activity) => + { + if (!success) + { + Finish(false, result); + } + else + { + Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully)); + } + _saveDb = null; + }), _app.CurrentDb, false, remoteData); + _saveDb.Run(); + + _app.CurrentDb.UpdateGlobals(); + + _app.MarkAllGroupsAsDirty(); + } + else + { + //only the remote file was modified -> reload database. + //note: it's best to lock the database and do a complete reload here (also better for UI consistency in case something goes wrong etc.) + _app.TriggerReload(_context, (bool result) => Finish(result)); + } + } + else + { + //remote file is unmodified + if (cachingFileStorage.HasLocalChanges(ioc)) + { + //but we have local changes -> upload: + StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.UploadingFile)); + cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions)); + StatusLogger.UpdateSubMessage(""); + Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully)); + } + else + { + //files are in sync: just set the result + Finish(true, _app.GetResourceString(UiStringKey.FilesInSync)); + } + } + } + catch (Exception e) + { + Kp2aLog.LogUnexpectedError(e); + Finish(false, e.Message); + } + + } + + public void JoinWorkerThread() + { + if (_saveDb != null) + _saveDb.JoinWorkerThread(); + } + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/ActionOnFinish.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/ActionOnFinish.cs new file mode 100644 index 00000000..f06a3ae8 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/ActionOnFinish.cs @@ -0,0 +1,57 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System; +using Android.App; +using Android.OS; + +namespace keepass2android +{ + public class ActionOnFinish: OnFinish + { + public delegate void ActionToPerformOnFinsh(bool success, String message, Activity activeActivity); + + readonly ActionToPerformOnFinsh _actionToPerform; + + public ActionOnFinish(Activity activity, ActionToPerformOnFinsh actionToPerform) : base(activity, null, null) + { + _actionToPerform = actionToPerform; + } + + public ActionOnFinish(Activity activity, ActionToPerformOnFinsh actionToPerform, OnFinish finish) : base(activity, finish) + { + _actionToPerform = actionToPerform; + } + + //if set to true, the previously active active will be passed to ActionToPerformOnFinish instead null if no activity is on foreground + public bool AllowInactiveActivity { get; set; } + + public override void Run() + { + if (Message == null) + Message = ""; + if (Handler != null) + { + Handler.Post(() => {_actionToPerform(Success, Message, ActiveActivity);}); + } + else + _actionToPerform(Success, Message, AllowInactiveActivity ? (ActiveActivity ?? PreviouslyActiveActivity) : ActiveActivity); + base.Run(); + } + } +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/AddEntry.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/AddEntry.cs new file mode 100644 index 00000000..03dc9daa --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/AddEntry.cs @@ -0,0 +1,111 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using Android.App; +using Android.Content; +using KeePassLib; + +namespace keepass2android +{ + public class AddEntry : RunnableOnFinish { + protected Database Db + { + get { return _app.CurrentDb; } + } + + private readonly IKp2aApp _app; + private readonly PwEntry _entry; + private readonly PwGroup _parentGroup; + private readonly Activity _ctx; + private readonly Database _db; + + public static AddEntry GetInstance(Activity ctx, IKp2aApp app, PwEntry entry, PwGroup parentGroup, OnFinish finish, Database db) { + + return new AddEntry(ctx, db, app, entry, parentGroup, finish); + } + + public AddEntry(Activity ctx, Database db, IKp2aApp app, PwEntry entry, PwGroup parentGroup, OnFinish finish):base(ctx, finish) { + _ctx = ctx; + _db = db; + _parentGroup = parentGroup; + _app = app; + _entry = entry; + + _onFinishToRun = new AfterAdd(ctx, app.CurrentDb, entry, app,OnFinishToRun); + } + + + public override void Run() { + StatusLogger.UpdateMessage(UiStringKey.AddingEntry); + + //make sure we're not adding the entry if it was added before. + //(this might occur in very rare cases where the user dismissis the save dialog + //by rotating the screen while saving and then presses save again) + if (_parentGroup.FindEntry(_entry.Uuid, false) == null) + { + _parentGroup.AddEntry(_entry, true); + } + + // Add entry to global + _db.EntriesById[_entry.Uuid] = _entry; + _db.Elements.Add(_entry); + + // Commit to disk + SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun); + save.SetStatusLogger(StatusLogger); + save.Run(); + } + + private class AfterAdd : OnFinish { + private readonly Database _db; + private readonly PwEntry _entry; + private readonly IKp2aApp _app; + + public AfterAdd(Activity activity, Database db, PwEntry entry, IKp2aApp app, OnFinish finish):base(activity, finish) { + _db = db; + _entry = entry; + _app = app; + } + + + + public override void Run() { + if ( Success ) { + + PwGroup parent = _entry.ParentGroup; + + // Mark parent group dirty + _app.DirtyGroups.Add(parent); + + + + } else + { + StatusLogger.UpdateMessage(UiStringKey.UndoingChanges); + //TODO test fail + _entry.ParentGroup.Entries.Remove(_entry); + } + + base.Run(); + } + } + + + } + +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/AddGroup.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/AddGroup.cs new file mode 100644 index 00000000..5c96cf84 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/AddGroup.cs @@ -0,0 +1,114 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System; +using Android.App; +using Android.Content; +using KeePassLib; + +namespace keepass2android +{ + + public class AddGroup : RunnableOnFinish { + internal Database Db + { + get { return _app.CurrentDb; } + } + + public IKp2aApp App { get => _app; } + + private IKp2aApp _app; + private readonly String _name; + private readonly int _iconId; + private readonly PwUuid _groupCustomIconId; + public PwGroup Group; + internal PwGroup Parent; + protected bool DontSave; + readonly Activity _ctx; + + + public static AddGroup GetInstance(Activity ctx, IKp2aApp app, string name, int iconid, PwUuid groupCustomIconId, PwGroup parent, OnFinish finish, bool dontSave) { + return new AddGroup(ctx, app, name, iconid, groupCustomIconId, parent, finish, dontSave); + } + + + private AddGroup(Activity ctx, IKp2aApp app, String name, int iconid, PwUuid groupCustomIconId, PwGroup parent, OnFinish finish, bool dontSave) + : base(ctx, finish) + { + _ctx = ctx; + _name = name; + _iconId = iconid; + _groupCustomIconId = groupCustomIconId; + Parent = parent; + DontSave = dontSave; + _app = app; + + _onFinishToRun = new AfterAdd(ctx, this, OnFinishToRun); + } + + + public override void Run() { + StatusLogger.UpdateMessage(UiStringKey.AddingGroup); + // Generate new group + Group = new PwGroup(true, true, _name, (PwIcon)_iconId); + if (_groupCustomIconId != null) + { + Group.CustomIconUuid = _groupCustomIconId; + } + Parent.AddGroup(Group, true); + _app.CurrentDb.GroupsById[Group.Uuid] = Group; + _app.CurrentDb.Elements.Add(Group); + + // Commit to disk + SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun, DontSave); + save.SetStatusLogger(StatusLogger); + save.Run(); + } + + private class AfterAdd : OnFinish { + readonly AddGroup _addGroup; + + public AfterAdd(Activity activity, AddGroup addGroup,OnFinish finish): base(activity, finish) { + _addGroup = addGroup; + } + + + public override void Run() { + + if ( Success ) { + // Mark parent group dirty + _addGroup.App.DirtyGroups.Add(_addGroup.Parent); + + // Add group to global list + _addGroup.Db.GroupsById[_addGroup.Group.Uuid] = _addGroup.Group; + _addGroup.Db.Elements.Add(_addGroup.Group); + } else { + StatusLogger.UpdateMessage(UiStringKey.UndoingChanges); + _addGroup.Parent.Groups.Remove(_addGroup.Group); + + } + + base.Run(); + } + + } + + + } + +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/AddTemplateEntries.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/AddTemplateEntries.cs new file mode 100644 index 00000000..f1d53da3 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/AddTemplateEntries.cs @@ -0,0 +1,399 @@ +/* +This file is part of Keepass2Android, Copyright 2016 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using Android.App; +using Android.Content; +using KeePassLib; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace keepass2android +{ + public class AddTemplateEntries : RunnableOnFinish { + + public class TemplateEntry + { + public UiStringKey Title { get; set; } + public PwIcon Icon { get; set; } + + public PwUuid Uuid { get; set; } + + public List Fields + { + get; + set; + } + + + public interface ITemplateField + { + void AddToEntry(IKp2aApp app, PwEntry entry, int position); + } + + public enum FieldType + { + Inline, ProtectedInline + } + + public enum SpecialFieldKey + { + ExpDate, OverrideUrl, Tags + } + + public class CustomField : ITemplateField + { + public UiStringKey FieldName { get; set; } + + public FieldType Type { get; set; } + + + public void AddToEntry(IKp2aApp app, PwEntry entry, int position) + { + Dictionary fn = new Dictionary() + { + { FieldType.ProtectedInline, "Protected Inline"}, + { FieldType.Inline, "Inline"} + }; + + string fieldKey = app.GetResourceString(FieldName); + entry.Strings.Set("_etm_position_"+fieldKey, new ProtectedString(false, position.ToString())); + entry.Strings.Set("_etm_title_"+fieldKey, new ProtectedString(false, fieldKey)); + entry.Strings.Set("_etm_type_"+fieldKey, new ProtectedString(false, fn[Type])); + } + } + + public class StandardField : ITemplateField + { + public string FieldName { get; set; } + public void AddToEntry(IKp2aApp app, PwEntry entry, int position) + { + string fieldKey = FieldName; + entry.Strings.Set("_etm_position_"+fieldKey, new ProtectedString(false, position.ToString())); + entry.Strings.Set("_etm_title_"+fieldKey, new ProtectedString(false, fieldKey)); + entry.Strings.Set("_etm_type_"+fieldKey, new ProtectedString(false, FieldName == PwDefs.PasswordField ? "Protected Inline" : "Inline")); + } + } + + public class SpecialField : ITemplateField + { + public SpecialFieldKey FieldName { get; set; } + + + public const string TagsKey = "@tags"; + public const string OverrideUrlKey = "@override"; + public const string ExpDateKey = "@exp_date"; + + public void AddToEntry(IKp2aApp app, PwEntry entry, int position) + { + string fieldKey = ""; + string type = "Inline"; + switch (FieldName) + { + case SpecialFieldKey.ExpDate: + fieldKey = ExpDateKey; + type = "Date Time"; + break; + case SpecialFieldKey.OverrideUrl: + fieldKey = OverrideUrlKey; + break; + case SpecialFieldKey.Tags: + fieldKey = TagsKey; + break; + } + entry.Strings.Set("_etm_position_" + fieldKey, new ProtectedString(false, position.ToString())); + entry.Strings.Set("_etm_title_" + fieldKey, new ProtectedString(false, fieldKey)); + entry.Strings.Set("_etm_type_" + fieldKey, new ProtectedString(false, type)); + } + } + } + + protected Database Db + { + get { return _app.CurrentDb; } + } + + private readonly IKp2aApp _app; + private readonly Activity _ctx; + + public AddTemplateEntries(Activity ctx, IKp2aApp app, OnFinish finish) + : base(ctx, finish) + { + _ctx = ctx; + _app = app; + + //_onFinishToRun = new AfterAdd(this, OnFinishToRun); + } + + public static readonly List TemplateEntries = new List() + { + new TemplateEntry() + { + Title = UiStringKey.TemplateTitle_IdCard, + Icon = PwIcon.Identity, + Uuid = new PwUuid(MemUtil.HexStringToByteArray("A7B525BD0CECC84EB9F0CEDC0B49B5B8")), + Fields = new List() + { + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_Number, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_IdCard_Name, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_IdCard_PlaceOfIssue, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_IdCard_IssueDate, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.SpecialField() + { + FieldName = TemplateEntry.SpecialFieldKey.ExpDate + } + } + }, + new TemplateEntry() + { + Title = UiStringKey.TemplateTitle_EMail, + Icon = PwIcon.EMail, + Uuid = new PwUuid(MemUtil.HexStringToByteArray("0B84EC3029E330478CD99B670942295B")), + Fields = new List() + { + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_EMail_EMail, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.StandardField() + { + FieldName = PwDefs.UrlField + }, + new TemplateEntry.StandardField() + { + FieldName = PwDefs.PasswordField + } + + } + }, + new TemplateEntry() + { + Title = UiStringKey.TemplateTitle_WLan, + Icon = PwIcon.IRCommunication, + Uuid = new PwUuid(MemUtil.HexStringToByteArray("46B56A7E90407545B646E8DC488A5FA2")), + Fields = new List() + { + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_WLan_SSID, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.StandardField() + { + FieldName = PwDefs.PasswordField + } + } + }, + + new TemplateEntry() + { + Title = UiStringKey.TemplateTitle_Notes, + Icon = PwIcon.Notepad, + Uuid = new PwUuid(MemUtil.HexStringToByteArray("10F8C25C26AE9B49A47FDA7CDACACEE2")), + Fields = new List() + { + new TemplateEntry.StandardField() + { + FieldName = PwDefs.NotesField + } + } + }, + + new TemplateEntry() + { + Title = UiStringKey.TemplateTitle_CreditCard, + Icon = PwIcon.Homebanking, + Uuid = new PwUuid(MemUtil.HexStringToByteArray("49DD48DBFF149445B3392CE90EA75309")), + Fields = new List() + { + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_Number, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_CreditCard_CVV, + Type = TemplateEntry.FieldType.ProtectedInline + }, + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_CreditCard_PIN, + Type = TemplateEntry.FieldType.ProtectedInline + }, + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_CreditCard_Owner, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.SpecialField() { FieldName = TemplateEntry.SpecialFieldKey.ExpDate} + } + }, + new TemplateEntry() + { + Title = UiStringKey.TemplateTitle_Membership, + Icon = PwIcon.UserKey, + Uuid = new PwUuid(MemUtil.HexStringToByteArray("DD5C627BC66C28498FDEC70740D29168")), + Fields = new List() + { + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_Number, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.StandardField() + { + FieldName = PwDefs.UrlField + }, + new TemplateEntry.SpecialField() + { + FieldName = TemplateEntry.SpecialFieldKey.ExpDate + } +}} + + }; + + public static bool ContainsAllTemplates(Database db) + { + return TemplateEntries.All(t => + { + string hexId = t.Uuid.ToHexString(); + + return db.EntriesById.Any(kvp => kvp.Key.Equals(t.Uuid) || + kvp.Value.Strings.ReadSafe(TemplateIdStringKey) == hexId); + }); + } + + public static string TemplateIdStringKey + { + get { return "KP2A_TemplateId"; } + } + + public override void Run() { + StatusLogger.UpdateMessage(UiStringKey.AddingEntry); + + List addedEntries; + var templateGroup = AddTemplates(out addedEntries); + + if (addedEntries.Any()) + { + _app.DirtyGroups.Add(templateGroup); + + // Commit to disk + SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun); + save.SetStatusLogger(StatusLogger); + save.Run(); + } + } + + public PwGroup AddTemplates(out List addedEntries) + { + if (TemplateEntries.GroupBy(e => e.Uuid).Any(g => g.Count() > 1)) + { + throw new Exception("invalid UUIDs in template list!"); + } + + PwGroup templateGroup; + if (!_app.CurrentDb.GroupsById.TryGetValue(_app.CurrentDb.KpDatabase.EntryTemplatesGroup, out templateGroup)) + { + //create template group + templateGroup = new PwGroup(true, true, _app.GetResourceString(UiStringKey.TemplateGroupName), PwIcon.Folder); + _app.CurrentDb.KpDatabase.RootGroup.AddGroup(templateGroup, true); + _app.CurrentDb.KpDatabase.EntryTemplatesGroup = templateGroup.Uuid; + _app.CurrentDb.KpDatabase.EntryTemplatesGroupChanged = DateTime.Now; + _app.DirtyGroups.Add(_app.CurrentDb.KpDatabase.RootGroup); + _app.CurrentDb.GroupsById[templateGroup.Uuid] = templateGroup; + _app.CurrentDb.Elements.Add(templateGroup); + + } + addedEntries = new List(); + + foreach (var template in TemplateEntries) + { + if (_app.CurrentDb.EntriesById.ContainsKey(template.Uuid)) + continue; + PwEntry entry = CreateEntry(template); + templateGroup.AddEntry(entry, true); + addedEntries.Add(entry); + _app.CurrentDb.EntriesById[entry.Uuid] = entry; + } + return templateGroup; + } + + private PwEntry CreateEntry(TemplateEntry template) + { + PwEntry entry = new PwEntry(false, true); + entry.Uuid = template.Uuid; + entry.IconId = template.Icon; + entry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, _app.GetResourceString(template.Title))); + entry.Strings.Set("_etm_template", new ProtectedString(false, "1")); + int position = 0; + foreach (var field in template.Fields) + { + field.AddToEntry(_app, entry, position); + position++; + } + return entry; + } + + private class AfterAdd : OnFinish { + private readonly Database _db; + private readonly List _entries; + + public AfterAdd(Activity activity, Database db, List entries, OnFinish finish):base(activity, finish) { + _db = db; + _entries = entries; + + } + + + + public override void Run() { + + + base.Run(); + } + } + + + public static bool IsTemplateId(PwUuid pwUuid) + { + return TemplateEntries.Any(te => te.Uuid.Equals(pwUuid)); + } + } + +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/CopyEntry.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/CopyEntry.cs new file mode 100644 index 00000000..fe493763 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/CopyEntry.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; +using KeePassLib; +using KeePassLib.Security; + +namespace keepass2android.database.edit +{ + public class CopyEntry: AddEntry + { + public CopyEntry(Activity ctx, IKp2aApp app, PwEntry entry, OnFinish finish, Database db) + : base(ctx, db, app, CreateCopy(entry, app), entry.ParentGroup, finish) + { + } + + private static PwEntry CreateCopy(PwEntry entry, IKp2aApp app) + { + var newEntry = entry.CloneDeep(); + newEntry.SetUuid(new PwUuid(true), true); // Create new UUID + string strTitle = newEntry.Strings.ReadSafe(PwDefs.TitleField); + newEntry.Strings.Set(PwDefs.TitleField, new ProtectedString( + false, strTitle + " - " + app.GetResourceString(UiStringKey.DuplicateTitle))); + + return newEntry; + } + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/CreateDB.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/CreateDB.cs new file mode 100644 index 00000000..c007d8d7 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/CreateDB.cs @@ -0,0 +1,103 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System.Collections.Generic; +using Android.App; +using Android.Content; +using KeePassLib; +using KeePassLib.Cryptography.KeyDerivation; +using KeePassLib.Serialization; +using KeePassLib.Keys; + +namespace keepass2android +{ + + public class CreateDb : RunnableOnFinish { + private readonly IOConnectionInfo _ioc; + private readonly bool _dontSave; + private readonly Activity _ctx; + private readonly IKp2aApp _app; + private CompositeKey _key; + private readonly bool _makeCurrent; + + public CreateDb(IKp2aApp app, Activity ctx, IOConnectionInfo ioc, OnFinish finish, bool dontSave, bool makeCurrent): base(ctx, finish) { + _ctx = ctx; + _ioc = ioc; + _dontSave = dontSave; + _makeCurrent = makeCurrent; + _app = app; + } + + public CreateDb(IKp2aApp app, Activity ctx, IOConnectionInfo ioc, OnFinish finish, bool dontSave, CompositeKey key, bool makeCurrent) + : base(ctx, finish) + { + _ctx = ctx; + _ioc = ioc; + _dontSave = dontSave; + _app = app; + _key = key; + _makeCurrent = makeCurrent; + } + + + public override void Run() { + StatusLogger.UpdateMessage(UiStringKey.progress_create); + Database db = _app.CreateNewDatabase(_makeCurrent); + + db.KpDatabase = new KeePassLib.PwDatabase(); + + if (_key == null) + { + _key = new CompositeKey(); //use a temporary key which should be changed after creation + } + + db.KpDatabase.New(_ioc, _key, _app.GetFileStorage(_ioc).GetFilenameWithoutPathAndExt(_ioc)); + + db.KpDatabase.KdfParameters = (new AesKdf()).GetDefaultParameters(); + db.KpDatabase.Name = "Keepass2Android Password Database"; + //re-set the name of the root group because the PwDatabase uses UrlUtil which is not appropriate for all file storages: + db.KpDatabase.RootGroup.Name = _app.GetFileStorage(_ioc).GetFilenameWithoutPathAndExt(_ioc); + + // Set Database state + db.Root = db.KpDatabase.RootGroup; + db.SearchHelper = new SearchDbHelper(_app); + + // Add a couple default groups + AddGroup internet = AddGroup.GetInstance(_ctx, _app, "Internet", 1, null, db.KpDatabase.RootGroup, null, true); + internet.Run(); + AddGroup email = AddGroup.GetInstance(_ctx, _app, "eMail", 19, null, db.KpDatabase.RootGroup, null, true); + email.Run(); + + List addedEntries; + AddTemplateEntries addTemplates = new AddTemplateEntries(_ctx, _app, null); + addTemplates.AddTemplates(out addedEntries); + + // Commit changes + SaveDb save = new SaveDb(_ctx, _app, db, OnFinishToRun, _dontSave); + save.SetStatusLogger(StatusLogger); + _onFinishToRun = null; + save.Run(); + + db.UpdateGlobals(); + + + } + + } + +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/DeleteEntry.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/DeleteEntry.cs new file mode 100644 index 00000000..b8f2e5e2 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/DeleteEntry.cs @@ -0,0 +1,75 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System; +using System.Collections.Generic; +using Android.App; +using Android.Content; +using KeePassLib; +using KeePassLib.Interfaces; + +namespace keepass2android +{ + public class DeleteEntry : DeleteRunnable { + + private readonly PwEntry _entry; + private UiStringKey _statusMessage; + + public DeleteEntry(Activity activiy, IKp2aApp app, PwEntry entry, OnFinish finish):base(activiy, finish, app) { + Ctx = activiy; + Db = app.FindDatabaseForElement(entry); + _entry = entry; + + } + + public override bool CanRecycle + { + get + { + return Db.DatabaseFormat.CanRecycle && CanRecycleGroup(_entry.ParentGroup); + } + } + + protected override UiStringKey QuestionRecycleResourceId + { + get + { + return UiStringKey.AskDeletePermanentlyEntry; + } + } + + protected override UiStringKey QuestionNoRecycleResourceId + { + get + { + return UiStringKey.AskDeletePermanentlyEntryNoRecycle; + } + } + + protected override void PerformDelete(List touchedGroups, List permanentlyDeletedGroups) + { + DoDeleteEntry(_entry, touchedGroups); + } + + public override UiStringKey StatusMessage + { + get { return UiStringKey.DeletingEntry; } + } + } + +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/DeleteGroup.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/DeleteGroup.cs new file mode 100644 index 00000000..87d541a8 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/DeleteGroup.cs @@ -0,0 +1,90 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System; +using System.Collections.Generic; +using Android.App; +using Android.Content; +using KeePassLib; + +namespace keepass2android +{ + + public class DeleteGroup : DeleteRunnable { + + private PwGroup _group; + protected bool DontSave; + + public DeleteGroup(Activity activity, IKp2aApp app, PwGroup group, OnFinish finish) + : base(activity, finish, app) + { + SetMembers(activity, app, group, false); + } + /* + public DeleteGroup(Context ctx, Database db, PwGroup group, Activity act, OnFinish finish, bool dontSave) + : base(finish) + { + SetMembers(ctx, db, group, act, dontSave); + } + + public DeleteGroup(Context ctx, Database db, PwGroup group, OnFinish finish, bool dontSave):base(finish) { + SetMembers(ctx, db, group, null, dontSave); + } + */ + private void SetMembers(Activity activity, IKp2aApp app, PwGroup group, bool dontSave) + { + base.SetMembers(activity, app.FindDatabaseForElement(group)); + + _group = group; + DontSave = dontSave; + + } + + public override bool CanRecycle + { + get + { + return Db.DatabaseFormat.CanRecycle && CanRecycleGroup(_group); + } + } + + protected override UiStringKey QuestionRecycleResourceId + { + get + { + return UiStringKey.AskDeletePermanentlyGroup; + } + } + + protected override UiStringKey QuestionNoRecycleResourceId + { + get { return UiStringKey.AskDeletePermanentlyGroupNoRecycle; } + } + + protected override void PerformDelete(List touchedGroups, List permanentlyDeletedGroups) + { + DoDeleteGroup(_group, touchedGroups, permanentlyDeletedGroups); + } + + public override UiStringKey StatusMessage + { + get { return UiStringKey.DeletingGroup; } + } + } + +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/DeleteMultipleItemsFromOneDatabase.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/DeleteMultipleItemsFromOneDatabase.cs new file mode 100644 index 00000000..f31affcd --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/DeleteMultipleItemsFromOneDatabase.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Linq; +using Android.App; +using Android.Content; +using KeePassLib; +using KeePassLib.Interfaces; + +namespace keepass2android +{ + public class DeleteMultipleItemsFromOneDatabase : DeleteRunnable + { + private readonly List _elementsToDelete; + private readonly bool _canRecycle; + + public DeleteMultipleItemsFromOneDatabase(Activity activity, Database db, List elementsToDelete, OnFinish finish, IKp2aApp app) + : base(activity, finish, app) + { + _elementsToDelete = elementsToDelete; + SetMembers(activity, db); + + //determine once. The property is queried for each delete operation, but might return false + //after one entry/group is deleted (and thus in recycle bin and thus can't be recycled anymore) + _canRecycle = DetermineCanRecycle(); + ShowDatabaseIocInStatus = true; + } + + private bool DetermineCanRecycle() + { + Android.Util.Log.Debug("KP2A", "CanRecycle?"); + if (!Db.DatabaseFormat.CanRecycle) + { + Android.Util.Log.Debug("KP2A", "CanRecycle? No because of DB format."); + return false; + } + + + if (_elementsToDelete.OfType().Any(g => !CanRecycleGroup(g))) + { + return false; + } + + if (_elementsToDelete.OfType().Any(e => !CanRecycleGroup(e.ParentGroup))) + { + return false; + } + Android.Util.Log.Debug("KP2A", "CanRecycle? Yes."); + return true; + } + + public override bool CanRecycle + { + get { return _canRecycle; } + } + + protected override UiStringKey QuestionRecycleResourceId + { + get { return UiStringKey.AskDeletePermanentlyItems; } + } + + protected override UiStringKey QuestionNoRecycleResourceId + { + get { return UiStringKey.AskDeletePermanentlyItemsNoRecycle; } + } + + protected override void PerformDelete(List touchedGroups, List permanentlyDeletedGroups) + { + foreach (var g in _elementsToDelete.OfType()) + { + Android.Util.Log.Debug("KP2A", "Deleting " + g.Name); + DoDeleteGroup(g, touchedGroups, permanentlyDeletedGroups); + + } + + foreach (var e in _elementsToDelete.OfType()) + { + Android.Util.Log.Debug("KP2A", "Deleting " + e.Strings.ReadSafe(PwDefs.TitleField)); + DoDeleteEntry(e, touchedGroups); + } + } + + public override UiStringKey StatusMessage + { + get { return UiStringKey.DeletingItems; } + } + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/DeleteRunnable.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/DeleteRunnable.cs new file mode 100644 index 00000000..9f1ce5a2 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/DeleteRunnable.cs @@ -0,0 +1,305 @@ +using System; +using System.Collections.Generic; +using Android.App; +using Android.Content; +using KeePassLib; + +namespace keepass2android +{ + public abstract class DeleteRunnable : RunnableOnFinish + { + protected DeleteRunnable(Activity activity, OnFinish finish, IKp2aApp app) + : base(activity, finish) + { + App = app; + } + + protected IKp2aApp App; + + protected Database Db; + + protected Activity Ctx; + + protected void SetMembers(Activity activity, Database db) + { + Ctx = activity; + Db = db; + } + + + private bool _deletePermanently = true; + + public bool DeletePermanently + { + get + { + return _deletePermanently; + } + set + { + _deletePermanently = value; + } + } + + public abstract bool CanRecycle + { + get; + } + + protected bool CanRecycleGroup(PwGroup pgParent) + { + PwDatabase pd = Db.KpDatabase; + PwGroup pgRecycleBin = pd.RootGroup.FindGroup(pd.RecycleBinUuid, true); + bool bPermanent = false; + if (pgParent != null) + { + if (pd.RecycleBinEnabled == false) + { + Android.Util.Log.Debug("KP2A", "CanRecycle? No, RecycleBinIsNotEnabled"); + bPermanent = true; + } + + else if (pgRecycleBin == null) + { + } // Recycle + else if (pgParent == pgRecycleBin) + { + Android.Util.Log.Debug("KP2A", "CanRecycle? No, Can't recycle RecycleBin"); + bPermanent = true; + } + + else if (pgParent.IsContainedIn(pgRecycleBin)) + { + Android.Util.Log.Debug("KP2A", "CanRecycle? No, "+pgParent.Name+" is in RecycleBin"); + bPermanent = true; + } + + } + return !bPermanent; + } + + + protected void EnsureRecycleBinExists(ref PwGroup pgRecycleBin, + ref bool bGroupListUpdateRequired) + { + if ((Db == null) || (Db.KpDatabase == null)) { return; } + + if (pgRecycleBin == Db.KpDatabase.RootGroup) + { + pgRecycleBin = null; + } + + if (pgRecycleBin == null) + { + pgRecycleBin = new PwGroup(true, true, App.GetResourceString(UiStringKey.RecycleBin), + PwIcon.TrashBin) + { + EnableAutoType = false, + EnableSearching = false, + IsExpanded = false + }; + + Db.KpDatabase.RootGroup.AddGroup(pgRecycleBin, true); + Db.GroupsById[pgRecycleBin.Uuid] = pgRecycleBin; + Db.Elements.Add(pgRecycleBin); + Db.KpDatabase.RecycleBinUuid = pgRecycleBin.Uuid; + + bGroupListUpdateRequired = true; + } + else { System.Diagnostics.Debug.Assert(pgRecycleBin.Uuid.Equals(Db.KpDatabase.RecycleBinUuid)); } + } + + protected abstract UiStringKey QuestionRecycleResourceId + { + get; + } + + protected abstract UiStringKey QuestionNoRecycleResourceId + { + get; + } + + + public void Start() + { + string messageSuffix = ShowDatabaseIocInStatus ? "(" + App.GetFileStorage(Db.Ioc).GetDisplayName(Db.Ioc) + ")" : ""; + + if (CanRecycle) + { + App.AskYesNoCancel(UiStringKey.AskDeletePermanently_title, + QuestionRecycleResourceId, + (dlgSender, dlgEvt) => + { + DeletePermanently = true; + ProgressTask pt = new ProgressTask(App, Ctx, this); + pt.Run(); + + }, + (dlgSender, dlgEvt) => + { + DeletePermanently = false; + ProgressTask pt = new ProgressTask(App, Ctx, this); + pt.Run(); + }, + (dlgSender, dlgEvt) => { }, + Ctx, messageSuffix); + + + + } + else + { + App.AskYesNoCancel(UiStringKey.AskDeletePermanently_title, + QuestionNoRecycleResourceId, + (dlgSender, dlgEvt) => + { + ProgressTask pt = new ProgressTask(App, Ctx, this); + pt.Run(); + }, + null, + (dlgSender, dlgEvt) => { }, + Ctx, messageSuffix); + + + } + } + + + protected void DoDeleteEntry(PwEntry pe, List touchedGroups) + { + PwDatabase pd = Db.KpDatabase; + + PwGroup pgRecycleBin = pd.RootGroup.FindGroup(pd.RecycleBinUuid, true); + + bool bUpdateGroupList = false; + DateTime dtNow = DateTime.Now; + + PwGroup pgParent = pe.ParentGroup; + if (pgParent != null) + { + pgParent.Entries.Remove(pe); + //TODO check if RecycleBin is deleted + //TODO no recycle bin in KDB + + if ((DeletePermanently) || (!CanRecycle)) + { + PwDeletedObject pdo = new PwDeletedObject(pe.Uuid, dtNow); + pd.DeletedObjects.Add(pdo); + touchedGroups.Add(pgParent); + Db.EntriesById.Remove(pe.Uuid); + Db.Elements.Remove(pe); + } + else // Recycle + { + EnsureRecycleBinExists(ref pgRecycleBin, ref bUpdateGroupList); + + pgRecycleBin.AddEntry(pe, true, true); + pe.Touch(false); + + touchedGroups.Add(pgParent); + // Mark new parent dirty + touchedGroups.Add(pgRecycleBin); + // mark root dirty if recycle bin was created + touchedGroups.Add(Db.Root); + } + } + } + + + public override void Run() + { + StatusLogger.UpdateMessage(StatusMessage); + + List touchedGroups = new List(); + List permanentlyDeletedGroups = new List(); + Android.Util.Log.Debug("KP2A", "Calling PerformDelete.."); + PerformDelete(touchedGroups, permanentlyDeletedGroups); + + _onFinishToRun = new ActionOnFinish(ActiveActivity,(success, message, activity) => + { + if (success) + { + foreach (var g in touchedGroups) + App.DirtyGroups.Add(g); + foreach (var g in permanentlyDeletedGroups) + { + //remove groups from global lists if present there + App.DirtyGroups.Remove(g); + Db.GroupsById.Remove(g.Uuid); + Db.Elements.Remove(g); + + } + + } + else + { + // Let's not bother recovering from a failure to save. It is too much work. + App.Lock(false, false); + } + }, OnFinishToRun); + + // Commit database + SaveDb save = new SaveDb(Ctx, App, Db, OnFinishToRun, false); + save.ShowDatabaseIocInStatus = ShowDatabaseIocInStatus; + + save.SetStatusLogger(StatusLogger); + save.Run(); + + + } + + public bool ShowDatabaseIocInStatus + { + get; + set; + } + + protected abstract void PerformDelete(List touchedGroups, List permanentlyDeletedGroups); + + public abstract UiStringKey StatusMessage { get; } + + protected bool DoDeleteGroup(PwGroup pg, List touchedGroups, List permanentlyDeletedGroups) + { + PwGroup pgParent = pg.ParentGroup; + if (pgParent == null) return false; + + PwDatabase pd = Db.KpDatabase; + PwGroup pgRecycleBin = pd.RootGroup.FindGroup(pd.RecycleBinUuid, true); + + if (pg.Uuid.Equals(pd.EntryTemplatesGroup)) + { + pd.EntryTemplatesGroup = PwUuid.Zero; + pd.EntryTemplatesGroupChanged = DateTime.Now; + } + + pgParent.Groups.Remove(pg); + touchedGroups.Add(pgParent); + if ((DeletePermanently) || (!CanRecycle)) + { + pg.DeleteAllObjects(pd); + + PwDeletedObject pdo = new PwDeletedObject(pg.Uuid, DateTime.Now); + pd.DeletedObjects.Add(pdo); + + + permanentlyDeletedGroups.Add(pg); + + } + else // Recycle + { + bool groupListUpdateRequired = false; + EnsureRecycleBinExists(ref pgRecycleBin, ref groupListUpdateRequired); + + pgRecycleBin.AddGroup(pg, true, true); + pg.Touch(false); + // Mark new parent (Recycle bin) touched + touchedGroups.Add(pg.ParentGroup); + // mark root touched if recycle bin was created + if (groupListUpdateRequired) + touchedGroups.Add(Db.Root); + } + return true; + } + } +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/EditGroup.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/EditGroup.cs new file mode 100644 index 00000000..2ef34b7d --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/EditGroup.cs @@ -0,0 +1,97 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System; +using Android.App; +using Android.Content; +using KeePassLib; + +namespace keepass2android +{ + + public class EditGroup : RunnableOnFinish { + internal Database Db + { + get { return _app.FindDatabaseForElement(Group); } + } + + public IKp2aApp App { get => _app; } + + private IKp2aApp _app; + private readonly String _name; + private readonly PwIcon _iconId; + private readonly PwUuid _customIconId; + internal PwGroup Group; + readonly Activity _ctx; + + public EditGroup(Activity ctx, IKp2aApp app, String name, PwIcon iconid, PwUuid customIconId, PwGroup group, OnFinish finish) + : base(ctx, finish) + { + _ctx = ctx; + _name = name; + _iconId = iconid; + Group = group; + _customIconId = customIconId; + _app = app; + + _onFinishToRun = new AfterEdit(ctx, this, OnFinishToRun); + } + + + public override void Run() { + // modify group: + Group.Name = _name; + Group.IconId = _iconId; + Group.CustomIconUuid = _customIconId; + Group.Touch(true); + + // Commit to disk + SaveDb save = new SaveDb(_ctx, _app, Db, OnFinishToRun); + save.SetStatusLogger(StatusLogger); + save.Run(); + } + + private class AfterEdit : OnFinish { + readonly EditGroup _editGroup; + + public AfterEdit(Activity ctx, EditGroup editGroup, OnFinish finish) + : base(ctx, finish) + { + _editGroup = editGroup; + } + + + public override void Run() { + + if ( Success ) { + // Mark parent group dirty + _editGroup.App.DirtyGroups.Add(_editGroup.Group.ParentGroup); + } else + { + _editGroup._app.Lock(false, false); + } + + base.Run(); + } + + } + + + } + +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/FileOnFinish.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/FileOnFinish.cs new file mode 100644 index 00000000..b6f9da21 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/FileOnFinish.cs @@ -0,0 +1,37 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System; +using Android.App; + +namespace keepass2android +{ + + public abstract class FileOnFinish : OnFinish { + private String _filename = ""; + + protected FileOnFinish(Activity activity, FileOnFinish finish):base(activity, finish) { + } + + public string Filename + { + get { return _filename; } + set { _filename = value; } + } + } +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/LoadDB.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/LoadDB.cs new file mode 100644 index 00000000..3c476abd --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/LoadDB.cs @@ -0,0 +1,193 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Android.App; +using keepass2android.database.edit; +using KeePassLib; +using KeePassLib.Keys; +using KeePassLib.Serialization; + +namespace keepass2android +{ + public class LoadDb : RunnableOnFinish { + private readonly IOConnectionInfo _ioc; + private readonly Task _databaseData; + private readonly CompositeKey _compositeKey; + private readonly string _keyfileOrProvider; + private readonly IKp2aApp _app; + private readonly bool _rememberKeyfile; + IDatabaseFormat _format; + + public LoadDb(Activity activity, IKp2aApp app, IOConnectionInfo ioc, Task databaseData, CompositeKey compositeKey, String keyfileOrProvider, OnFinish finish, bool updateLastUsageTimestamp, bool makeCurrent): base(activity, finish) + { + _app = app; + _ioc = ioc; + _databaseData = databaseData; + _compositeKey = compositeKey; + _keyfileOrProvider = keyfileOrProvider; + _updateLastUsageTimestamp = updateLastUsageTimestamp; + _makeCurrent = makeCurrent; + + + _rememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile); + } + + protected bool success = false; + private bool _updateLastUsageTimestamp; + private readonly bool _makeCurrent; + + public override void Run() + { + try + { + try + { + //make sure the file data is stored in the recent files list even if loading fails + SaveFileData(_ioc, _keyfileOrProvider); + + + StatusLogger.UpdateMessage(UiStringKey.loading_database); + //get the stream data into a single stream variable (databaseStream) regardless whether its preloaded or not: + MemoryStream preloadedMemoryStream = _databaseData == null ? null : _databaseData.Result; + MemoryStream databaseStream; + if (preloadedMemoryStream != null) + databaseStream = preloadedMemoryStream; + else + { + using (Stream s = _app.GetFileStorage(_ioc).OpenFileForRead(_ioc)) + { + databaseStream = new MemoryStream(); + s.CopyTo(databaseStream); + databaseStream.Seek(0, SeekOrigin.Begin); + } + } + + //ok, try to load the database. Let's start with Kdbx format and retry later if that is the wrong guess: + _format = new KdbxDatabaseFormat(KdbxDatabaseFormat.GetFormatToUse(_app.GetFileStorage(_ioc).GetFileExtension(_ioc))); + TryLoad(databaseStream); + + + + success = true; + } + catch (Exception e) + { + this.Exception = e; + throw; + } + } + catch (KeyFileException) + { + Kp2aLog.Log("KeyFileException"); + Finish(false, /*TODO Localize: use Keepass error text KPRes.KeyFileError (including "or invalid format")*/ + _app.GetResourceString(UiStringKey.keyfile_does_not_exist), false, Exception); + } + catch (AggregateException e) + { + string message = e.Message; + foreach (var innerException in e.InnerExceptions) + { + message = innerException.Message; + // Override the message shown with the last (hopefully most recent) inner exception + Kp2aLog.LogUnexpectedError(innerException); + } + Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + message, false, Exception); + return; + } + catch (DuplicateUuidsException e) + { + Kp2aLog.Log(e.ToString()); + Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + e.Message + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), false, Exception); + return; + } + catch (Exception e) + { + if (!(e is InvalidCompositeKeyException)) + Kp2aLog.LogUnexpectedError(e); + Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + (e.Message ?? (e is FileNotFoundException ? _app.GetResourceString(UiStringKey.FileNotFound) : "")), false, Exception); + return; + } + + + } + + /// + /// Holds the exception which was thrown during execution (if any) + /// + public Exception Exception { get; set; } + + Database TryLoad(MemoryStream databaseStream) + { + //create a copy of the stream so we can try again if we get an exception which indicates we should change parameters + //This is not optimal in terms of (short-time) memory usage but is hard to avoid because the Keepass library closes streams also in case of errors. + //Alternatives would involve increased traffic (if file is on remote) and slower loading times, so this seems to be the best choice. + MemoryStream workingCopy = new MemoryStream(); + databaseStream.CopyTo(workingCopy); + workingCopy.Seek(0, SeekOrigin.Begin); + //reset stream if we need to reuse it later: + databaseStream.Seek(0, SeekOrigin.Begin); + //now let's go: + try + { + Database newDb = _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent); + Kp2aLog.Log("LoadDB OK"); + + Finish(true, _format.SuccessMessage); + return newDb; + } + catch (OldFormatException) + { + _format = new KdbDatabaseFormat(_app); + return TryLoad(databaseStream); + } + catch (InvalidCompositeKeyException) + { + KcpPassword passwordKey = (KcpPassword)_compositeKey.GetUserKey(typeof(KcpPassword)); + + if ((passwordKey != null) && (passwordKey.Password.ReadString() == "") && (_compositeKey.UserKeyCount > 1)) + { + //if we don't get a password, we don't know whether this means "empty password" or "no password" + //retry without password: + _compositeKey.RemoveUserKey(passwordKey); + //retry: + return TryLoad(databaseStream); + } + else throw; + } + + } + + private void SaveFileData(IOConnectionInfo ioc, String keyfileOrProvider) { + + if (!_rememberKeyfile) + { + keyfileOrProvider = ""; + } + _app.StoreOpenedFileAsRecent(ioc, keyfileOrProvider, _updateLastUsageTimestamp); + } + + + + } + +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/MoveElements.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/MoveElements.cs new file mode 100644 index 00000000..c3e9a6d0 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/MoveElements.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Android.App; +using Android.Content; +using KeePassLib; +using KeePassLib.Interfaces; + +namespace keepass2android.database.edit +{ + public class MoveElements: RunnableOnFinish + { + private readonly List _elementsToMove; + private readonly PwGroup _targetGroup; + private readonly Activity _ctx; + private readonly IKp2aApp _app; + + public MoveElements(List elementsToMove, PwGroup targetGroup, Activity ctx, IKp2aApp app, OnFinish finish) : base(ctx, finish) + { + _elementsToMove = elementsToMove; + _targetGroup = targetGroup; + _ctx = ctx; + _app = app; + } + + public override void Run() + { + //check if we will run into problems. Then finish with error before we start doing anything. + foreach (var _elementToMove in _elementsToMove) + { + PwGroup pgParent = _elementToMove.ParentGroup; + if (pgParent != _targetGroup) + { + if (pgParent != null) + { + PwGroup group = _elementToMove as PwGroup; + if (group != null) + { + if ((_targetGroup == group) || (_targetGroup.IsContainedIn(group))) + { + Finish(false, _app.GetResourceString(UiStringKey.CannotMoveGroupHere)); + return; + } + + } + + } + } + + } + + HashSet removeDatabases = new HashSet(); + Database addDatabase = _app.FindDatabaseForElement(_targetGroup); + if (addDatabase == null) + { + Finish(false, "Did not find target database. Did you lock it?"); + return; + } + + foreach (var elementToMove in _elementsToMove) + { + + _app.DirtyGroups.Add(elementToMove.ParentGroup); + + + PwGroup pgParent = elementToMove.ParentGroup; + if (pgParent != _targetGroup) + { + if (pgParent != null) // Remove from parent + { + PwEntry entry = elementToMove as PwEntry; + if (entry != null) + { + var dbRem = _app.FindDatabaseForElement(entry); + removeDatabases.Add(dbRem); + dbRem.EntriesById.Remove(entry.Uuid); + dbRem.Elements.Remove(entry); + pgParent.Entries.Remove(entry); + _targetGroup.AddEntry(entry, true, true); + addDatabase.EntriesById.Add(entry.Uuid, entry); + addDatabase.Elements.Add(entry); + } + else + { + PwGroup group = (PwGroup)elementToMove; + if ((_targetGroup == group) || (_targetGroup.IsContainedIn(group))) + { + Finish(false, _app.GetResourceString(UiStringKey.CannotMoveGroupHere)); + return; + } + + var dbRem = _app.FindDatabaseForElement(@group); + if (dbRem == null) + { + Finish(false, "Did not find source database. Did you lock it?"); + return; + } + + dbRem.GroupsById.Remove(group.Uuid); + dbRem.Elements.Remove(group); + removeDatabases.Add(dbRem); + pgParent.Groups.Remove(group); + _targetGroup.AddGroup(group, true, true); + addDatabase.GroupsById.Add(group.Uuid, group); + addDatabase.Elements.Add(group); + } + } + + } + + } + + + + + //first save the database where we added the elements + var allDatabasesToSave = new List {addDatabase}; + //then all databases where we removed elements: + removeDatabases.RemoveWhere(db => db == addDatabase); + allDatabasesToSave.AddRange(removeDatabases); + + int indexToSave = 0; + bool allSavesSuccess = true; + void ContinueSave(bool success, string message, Activity activeActivity) + { + allSavesSuccess &= success; + indexToSave++; + if (indexToSave == allDatabasesToSave.Count) + { + OnFinishToRun.SetResult(allSavesSuccess); + OnFinishToRun.Run(); + return; + } + SaveDb saveDb = new SaveDb(_ctx, _app, allDatabasesToSave[indexToSave], new ActionOnFinish(activeActivity, ContinueSave), false); + saveDb.SetStatusLogger(StatusLogger); + saveDb.ShowDatabaseIocInStatus = allDatabasesToSave.Count > 1; + saveDb.Run(); + } + + + SaveDb save = new SaveDb(_ctx, _app, allDatabasesToSave[0], new ActionOnFinish(ActiveActivity, ContinueSave), false); + save.SetStatusLogger(StatusLogger); + save.ShowDatabaseIocInStatus = allDatabasesToSave.Count > 1; + save.Run(); + } + } +} diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/OnFinish.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/OnFinish.cs new file mode 100644 index 00000000..383f402c --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/OnFinish.cs @@ -0,0 +1,153 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System; +using Android; +using Android.App; +using Android.Content; +using Android.OS; +using Android.Widget; + +namespace keepass2android +{ + public abstract class OnFinish + { + protected bool Success; + protected String Message; + protected Exception Exception; + + protected bool ImportantMessage + { + get; + set; + } + + protected OnFinish BaseOnFinish; + protected Handler Handler; + private ProgressDialogStatusLogger _statusLogger = new ProgressDialogStatusLogger(); //default: no logging but not null -> can be used whenever desired + private Activity _activeActivity, _previouslyActiveActivity; + + + public ProgressDialogStatusLogger StatusLogger + { + get { return _statusLogger; } + set { _statusLogger = value; } + } + + public Activity ActiveActivity + { + get { return _activeActivity; } + set + { + if (_activeActivity != null && _activeActivity != _previouslyActiveActivity) + { + _previouslyActiveActivity = _activeActivity; + + } + _activeActivity = value; + if (BaseOnFinish != null) + { + BaseOnFinish.ActiveActivity = value; + } + } + } + + public Activity PreviouslyActiveActivity + { + get { return _previouslyActiveActivity; } + + } + + + + protected OnFinish(Activity activeActivity, Handler handler) + { + ActiveActivity = activeActivity; + BaseOnFinish = null; + Handler = handler; + + } + + protected OnFinish(Activity activeActivity, OnFinish finish, Handler handler) + { + ActiveActivity = activeActivity; + BaseOnFinish = finish; + Handler = handler; + } + + protected OnFinish(Activity activeActivity, OnFinish finish) + { + ActiveActivity = activeActivity; + BaseOnFinish = finish; + Handler = null; + } + + public void SetResult(bool success, string message, bool importantMessage, Exception exception) { + Success = success; + Message = message; + ImportantMessage = importantMessage; + Exception = exception; + } + + + public void SetResult(bool success) { + Success = success; + } + + public virtual void Run() { + if (BaseOnFinish == null) return; + // Pass on result on call finish + BaseOnFinish.SetResult(Success, Message, ImportantMessage, Exception); + + if ( Handler != null ) { + Handler.Post(BaseOnFinish.Run); + } else { + BaseOnFinish.Run(); + } + } + + protected void DisplayMessage(Context ctx) { + DisplayMessage(ctx, Message, ImportantMessage); + } + + public static void DisplayMessage(Context ctx, string message, bool makeDialog) + { + if ( !String.IsNullOrEmpty(message) ) { + Kp2aLog.Log("OnFinish message: " + message); + if (makeDialog && ctx != null) + { + try + { + AlertDialog.Builder builder = new AlertDialog.Builder(ctx); + + builder.SetMessage(message) + .SetPositiveButton(Android.Resource.String.Ok, (sender, args) => ((Dialog)sender).Dismiss()) + .Show(); + + } + catch (Exception) + { + Toast.MakeText(ctx, message, ToastLength.Long).Show(); + } + } + else + Toast.MakeText(ctx ?? Application.Context, message, ToastLength.Long).Show(); + } + } + } +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/RunnableOnFinish.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/RunnableOnFinish.cs new file mode 100644 index 00000000..b24e5a46 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/RunnableOnFinish.cs @@ -0,0 +1,78 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ +using System; +using Android.App; +using Android.Content; + +namespace keepass2android +{ + + public abstract class RunnableOnFinish { + + protected OnFinish _onFinishToRun; + public ProgressDialogStatusLogger StatusLogger = new ProgressDialogStatusLogger(); //default: empty but not null + private Activity _activeActivity; + + protected RunnableOnFinish(Activity activeActivity, OnFinish finish) + { + _activeActivity = activeActivity; + _onFinishToRun = finish; + } + + public OnFinish OnFinishToRun + { + get { return _onFinishToRun; } + set { _onFinishToRun = value; } + } + + public Activity ActiveActivity + { + get { return _activeActivity; } + set + { + _activeActivity = value; + if (_onFinishToRun != null) + _onFinishToRun.ActiveActivity = _activeActivity; + } + } + + protected void Finish(bool result, String message, bool importantMessage = false, Exception exception = null) { + if ( OnFinishToRun != null ) { + OnFinishToRun.SetResult(result, message, importantMessage, exception); + OnFinishToRun.Run(); + } + } + + protected void Finish(bool result) { + if ( OnFinishToRun != null ) { + OnFinishToRun.SetResult(result); + OnFinishToRun.Run(); + } + } + + public void SetStatusLogger(ProgressDialogStatusLogger status) { + if (OnFinishToRun != null) + { + OnFinishToRun.StatusLogger = status; + } + StatusLogger = status; + } + + public abstract void Run(); + } +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/SaveDB.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/SaveDB.cs new file mode 100644 index 00000000..c432d416 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/SaveDB.cs @@ -0,0 +1,344 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System; +using System.IO; +using System.Security.Cryptography; +using Android.App; +using Android.Content; +using Android.OS; +using Android.Preferences; +using Java.Lang; +using KeePassLib; +using KeePassLib.Serialization; +using KeePassLib.Utility; +using keepass2android.Io; +using Debug = System.Diagnostics.Debug; +using Exception = System.Exception; + +namespace keepass2android +{ + + public class SaveDb : RunnableOnFinish { + private readonly IKp2aApp _app; + private readonly Database _db; + private readonly bool _dontSave; + + /// + /// stream for reading the data from the original file. If this is set to a non-null value, we know we need to sync + /// + private readonly Stream _streamForOrigFile; + private readonly Context _ctx; + private Java.Lang.Thread _workerThread; + + public SaveDb(Activity ctx, IKp2aApp app, Database db, OnFinish finish, bool dontSave) + : base(ctx, finish) + { + _db = db; + _ctx = ctx; + _app = app; + _dontSave = dontSave; + } + + /// + /// Constructor for sync + /// + /// + /// + /// + /// + /// Stream for reading the data from the (changed) original location + public SaveDb(Activity ctx, IKp2aApp app, OnFinish finish, Database db, bool dontSave, Stream streamForOrigFile) + : base(ctx, finish) + { + _db = db; + _ctx = ctx; + _app = app; + _dontSave = dontSave; + _streamForOrigFile = streamForOrigFile; + } + + public SaveDb(Activity ctx, IKp2aApp app, Database db, OnFinish finish) + : base(ctx, finish) + { + _ctx = ctx; + _app = app; + _db = db; + _dontSave = false; + } + + public bool ShowDatabaseIocInStatus { get; set; } + + public override void Run () + { + + if (!_dontSave) + { + try + { + if (_db.CanWrite == false) + { + //this should only happen if there is a problem in the UI so that the user sees an edit interface. + Finish(false,"Cannot save changes. File is read-only!"); + return; + } + + string message = _app.GetResourceString(UiStringKey.saving_database); + + if (ShowDatabaseIocInStatus) + message += " (" + _app.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")"; + + StatusLogger.UpdateMessage(message); + + IOConnectionInfo ioc = _db.Ioc; + IFileStorage fileStorage = _app.GetFileStorage(ioc); + + if (_streamForOrigFile == null) + { + if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave)) + || (_db.KpDatabase.HashOfFileOnDisk == null)) //first time saving + { + PerformSaveWithoutCheck(fileStorage, ioc); + Finish(true); + return; + } + } + + + bool hasStreamForOrigFile = (_streamForOrigFile != null); + bool hasChangeFast = hasStreamForOrigFile || + fileStorage.CheckForFileChangeFast(ioc, _db.LastFileVersion); //first try to use the fast change detection; + bool hasHashChanged = hasChangeFast || + (FileHashChanged(ioc, _db.KpDatabase.HashOfFileOnDisk) == + FileHashChange.Changed); //if that fails, hash the file and compare: + + if (hasHashChanged) + { + Kp2aLog.Log("Conflict. " + hasStreamForOrigFile + " " + hasChangeFast + " " + hasHashChanged); + + bool alwaysMerge = (PreferenceManager.GetDefaultSharedPreferences(Application.Context) + .GetBoolean("AlwaysMergeOnConflict", false)); + + if (alwaysMerge) + { + MergeAndFinish(fileStorage, ioc); + } + else + { + + + //ask user... + _app.AskYesNoCancel(UiStringKey.TitleSyncQuestion, UiStringKey.MessageSyncQuestion, + UiStringKey.YesSynchronize, + UiStringKey.NoOverwrite, + //yes = sync + (sender, args) => + { + Action runHandler = () => { MergeAndFinish(fileStorage, ioc); }; + RunInWorkerThread(runHandler); + }, + //no = overwrite + (sender, args) => + { + RunInWorkerThread(() => + { + PerformSaveWithoutCheck(fileStorage, ioc); + Finish(true); + }); + }, + //cancel + (sender, args) => + { + RunInWorkerThread(() => Finish(false)); + }, + _ctx + ); + } + + } + else + { + PerformSaveWithoutCheck(fileStorage, ioc); + Finish(true); + } + + } + catch (Exception e) + { + /* TODO KPDesktop: + * catch(Exception exSave) + { + MessageService.ShowSaveWarning(pd.IOConnectionInfo, exSave, true); + bSuccess = false; + } +*/ + Kp2aLog.LogUnexpectedError(e); + Finish(false, e.Message); + return; + } + } + else + { + Finish(true); + } + + } + + private void MergeAndFinish(IFileStorage fileStorage, IOConnectionInfo ioc) + { + //note: when synced, the file might be downloaded once again from the server. Caching the data + //in the hashing function would solve this but increases complexity. I currently assume the files are + //small. + MergeIn(fileStorage, ioc); + PerformSaveWithoutCheck(fileStorage, ioc); + _db.UpdateGlobals(); + Finish(true); + } + + private void RunInWorkerThread(Action runHandler) + { + try + { + _workerThread = new Java.Lang.Thread(() => + { + try + { + runHandler(); + } + catch (Exception e) + { + Kp2aLog.LogUnexpectedError(e); + Kp2aLog.Log("Error in worker thread of SaveDb: " + e); + Finish(false, e.Message); + } + + }); + _workerThread.Start(); + } + catch (Exception e) + { + Kp2aLog.LogUnexpectedError(e); + Kp2aLog.Log("Error starting worker thread of SaveDb: "+e); + Finish(false, e.Message); + } + + } + + public void JoinWorkerThread() + { + if (_workerThread != null) + _workerThread.Join(); + } + + private void MergeIn(IFileStorage fileStorage, IOConnectionInfo ioc) + { + StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.SynchronizingDatabase)); + + PwDatabase pwImp = new PwDatabase(); + PwDatabase pwDatabase = _db.KpDatabase; + pwImp.New(new IOConnectionInfo(), pwDatabase.MasterKey, _app.GetFileStorage(ioc).GetFilenameWithoutPathAndExt(ioc)); + pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep(); + pwImp.MasterKey = pwDatabase.MasterKey; + var stream = GetStreamForBaseFile(fileStorage, ioc); + + _db.DatabaseFormat.PopulateDatabaseFromStream(pwImp, stream, null); + + + pwDatabase.MergeIn(pwImp, PwMergeMethod.Synchronize, null); + + } + + private Stream GetStreamForBaseFile(IFileStorage fileStorage, IOConnectionInfo ioc) + { + //if we have the original file already available: use it + if (_streamForOrigFile != null) + return _streamForOrigFile; + + //if the file storage caches, it might return the local data in case of a conflict. This would result in data loss + // so we need to ensure we get the data from remote (only if the remote file is available. if not, we won't overwrite anything) + CachingFileStorage cachingFileStorage = fileStorage as CachingFileStorage; + if (cachingFileStorage != null) + { + return cachingFileStorage.OpenRemoteForReadIfAvailable(ioc); + } + return fileStorage.OpenFileForRead(ioc); + } + + private void PerformSaveWithoutCheck(IFileStorage fileStorage, IOConnectionInfo ioc) + { + StatusLogger.UpdateSubMessage(""); + _db.SaveData(); + _db.LastFileVersion = fileStorage.GetCurrentFileVersionFast(ioc); + } + + public byte[] HashOriginalFile(IOConnectionInfo iocFile) + { + if (iocFile == null) { Debug.Assert(false); return null; } // Assert only + + Stream sIn; + try + { + IFileStorage fileStorage = _app.GetFileStorage(iocFile); + CachingFileStorage cachingFileStorage = fileStorage as CachingFileStorage; + if (cachingFileStorage != null) + { + string hash; + cachingFileStorage.GetRemoteDataAndHash(iocFile, out hash); + return MemUtil.HexStringToByteArray(hash); + } + else + { + sIn = fileStorage.OpenFileForRead(iocFile); + } + + if (sIn == null) throw new FileNotFoundException(); + } + catch (Exception) { return null; } + + byte[] pbHash; + try + { + SHA256Managed sha256 = new SHA256Managed(); + pbHash = sha256.ComputeHash(sIn); + } + catch (Exception) { Debug.Assert(false); sIn.Close(); return null; } + + sIn.Close(); + return pbHash; + } + + enum FileHashChange + { + Equal, + Changed, + FileNotAvailable + } + + private FileHashChange FileHashChanged(IOConnectionInfo ioc, byte[] hashOfFileOnDisk) + { + StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.CheckingTargetFileForChanges)); + byte[] fileHash = HashOriginalFile(ioc); + if (fileHash == null) + return FileHashChange.FileNotAvailable; + return MemUtil.ArraysEqual(fileHash, hashOfFileOnDisk) ? FileHashChange.Equal : FileHashChange.Changed; + } + + + } + +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/SetPassword.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/SetPassword.cs new file mode 100644 index 00000000..36fcad55 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/SetPassword.cs @@ -0,0 +1,108 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ +using System; +using Android.App; +using Android.Content; +using KeePassLib; +using KeePassLib.Keys; + +namespace keepass2android +{ + public class SetPassword : RunnableOnFinish { + + private readonly String _password; + private readonly String _keyfile; + private readonly IKp2aApp _app; + private readonly bool _dontSave; + private readonly Activity _ctx; + + public SetPassword(Activity ctx, IKp2aApp app, String password, String keyfile, OnFinish finish): base(ctx, finish) { + _ctx = ctx; + _app = app; + _password = password; + _keyfile = keyfile; + _dontSave = false; + } + + public SetPassword(Activity ctx, IKp2aApp app, String password, String keyfile, OnFinish finish, bool dontSave) + : base(ctx, finish) + { + _ctx = ctx; + _app = app; + _password = password; + _keyfile = keyfile; + _dontSave = dontSave; + } + + + public override void Run () + { + StatusLogger.UpdateMessage(UiStringKey.SettingPassword); + PwDatabase pm = _app.CurrentDb.KpDatabase; + CompositeKey newKey = new CompositeKey (); + if (String.IsNullOrEmpty (_password) == false) { + newKey.AddUserKey (new KcpPassword (_password)); + } + if (String.IsNullOrEmpty (_keyfile) == false) { + try { + newKey.AddUserKey (new KcpKeyFile (_keyfile)); + } catch (Exception) { + //TODO MessageService.ShowWarning (strKeyFile, KPRes.KeyFileError, exKF); + return; + } + } + + DateTime previousMasterKeyChanged = pm.MasterKeyChanged; + CompositeKey previousKey = pm.MasterKey; + + pm.MasterKeyChanged = DateTime.Now; + pm.MasterKey = newKey; + + // Save Database + _onFinishToRun = new AfterSave(ActiveActivity, previousKey, previousMasterKeyChanged, pm, OnFinishToRun); + SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun, _dontSave); + save.SetStatusLogger(StatusLogger); + save.Run(); + } + + private class AfterSave : OnFinish { + private readonly CompositeKey _backup; + private readonly DateTime _previousKeyChanged; + private readonly PwDatabase _db; + + public AfterSave(Activity activity, CompositeKey backup, DateTime previousKeyChanged, PwDatabase db, OnFinish finish): base(activity, finish) { + _previousKeyChanged = previousKeyChanged; + _backup = backup; + _db = db; + } + + public override void Run() { + if ( ! Success ) { + _db.MasterKey = _backup; + _db.MasterKeyChanged = _previousKeyChanged; + } + + base.Run(); + } + + } + + + } + +} + diff --git a/src/Kp2aBusinessLogicSdkStyle/database/edit/UpdateEntry.cs b/src/Kp2aBusinessLogicSdkStyle/database/edit/UpdateEntry.cs new file mode 100644 index 00000000..bec284c6 --- /dev/null +++ b/src/Kp2aBusinessLogicSdkStyle/database/edit/UpdateEntry.cs @@ -0,0 +1,82 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using Android.App; +using Android.Content; +using KeePassLib; + +namespace keepass2android +{ + + public class UpdateEntry : RunnableOnFinish { + private readonly IKp2aApp _app; + private readonly Activity _ctx; + + public UpdateEntry(Activity ctx, IKp2aApp app, PwEntry oldE, PwEntry newE, OnFinish finish):base(ctx, finish) { + _ctx = ctx; + _app = app; + + _onFinishToRun = new AfterUpdate(ctx, oldE, newE, app, finish); + } + + + public override void Run() { + // Commit to disk + SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun); + save.SetStatusLogger(StatusLogger); + save.Run(); + } + + private class AfterUpdate : OnFinish { + private readonly PwEntry _backup; + private readonly PwEntry _updatedEntry; + private readonly IKp2aApp _app; + + public AfterUpdate(Activity activity, PwEntry backup, PwEntry updatedEntry, IKp2aApp app, OnFinish finish):base(activity, finish) { + _backup = backup; + _updatedEntry = updatedEntry; + _app = app; + } + + public override void Run() { + if ( Success ) { + // Mark parent group dirty. Even only the last modification date changed, this might affect sort order + PwGroup parent = _updatedEntry.ParentGroup; + if ( parent != null ) { + + // Mark parent group dirty + _app.DirtyGroups.Add(parent); + + } + + } else { + StatusLogger.UpdateMessage(UiStringKey.UndoingChanges); + // If we fail to save, back out changes to global structure + //TODO test fail + _updatedEntry.AssignProperties(_backup, false, true, false); + } + + base.Run(); + } + + } + + + } + +} + diff --git a/src/TwofishCipherSdkStyle/Twofish.cs b/src/TwofishCipherSdkStyle/Twofish.cs new file mode 100644 index 00000000..db054e47 --- /dev/null +++ b/src/TwofishCipherSdkStyle/Twofish.cs @@ -0,0 +1,121 @@ +/* + A C# implementation of the Twofish cipher + By Shaun Wilde + + An article on integrating a C# implementation of the Twofish cipher into the + .NET framework. + + http://www.codeproject.com/KB/recipes/twofish_csharp.aspx + + The Code Project Open License (CPOL) 1.02 + http://www.codeproject.com/info/cpol10.aspx + + Download a copy of the CPOL. + http://www.codeproject.com/info/CPOL.zip +*/ + +using System; +using System.Diagnostics; +using System.Security.Cryptography; + +namespace TwofishCipher.Crypto +{ + /// + /// Summary description for Twofish encryption algorithm of which more information can be found at http://www.counterpane.com/twofish.html. + /// This is based on the MS cryptographic framework and can therefore be used in place of the RijndaelManaged classes + /// provided by MS in System.Security.Cryptography and the other related classes + /// + public sealed class Twofish : SymmetricAlgorithm + { + /// + /// This is the Twofish constructor. + /// + public Twofish() + { + this.LegalKeySizesValue = new KeySizes[]{new KeySizes(128,256,64)}; // this allows us to have 128,192,256 key sizes + + this.LegalBlockSizesValue = new KeySizes[]{new KeySizes(128,128,0)}; // this is in bits - typical of MS - always 16 bytes + + this.BlockSize = 128; // set this to 16 bytes we cannot have any other value + this.KeySize = 128; // in bits - this can be changed to 128,192,256 + + this.Padding = PaddingMode.Zeros; + + this.Mode = CipherMode.ECB; + + } + + /// + /// Creates an object that supports ICryptoTransform that can be used to encrypt data using the Twofish encryption algorithm. + /// + /// A byte array that contains a key. The length of this key should be equal to the KeySize property + /// A byte array that contains an initialization vector. The length of this IV should be equal to the BlockSize property + public override ICryptoTransform CreateEncryptor(byte[] key, byte[] iv) + { + Key = key; // this appears to make a new copy + + if (Mode == CipherMode.CBC) + IV = iv; + + return new TwofishEncryption(KeySize, ref KeyValue, ref IVValue, ModeValue, TwofishBase.EncryptionDirection.Encrypting); + } + + /// + /// Creates an object that supports ICryptoTransform that can be used to decrypt data using the Twofish encryption algorithm. + /// + /// A byte array that contains a key. The length of this key should be equal to the KeySize property + /// A byte array that contains an initialization vector. The length of this IV should be equal to the BlockSize property + public override ICryptoTransform CreateDecryptor(byte[] key, byte[] iv) + { + Key = key; + + if (Mode == CipherMode.CBC) + IV = iv; + + return new TwofishEncryption(KeySize, ref KeyValue, ref IVValue, ModeValue, TwofishBase.EncryptionDirection.Decrypting); + } + + /// + /// Generates a random initialization Vector (IV). + /// + public override void GenerateIV() + { + IV = new byte[16]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + } + + /// + /// Generates a random Key. This is only really useful in testing scenarios. + /// + public override void GenerateKey() + { + Key = new byte[KeySize/8]; + + // set the array to all 0 - implement a random key generation mechanism later probably based on PRNG + for (int i=Key.GetLowerBound(0);i + /// Override the Set method on this property so that we only support CBC and EBC + /// + public override CipherMode Mode + { + set + { + switch (value) + { + case CipherMode.CBC: + break; + case CipherMode.ECB: + break; + default: + throw (new CryptographicException("Specified CipherMode is not supported.")); + } + this.ModeValue = value; + } + } + + } +} diff --git a/src/TwofishCipherSdkStyle/TwofishBase.cs b/src/TwofishCipherSdkStyle/TwofishBase.cs new file mode 100644 index 00000000..a29a9148 --- /dev/null +++ b/src/TwofishCipherSdkStyle/TwofishBase.cs @@ -0,0 +1,641 @@ +/* + A C# implementation of the Twofish cipher + By Shaun Wilde + + An article on integrating a C# implementation of the Twofish cipher into the + .NET framework. + + http://www.codeproject.com/KB/recipes/twofish_csharp.aspx + + The Code Project Open License (CPOL) 1.02 + http://www.codeproject.com/info/cpol10.aspx + + Download a copy of the CPOL. + http://www.codeproject.com/info/CPOL.zip +*/ + +//#define FEISTEL + +using System; +using System.Diagnostics; +using System.Security.Cryptography; + +namespace TwofishCipher.Crypto +{ + + /// + /// Summary description for TwofishBase. + /// + internal class TwofishBase + { + public enum EncryptionDirection + { + Encrypting, + Decrypting + } + + public TwofishBase() + { + } + + protected int inputBlockSize = BLOCK_SIZE/8; + protected int outputBlockSize = BLOCK_SIZE/8; + + /* + +***************************************************************************** + * + * Function Name: f32 + * + * Function: Run four bytes through keyed S-boxes and apply MDS matrix + * + * Arguments: x = input to f function + * k32 = pointer to key dwords + * keyLen = total key length (k32 --> keyLey/2 bits) + * + * Return: The output of the keyed permutation applied to x. + * + * Notes: + * This function is a keyed 32-bit permutation. It is the major building + * block for the Twofish round function, including the four keyed 8x8 + * permutations and the 4x4 MDS matrix multiply. This function is used + * both for generating round subkeys and within the round function on the + * block being encrypted. + * + * This version is fairly slow and pedagogical, although a smartcard would + * probably perform the operation exactly this way in firmware. For + * ultimate performance, the entire operation can be completed with four + * lookups into four 256x32-bit tables, with three dword xors. + * + * The MDS matrix is defined in TABLE.H. To multiply by Mij, just use the + * macro Mij(x). + * + -****************************************************************************/ + private static uint f32(uint x,ref uint[] k32,int keyLen) + { + byte[] b = {b0(x),b1(x),b2(x),b3(x)}; + + /* Run each byte thru 8x8 S-boxes, xoring with key byte at each stage. */ + /* Note that each byte goes through a different combination of S-boxes.*/ + + //*((DWORD *)b) = Bswap(x); /* make b[0] = LSB, b[3] = MSB */ + switch (((keyLen + 63)/64) & 3) + { + case 0: /* 256 bits of key */ + b[0] = (byte)(P8x8[P_04,b[0]] ^ b0(k32[3])); + b[1] = (byte)(P8x8[P_14,b[1]] ^ b1(k32[3])); + b[2] = (byte)(P8x8[P_24,b[2]] ^ b2(k32[3])); + b[3] = (byte)(P8x8[P_34,b[3]] ^ b3(k32[3])); + /* fall thru, having pre-processed b[0]..b[3] with k32[3] */ + goto case 3; + case 3: /* 192 bits of key */ + b[0] = (byte)(P8x8[P_03,b[0]] ^ b0(k32[2])); + b[1] = (byte)(P8x8[P_13,b[1]] ^ b1(k32[2])); + b[2] = (byte)(P8x8[P_23,b[2]] ^ b2(k32[2])); + b[3] = (byte)(P8x8[P_33,b[3]] ^ b3(k32[2])); + /* fall thru, having pre-processed b[0]..b[3] with k32[2] */ + goto case 2; + case 2: /* 128 bits of key */ + b[0] = P8x8[P_00, P8x8[P_01, P8x8[P_02, b[0]] ^ b0(k32[1])] ^ b0(k32[0])]; + b[1] = P8x8[P_10, P8x8[P_11, P8x8[P_12, b[1]] ^ b1(k32[1])] ^ b1(k32[0])]; + b[2] = P8x8[P_20, P8x8[P_21, P8x8[P_22, b[2]] ^ b2(k32[1])] ^ b2(k32[0])]; + b[3] = P8x8[P_30, P8x8[P_31, P8x8[P_32, b[3]] ^ b3(k32[1])] ^ b3(k32[0])]; + break; + } + + + /* Now perform the MDS matrix multiply inline. */ + return (uint)((M00(b[0]) ^ M01(b[1]) ^ M02(b[2]) ^ M03(b[3]))) ^ + (uint)((M10(b[0]) ^ M11(b[1]) ^ M12(b[2]) ^ M13(b[3])) << 8) ^ + (uint)((M20(b[0]) ^ M21(b[1]) ^ M22(b[2]) ^ M23(b[3])) << 16) ^ + (uint)((M30(b[0]) ^ M31(b[1]) ^ M32(b[2]) ^ M33(b[3])) << 24) ; + } + + /* + +***************************************************************************** + * + * Function Name: reKey + * + * Function: Initialize the Twofish key schedule from key32 + * + * Arguments: key = ptr to keyInstance to be initialized + * + * Return: TRUE on success + * + * Notes: + * Here we precompute all the round subkeys, although that is not actually + * required. For example, on a smartcard, the round subkeys can + * be generated on-the-fly using f32() + * + -****************************************************************************/ + protected bool reKey(int keyLen, ref uint[] key32) + { + int i,k64Cnt; + keyLength = keyLen; + rounds = numRounds[(keyLen-1)/64]; + int subkeyCnt = ROUND_SUBKEYS + 2*rounds; + uint A,B; + uint[] k32e = new uint[MAX_KEY_BITS/64]; + uint[] k32o = new uint[MAX_KEY_BITS/64]; /* even/odd key dwords */ + + k64Cnt=(keyLen+63)/64; /* round up to next multiple of 64 bits */ + for (i=0;i=0;r--) /* main Twofish decryption loop */ + { + t0 = f32( x[0] ,ref sboxKeys,keyLength); + t1 = f32(ROL(x[1],8),ref sboxKeys,keyLength); + + x[2] = ROL(x[2],1); + x[2]^= t0 + t1 + subKeys[ROUND_SUBKEYS+2*r ]; /* PHT, round keys */ + x[3]^= t0 + 2*t1 + subKeys[ROUND_SUBKEYS+2*r+1]; + x[3] = ROR(x[3],1); + + if (r>0) /* unswap, except for last round */ + { + t0 = x[0]; x[0]= x[2]; x[2] = t0; + t1 = x[1]; x[1]= x[3]; x[3] = t1; + } + } + + for (int i=0;i0) ? k0 : k1; /* merge in 32 more key bits */ + for (j=0;j<4;j++) /* shift one byte at a time */ + RS_rem(ref r); + } + return r; + } + + protected uint[] sboxKeys = new uint[MAX_KEY_BITS/64]; /* key bits used for S-boxes */ + protected uint[] subKeys = new uint[TOTAL_SUBKEYS]; /* round subkeys, input/output whitening bits */ + protected uint[] Key = {0,0,0,0,0,0,0,0}; //new int[MAX_KEY_BITS/32]; + protected uint[] IV = {0,0,0,0}; // this should be one block size + private int keyLength; + private int rounds; + protected CipherMode cipherMode = CipherMode.ECB; + + + #region These are all the definitions that were found in AES.H + static private readonly int BLOCK_SIZE = 128; /* number of bits per block */ + static private readonly int MAX_ROUNDS = 16; /* max # rounds (for allocating subkey array) */ + static private readonly int ROUNDS_128 = 16; /* default number of rounds for 128-bit keys*/ + static private readonly int ROUNDS_192 = 16; /* default number of rounds for 192-bit keys*/ + static private readonly int ROUNDS_256 = 16; /* default number of rounds for 256-bit keys*/ + static private readonly int MAX_KEY_BITS = 256; /* max number of bits of key */ +// static private readonly int MIN_KEY_BITS = 128; /* min number of bits of key (zero pad) */ + +//#define VALID_SIG 0x48534946 /* initialization signature ('FISH') */ +//#define MCT_OUTER 400 /* MCT outer loop */ +//#define MCT_INNER 10000 /* MCT inner loop */ +//#define REENTRANT 1 /* nonzero forces reentrant code (slightly slower) */ + + static private readonly int INPUT_WHITEN = 0; /* subkey array indices */ + static private readonly int OUTPUT_WHITEN = (INPUT_WHITEN + BLOCK_SIZE/32); + static private readonly int ROUND_SUBKEYS = (OUTPUT_WHITEN + BLOCK_SIZE/32); /* use 2 * (# rounds) */ + static private readonly int TOTAL_SUBKEYS = (ROUND_SUBKEYS + 2*MAX_ROUNDS); + + + #endregion + + #region These are all the definitions that were found in TABLE.H that we need + /* for computing subkeys */ + static private readonly uint SK_STEP = 0x02020202u; + static private readonly uint SK_BUMP = 0x01010101u; + static private readonly int SK_ROTL = 9; + + /* Reed-Solomon code parameters: (12,8) reversible code + g(x) = x**4 + (a + 1/a) x**3 + a x**2 + (a + 1/a) x + 1 + where a = primitive root of field generator 0x14D */ + static private readonly uint RS_GF_FDBK = 0x14D; /* field generator */ + static private void RS_rem(ref uint x) + { + byte b = (byte) (x >> 24); + // TODO: maybe change g2 and g3 to bytes + uint g2 = (uint)(((b << 1) ^ (((b & 0x80)==0x80) ? RS_GF_FDBK : 0 )) & 0xFF); + uint g3 = (uint)(((b >> 1) & 0x7F) ^ (((b & 1)==1) ? RS_GF_FDBK >> 1 : 0 ) ^ g2) ; + x = (x << 8) ^ (g3 << 24) ^ (g2 << 16) ^ (g3 << 8) ^ b; + } + + /* Macros for the MDS matrix + * The MDS matrix is (using primitive polynomial 169): + * 01 EF 5B 5B + * 5B EF EF 01 + * EF 5B 01 EF + * EF 01 EF 5B + *---------------------------------------------------------------- + * More statistical properties of this matrix (from MDS.EXE output): + * + * Min Hamming weight (one byte difference) = 8. Max=26. Total = 1020. + * Prob[8]: 7 23 42 20 52 95 88 94 121 128 91 + * 102 76 41 24 8 4 1 3 0 0 0 + * Runs[8]: 2 4 5 6 7 8 9 11 + * MSBs[8]: 1 4 15 8 18 38 40 43 + * HW= 8: 05040705 0A080E0A 14101C14 28203828 50407050 01499101 A080E0A0 + * HW= 9: 04050707 080A0E0E 10141C1C 20283838 40507070 80A0E0E0 C6432020 07070504 + * 0E0E0A08 1C1C1410 38382820 70705040 E0E0A080 202043C6 05070407 0A0E080E + * 141C101C 28382038 50704070 A0E080E0 4320C620 02924B02 089A4508 + * Min Hamming weight (two byte difference) = 3. Max=28. Total = 390150. + * Prob[3]: 7 18 55 149 270 914 2185 5761 11363 20719 32079 + * 43492 51612 53851 52098 42015 31117 20854 11538 6223 2492 1033 + * MDS OK, ROR: 6+ 7+ 8+ 9+ 10+ 11+ 12+ 13+ 14+ 15+ 16+ + * 17+ 18+ 19+ 20+ 21+ 22+ 23+ 24+ 25+ 26+ + */ + static private readonly int MDS_GF_FDBK = 0x169; /* primitive polynomial for GF(256)*/ + static private int LFSR1(int x) + { + return ( ((x) >> 1) ^ ((((x) & 0x01)==0x01) ? MDS_GF_FDBK/2 : 0)); + } + + static private int LFSR2(int x) + { + return ( ((x) >> 2) ^ ((((x) & 0x02)==0x02) ? MDS_GF_FDBK/2 : 0) ^ + ((((x) & 0x01)==0x01) ? MDS_GF_FDBK/4 : 0)); + } + + // TODO: not the most efficient use of code but it allows us to update the code a lot quicker we can possibly optimize this code once we have got it all working + static private int Mx_1(int x) + { + return x; /* force result to int so << will work */ + } + + static private int Mx_X(int x) + { + return x ^ LFSR2(x); /* 5B */ + } + + static private int Mx_Y(int x) + { + return x ^ LFSR1(x) ^ LFSR2(x); /* EF */ + } + + static private int M00(int x) + { + return Mul_1(x); + } + static private int M01(int x) + { + return Mul_Y(x); + } + static private int M02(int x) + { + return Mul_X(x); + } + static private int M03(int x) + { + return Mul_X(x); + } + + static private int M10(int x) + { + return Mul_X(x); + } + static private int M11(int x) + { + return Mul_Y(x); + } + static private int M12(int x) + { + return Mul_Y(x); + } + static private int M13(int x) + { + return Mul_1(x); + } + + static private int M20(int x) + { + return Mul_Y(x); + } + static private int M21(int x) + { + return Mul_X(x); + } + static private int M22(int x) + { + return Mul_1(x); + } + static private int M23(int x) + { + return Mul_Y(x); + } + + static private int M30(int x) + { + return Mul_Y(x); + } + static private int M31(int x) + { + return Mul_1(x); + } + static private int M32(int x) + { + return Mul_Y(x); + } + static private int M33(int x) + { + return Mul_X(x); + } + + static private int Mul_1(int x) + { + return Mx_1(x); + } + static private int Mul_X(int x) + { + return Mx_X(x); + } + static private int Mul_Y(int x) + { + return Mx_Y(x); + } + /* Define the fixed p0/p1 permutations used in keyed S-box lookup. + By changing the following constant definitions for P_ij, the S-boxes will + automatically get changed in all the Twofish source code. Note that P_i0 is + the "outermost" 8x8 permutation applied. See the f32() function to see + how these constants are to be used. + */ + static private readonly int P_00 = 1; /* "outermost" permutation */ + static private readonly int P_01 = 0; + static private readonly int P_02 = 0; + static private readonly int P_03 = (P_01^1); /* "extend" to larger key sizes */ + static private readonly int P_04 = 1; + + static private readonly int P_10 = 0; + static private readonly int P_11 = 0; + static private readonly int P_12 = 1; + static private readonly int P_13 = (P_11^1); + static private readonly int P_14 = 0; + + static private readonly int P_20 = 1; + static private readonly int P_21 = 1; + static private readonly int P_22 = 0; + static private readonly int P_23 = (P_21^1); + static private readonly int P_24 = 0; + + static private readonly int P_30 = 0; + static private readonly int P_31 = 1; + static private readonly int P_32 = 1; + static private readonly int P_33 = (P_31^1); + static private readonly int P_34 = 1; + + /* fixed 8x8 permutation S-boxes */ + + /*********************************************************************** + * 07:07:14 05/30/98 [4x4] TestCnt=256. keySize=128. CRC=4BD14D9E. + * maxKeyed: dpMax = 18. lpMax =100. fixPt = 8. skXor = 0. skDup = 6. + * log2(dpMax[ 6..18])= --- 15.42 1.33 0.89 4.05 7.98 12.05 + * log2(lpMax[ 7..12])= 9.32 1.01 1.16 4.23 8.02 12.45 + * log2(fixPt[ 0.. 8])= 1.44 1.44 2.44 4.06 6.01 8.21 11.07 14.09 17.00 + * log2(skXor[ 0.. 0]) + * log2(skDup[ 0.. 6])= --- 2.37 0.44 3.94 8.36 13.04 17.99 + ***********************************************************************/ + static private byte[,] P8x8 = + { + /* p0: */ + /* dpMax = 10. lpMax = 64. cycleCnt= 1 1 1 0. */ + /* 817D6F320B59ECA4.ECB81235F4A6709D.BA5E6D90C8F32471.D7F4126E9B3085CA. */ + /* Karnaugh maps: + * 0111 0001 0011 1010. 0001 1001 1100 1111. 1001 1110 0011 1110. 1101 0101 1111 1001. + * 0101 1111 1100 0100. 1011 0101 0010 0000. 0101 1000 1100 0101. 1000 0111 0011 0010. + * 0000 1001 1110 1101. 1011 1000 1010 0011. 0011 1001 0101 0000. 0100 0010 0101 1011. + * 0111 0100 0001 0110. 1000 1011 1110 1001. 0011 0011 1001 1101. 1101 0101 0000 1100. + */ + { + 0xA9, 0x67, 0xB3, 0xE8, 0x04, 0xFD, 0xA3, 0x76, + 0x9A, 0x92, 0x80, 0x78, 0xE4, 0xDD, 0xD1, 0x38, + 0x0D, 0xC6, 0x35, 0x98, 0x18, 0xF7, 0xEC, 0x6C, + 0x43, 0x75, 0x37, 0x26, 0xFA, 0x13, 0x94, 0x48, + 0xF2, 0xD0, 0x8B, 0x30, 0x84, 0x54, 0xDF, 0x23, + 0x19, 0x5B, 0x3D, 0x59, 0xF3, 0xAE, 0xA2, 0x82, + 0x63, 0x01, 0x83, 0x2E, 0xD9, 0x51, 0x9B, 0x7C, + 0xA6, 0xEB, 0xA5, 0xBE, 0x16, 0x0C, 0xE3, 0x61, + 0xC0, 0x8C, 0x3A, 0xF5, 0x73, 0x2C, 0x25, 0x0B, + 0xBB, 0x4E, 0x89, 0x6B, 0x53, 0x6A, 0xB4, 0xF1, + 0xE1, 0xE6, 0xBD, 0x45, 0xE2, 0xF4, 0xB6, 0x66, + 0xCC, 0x95, 0x03, 0x56, 0xD4, 0x1C, 0x1E, 0xD7, + 0xFB, 0xC3, 0x8E, 0xB5, 0xE9, 0xCF, 0xBF, 0xBA, + 0xEA, 0x77, 0x39, 0xAF, 0x33, 0xC9, 0x62, 0x71, + 0x81, 0x79, 0x09, 0xAD, 0x24, 0xCD, 0xF9, 0xD8, + 0xE5, 0xC5, 0xB9, 0x4D, 0x44, 0x08, 0x86, 0xE7, + 0xA1, 0x1D, 0xAA, 0xED, 0x06, 0x70, 0xB2, 0xD2, + 0x41, 0x7B, 0xA0, 0x11, 0x31, 0xC2, 0x27, 0x90, + 0x20, 0xF6, 0x60, 0xFF, 0x96, 0x5C, 0xB1, 0xAB, + 0x9E, 0x9C, 0x52, 0x1B, 0x5F, 0x93, 0x0A, 0xEF, + 0x91, 0x85, 0x49, 0xEE, 0x2D, 0x4F, 0x8F, 0x3B, + 0x47, 0x87, 0x6D, 0x46, 0xD6, 0x3E, 0x69, 0x64, + 0x2A, 0xCE, 0xCB, 0x2F, 0xFC, 0x97, 0x05, 0x7A, + 0xAC, 0x7F, 0xD5, 0x1A, 0x4B, 0x0E, 0xA7, 0x5A, + 0x28, 0x14, 0x3F, 0x29, 0x88, 0x3C, 0x4C, 0x02, + 0xB8, 0xDA, 0xB0, 0x17, 0x55, 0x1F, 0x8A, 0x7D, + 0x57, 0xC7, 0x8D, 0x74, 0xB7, 0xC4, 0x9F, 0x72, + 0x7E, 0x15, 0x22, 0x12, 0x58, 0x07, 0x99, 0x34, + 0x6E, 0x50, 0xDE, 0x68, 0x65, 0xBC, 0xDB, 0xF8, + 0xC8, 0xA8, 0x2B, 0x40, 0xDC, 0xFE, 0x32, 0xA4, + 0xCA, 0x10, 0x21, 0xF0, 0xD3, 0x5D, 0x0F, 0x00, + 0x6F, 0x9D, 0x36, 0x42, 0x4A, 0x5E, 0xC1, 0xE0 + }, + /* p1: */ + /* dpMax = 10. lpMax = 64. cycleCnt= 2 0 0 1. */ + /* 28BDF76E31940AC5.1E2B4C376DA5F908.4C75169A0ED82B3F.B951C3DE647F208A. */ + /* Karnaugh maps: + * 0011 1001 0010 0111. 1010 0111 0100 0110. 0011 0001 1111 0100. 1111 1000 0001 1100. + * 1100 1111 1111 1010. 0011 0011 1110 0100. 1001 0110 0100 0011. 0101 0110 1011 1011. + * 0010 0100 0011 0101. 1100 1000 1000 1110. 0111 1111 0010 0110. 0000 1010 0000 0011. + * 1101 1000 0010 0001. 0110 1001 1110 0101. 0001 0100 0101 0111. 0011 1011 1111 0010. + */ + { + 0x75, 0xF3, 0xC6, 0xF4, 0xDB, 0x7B, 0xFB, 0xC8, + 0x4A, 0xD3, 0xE6, 0x6B, 0x45, 0x7D, 0xE8, 0x4B, + 0xD6, 0x32, 0xD8, 0xFD, 0x37, 0x71, 0xF1, 0xE1, + 0x30, 0x0F, 0xF8, 0x1B, 0x87, 0xFA, 0x06, 0x3F, + 0x5E, 0xBA, 0xAE, 0x5B, 0x8A, 0x00, 0xBC, 0x9D, + 0x6D, 0xC1, 0xB1, 0x0E, 0x80, 0x5D, 0xD2, 0xD5, + 0xA0, 0x84, 0x07, 0x14, 0xB5, 0x90, 0x2C, 0xA3, + 0xB2, 0x73, 0x4C, 0x54, 0x92, 0x74, 0x36, 0x51, + 0x38, 0xB0, 0xBD, 0x5A, 0xFC, 0x60, 0x62, 0x96, + 0x6C, 0x42, 0xF7, 0x10, 0x7C, 0x28, 0x27, 0x8C, + 0x13, 0x95, 0x9C, 0xC7, 0x24, 0x46, 0x3B, 0x70, + 0xCA, 0xE3, 0x85, 0xCB, 0x11, 0xD0, 0x93, 0xB8, + 0xA6, 0x83, 0x20, 0xFF, 0x9F, 0x77, 0xC3, 0xCC, + 0x03, 0x6F, 0x08, 0xBF, 0x40, 0xE7, 0x2B, 0xE2, + 0x79, 0x0C, 0xAA, 0x82, 0x41, 0x3A, 0xEA, 0xB9, + 0xE4, 0x9A, 0xA4, 0x97, 0x7E, 0xDA, 0x7A, 0x17, + 0x66, 0x94, 0xA1, 0x1D, 0x3D, 0xF0, 0xDE, 0xB3, + 0x0B, 0x72, 0xA7, 0x1C, 0xEF, 0xD1, 0x53, 0x3E, + 0x8F, 0x33, 0x26, 0x5F, 0xEC, 0x76, 0x2A, 0x49, + 0x81, 0x88, 0xEE, 0x21, 0xC4, 0x1A, 0xEB, 0xD9, + 0xC5, 0x39, 0x99, 0xCD, 0xAD, 0x31, 0x8B, 0x01, + 0x18, 0x23, 0xDD, 0x1F, 0x4E, 0x2D, 0xF9, 0x48, + 0x4F, 0xF2, 0x65, 0x8E, 0x78, 0x5C, 0x58, 0x19, + 0x8D, 0xE5, 0x98, 0x57, 0x67, 0x7F, 0x05, 0x64, + 0xAF, 0x63, 0xB6, 0xFE, 0xF5, 0xB7, 0x3C, 0xA5, + 0xCE, 0xE9, 0x68, 0x44, 0xE0, 0x4D, 0x43, 0x69, + 0x29, 0x2E, 0xAC, 0x15, 0x59, 0xA8, 0x0A, 0x9E, + 0x6E, 0x47, 0xDF, 0x34, 0x35, 0x6A, 0xCF, 0xDC, + 0x22, 0xC9, 0xC0, 0x9B, 0x89, 0xD4, 0xED, 0xAB, + 0x12, 0xA2, 0x0D, 0x52, 0xBB, 0x02, 0x2F, 0xA9, + 0xD7, 0x61, 0x1E, 0xB4, 0x50, 0x04, 0xF6, 0xC2, + 0x16, 0x25, 0x86, 0x56, 0x55, 0x09, 0xBE, 0x91 + } + }; + #endregion + + #region These are all the definitions that were found in PLATFORM.H that we need + // left rotation + private static uint ROL(uint x, int n) + { + return ( ((x) << ((n) & 0x1F)) | (x) >> (32-((n) & 0x1F)) ); + } + + // right rotation + private static uint ROR(uint x,int n) + { + return (((x) >> ((n) & 0x1F)) | ((x) << (32-((n) & 0x1F)))); + } + + // first byte + protected static byte b0(uint x) + { + return (byte)(x );//& 0xFF); + } + // second byte + protected static byte b1(uint x) + { + return (byte)((x >> 8));// & (0xFF)); + } + // third byte + protected static byte b2(uint x) + { + return (byte)((x >> 16));// & (0xFF)); + } + // fourth byte + protected static byte b3(uint x) + { + return (byte)((x >> 24));// & (0xFF)); + } + + #endregion + } +} diff --git a/src/TwofishCipherSdkStyle/TwofishCipherEngine.cs b/src/TwofishCipherSdkStyle/TwofishCipherEngine.cs new file mode 100644 index 00000000..dcd862e4 --- /dev/null +++ b/src/TwofishCipherSdkStyle/TwofishCipherEngine.cs @@ -0,0 +1,127 @@ +/* + Twofish Cipher for KeePass Password Safe + Copyright (C) 2009-2010 SEG Tech + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Security; +using System.Security.Cryptography; +using System.Diagnostics; + +using KeePassLib; +using KeePassLib.Cryptography.Cipher; + +using TwofishCipher.Crypto; + +namespace TwofishCipher +{ + public sealed class TwofishCipherEngine : ICipherEngine + { + private const CipherMode m_rCipherMode = CipherMode.CBC; + private const PaddingMode m_rCipherPadding = PaddingMode.PKCS7; + + private PwUuid m_uuidCipher; + + public static readonly byte[] TwofishCipherUuidBytes = new byte[]{ + 0xAD, 0x68, 0xF2, 0x9F, 0x57, 0x6F, 0x4B, 0xB9, + 0xA3, 0x6A, 0xD4, 0x7A, 0xF9, 0x65, 0x34, 0x6C + }; + + public TwofishCipherEngine() + { + m_uuidCipher = new PwUuid(TwofishCipherUuidBytes); + } + + public PwUuid CipherUuid + { + get + { + Debug.Assert(m_uuidCipher != null); + return m_uuidCipher; + } + } + + public string DisplayName + { + get { return "Twofish (256-Bit Key)"; } + } + + private static void ValidateArguments(Stream stream, bool bEncrypt, byte[] pbKey, byte[] pbIV) + { + Debug.Assert(stream != null); if(stream == null) throw new ArgumentNullException("stream"); + + Debug.Assert(pbKey != null); if(pbKey == null) throw new ArgumentNullException("pbKey"); + Debug.Assert(pbKey.Length == 32); + if(pbKey.Length != 32) throw new ArgumentException("Key must be 256 bits wide!"); + + Debug.Assert(pbIV != null); if(pbIV == null) throw new ArgumentNullException("pbIV"); + Debug.Assert(pbIV.Length == 16); + if(pbIV.Length != 16) throw new ArgumentException("Initialization vector must be 128 bits wide!"); + + if(bEncrypt) + { + Debug.Assert(stream.CanWrite); + if(stream.CanWrite == false) throw new ArgumentException("Stream must be writable!"); + } + else // Decrypt + { + Debug.Assert(stream.CanRead); + if(stream.CanRead == false) throw new ArgumentException("Encrypted stream must be readable!"); + } + } + + private static Stream CreateStream(Stream s, bool bEncrypt, byte[] pbKey, byte[] pbIV) + { + ValidateArguments(s, bEncrypt, pbKey, pbIV); + + Twofish f = new Twofish(); + + byte[] pbLocalIV = new byte[16]; + Array.Copy(pbIV, pbLocalIV, 16); + f.IV = pbLocalIV; + + byte[] pbLocalKey = new byte[32]; + Array.Copy(pbKey, pbLocalKey, 32); + f.KeySize = 256; + f.Key = pbLocalKey; + + f.Mode = m_rCipherMode; + f.Padding = m_rCipherPadding; + + ICryptoTransform iTransform = (bEncrypt ? f.CreateEncryptor() : f.CreateDecryptor()); + Debug.Assert(iTransform != null); + if(iTransform == null) throw new SecurityException("Unable to create Twofish transform!"); + + return new CryptoStream(s, iTransform, bEncrypt ? CryptoStreamMode.Write : + CryptoStreamMode.Read); + } + + public Stream EncryptStream(Stream sPlainText, byte[] pbKey, byte[] pbIV) + { + return CreateStream(sPlainText, true, pbKey, pbIV); + } + + public Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV) + { + return CreateStream(sEncrypted, false, pbKey, pbIV); + } + + } +} diff --git a/src/TwofishCipherSdkStyle/TwofishCipherSdkStyle.csproj b/src/TwofishCipherSdkStyle/TwofishCipherSdkStyle.csproj new file mode 100644 index 00000000..3835ca36 --- /dev/null +++ b/src/TwofishCipherSdkStyle/TwofishCipherSdkStyle.csproj @@ -0,0 +1,11 @@ + + + net8.0-android + 21 + enable + enable + + + + + \ No newline at end of file diff --git a/src/TwofishCipherSdkStyle/TwofishEncryption.cs b/src/TwofishCipherSdkStyle/TwofishEncryption.cs new file mode 100644 index 00000000..4f2a2307 --- /dev/null +++ b/src/TwofishCipherSdkStyle/TwofishEncryption.cs @@ -0,0 +1,193 @@ +/* + A C# implementation of the Twofish cipher + By Shaun Wilde + + An article on integrating a C# implementation of the Twofish cipher into the + .NET framework. + + http://www.codeproject.com/KB/recipes/twofish_csharp.aspx + + The Code Project Open License (CPOL) 1.02 + http://www.codeproject.com/info/cpol10.aspx + + Download a copy of the CPOL. + http://www.codeproject.com/info/CPOL.zip +*/ + +using System; +using System.Diagnostics; +using System.Security.Cryptography; + +namespace TwofishCipher.Crypto +{ + /// + /// Summary description for TwofishEncryption. + /// + internal class TwofishEncryption : TwofishBase, ICryptoTransform + { + public TwofishEncryption(int keyLen, ref byte[] key, ref byte[] iv, CipherMode cMode, EncryptionDirection direction) + { + // convert our key into an array of ints + for (int i=0;i + /// Transform a block depending on whether we are encrypting or decrypting + /// + /// + /// + /// + /// + /// + /// + public int TransformBlock( + byte[] inputBuffer, + int inputOffset, + int inputCount, + byte[] outputBuffer, + int outputOffset + ) + { + uint[] x=new uint[4]; + + // load it up + for (int i=0;i<4;i++) + { + x[i]= (uint)(inputBuffer[i*4+3+inputOffset]<<24) | (uint)(inputBuffer[i*4+2+inputOffset] << 16) | + (uint)(inputBuffer[i*4+1+inputOffset] << 8) | (uint)(inputBuffer[i*4+0+inputOffset]); + + } + + if (encryptionDirection == EncryptionDirection.Encrypting) + { + blockEncrypt(ref x); + } + else + { + blockDecrypt(ref x); + } + + + // load it up + for (int i=0;i<4;i++) + { + outputBuffer[i*4+0+outputOffset] = b0(x[i]); + outputBuffer[i*4+1+outputOffset] = b1(x[i]); + outputBuffer[i*4+2+outputOffset] = b2(x[i]); + outputBuffer[i*4+3+outputOffset] = b3(x[i]); + } + + + return inputCount; + } + + public byte[] TransformFinalBlock( + byte[] inputBuffer, + int inputOffset, + int inputCount + ) + { + byte[] outputBuffer;// = new byte[0]; + + if (inputCount>0) + { + outputBuffer = new byte[16]; // blocksize + uint[] x=new uint[4]; + + // load it up + for (int i=0;i<4;i++) // should be okay as we have already said to pad with zeros + { + x[i]= (uint)(inputBuffer[i*4+3+inputOffset]<<24) | (uint)(inputBuffer[i*4+2+inputOffset] << 16) | + (uint)(inputBuffer[i*4+1+inputOffset] << 8) | (uint)(inputBuffer[i*4+0+inputOffset]); + + } + + if (encryptionDirection == EncryptionDirection.Encrypting) + { + blockEncrypt(ref x); + } + else + { + blockDecrypt(ref x); + } + + // load it up + for (int i=0;i<4;i++) + { + outputBuffer[i*4+0] = b0(x[i]); + outputBuffer[i*4+1] = b1(x[i]); + outputBuffer[i*4+2] = b2(x[i]); + outputBuffer[i*4+3] = b3(x[i]); + } + } + else + { + outputBuffer = new byte[0]; // the .NET framework doesn't like it if you return null - this calms it down + } + + return outputBuffer; + } + + // not worked out this property yet - placing break points here just don't get caught. + private bool canReuseTransform = true; + public bool CanReuseTransform + { + get + { + return canReuseTransform; + } + } + + // I normally set this to false when block encrypting so that I can work on one block at a time + // but for compression and stream type ciphers this can be set to true so that you get all the data + private bool canTransformMultipleBlocks = false; + public bool CanTransformMultipleBlocks + { + get + { + return canTransformMultipleBlocks; + } + } + + public int InputBlockSize + { + get + { + return inputBlockSize; + } + } + + public int OutputBlockSize + { + get + { + return outputBlockSize; + } + } + + private EncryptionDirection encryptionDirection; + } +} diff --git a/src/ZlibAndroidSdkStyle/CRC32.cs b/src/ZlibAndroidSdkStyle/CRC32.cs new file mode 100644 index 00000000..97593b91 --- /dev/null +++ b/src/ZlibAndroidSdkStyle/CRC32.cs @@ -0,0 +1,814 @@ +// CRC32.cs +// ------------------------------------------------------------------ +// +// Copyright (c) 2011 Dino Chiesa. +// All rights reserved. +// +// This code module is part of DotNetZip, a zipfile class library. +// +// ------------------------------------------------------------------ +// +// This code is licensed under the Microsoft Public License. +// See the file License.txt for the license details. +// More info on: http://dotnetzip.codeplex.com +// +// ------------------------------------------------------------------ +// +// Last Saved: <2011-August-02 18:25:54> +// +// ------------------------------------------------------------------ +// +// This module defines the CRC32 class, which can do the CRC32 algorithm, using +// arbitrary starting polynomials, and bit reversal. The bit reversal is what +// distinguishes this CRC-32 used in BZip2 from the CRC-32 that is used in PKZIP +// files, or GZIP files. This class does both. +// +// ------------------------------------------------------------------ + + +using System; +using Interop = System.Runtime.InteropServices; + +namespace Ionic.Crc +{ + /// + /// Computes a CRC-32. The CRC-32 algorithm is parameterized - you + /// can set the polynomial and enable or disable bit + /// reversal. This can be used for GZIP, BZip2, or ZIP. + /// + /// + /// This type is used internally by DotNetZip; it is generally not used + /// directly by applications wishing to create, read, or manipulate zip + /// archive files. + /// + + [Interop.GuidAttribute("ebc25cf6-9120-4283-b972-0e5520d0000C")] + [Interop.ComVisible(true)] +#if !NETCF + [Interop.ClassInterface(Interop.ClassInterfaceType.AutoDispatch)] +#endif + public class CRC32 + { + /// + /// Indicates the total number of bytes applied to the CRC. + /// + public Int64 TotalBytesRead + { + get + { + return _TotalBytesRead; + } + } + + /// + /// Indicates the current CRC for all blocks slurped in. + /// + public Int32 Crc32Result + { + get + { + return unchecked((Int32)(~_register)); + } + } + + /// + /// Returns the CRC32 for the specified stream. + /// + /// The stream over which to calculate the CRC32 + /// the CRC32 calculation + public Int32 GetCrc32(System.IO.Stream input) + { + return GetCrc32AndCopy(input, null); + } + + /// + /// Returns the CRC32 for the specified stream, and writes the input into the + /// output stream. + /// + /// The stream over which to calculate the CRC32 + /// The stream into which to deflate the input + /// the CRC32 calculation + public Int32 GetCrc32AndCopy(System.IO.Stream input, System.IO.Stream output) + { + if (input == null) + throw new Exception("The input stream must not be null."); + + unchecked + { + byte[] buffer = new byte[BUFFER_SIZE]; + int readSize = BUFFER_SIZE; + + _TotalBytesRead = 0; + int count = input.Read(buffer, 0, readSize); + if (output != null) output.Write(buffer, 0, count); + _TotalBytesRead += count; + while (count > 0) + { + SlurpBlock(buffer, 0, count); + count = input.Read(buffer, 0, readSize); + if (output != null) output.Write(buffer, 0, count); + _TotalBytesRead += count; + } + + return (Int32)(~_register); + } + } + + + /// + /// Get the CRC32 for the given (word,byte) combo. This is a + /// computation defined by PKzip for PKZIP 2.0 (weak) encryption. + /// + /// The word to start with. + /// The byte to combine it with. + /// The CRC-ized result. + public Int32 ComputeCrc32(Int32 W, byte B) + { + return _InternalComputeCrc32((UInt32)W, B); + } + + internal Int32 _InternalComputeCrc32(UInt32 W, byte B) + { + return (Int32)(crc32Table[(W ^ B) & 0xFF] ^ (W >> 8)); + } + + + /// + /// Update the value for the running CRC32 using the given block of bytes. + /// This is useful when using the CRC32() class in a Stream. + /// + /// block of bytes to slurp + /// starting point in the block + /// how many bytes within the block to slurp + public void SlurpBlock(byte[] block, int offset, int count) + { + if (block == null) + throw new Exception("The data buffer must not be null."); + + // bzip algorithm + for (int i = 0; i < count; i++) + { + int x = offset + i; + byte b = block[x]; + if (this.reverseBits) + { + UInt32 temp = (_register >> 24) ^ b; + _register = (_register << 8) ^ crc32Table[temp]; + } + else + { + UInt32 temp = (_register & 0x000000FF) ^ b; + _register = (_register >> 8) ^ crc32Table[temp]; + } + } + _TotalBytesRead += count; + } + + + /// + /// Process one byte in the CRC. + /// + /// the byte to include into the CRC . + public void UpdateCRC(byte b) + { + if (this.reverseBits) + { + UInt32 temp = (_register >> 24) ^ b; + _register = (_register << 8) ^ crc32Table[temp]; + } + else + { + UInt32 temp = (_register & 0x000000FF) ^ b; + _register = (_register >> 8) ^ crc32Table[temp]; + } + } + + /// + /// Process a run of N identical bytes into the CRC. + /// + /// + /// + /// This method serves as an optimization for updating the CRC when a + /// run of identical bytes is found. Rather than passing in a buffer of + /// length n, containing all identical bytes b, this method accepts the + /// byte value and the length of the (virtual) buffer - the length of + /// the run. + /// + /// + /// the byte to include into the CRC. + /// the number of times that byte should be repeated. + public void UpdateCRC(byte b, int n) + { + while (n-- > 0) + { + if (this.reverseBits) + { + uint temp = (_register >> 24) ^ b; + _register = (_register << 8) ^ crc32Table[(temp >= 0) + ? temp + : (temp + 256)]; + } + else + { + UInt32 temp = (_register & 0x000000FF) ^ b; + _register = (_register >> 8) ^ crc32Table[(temp >= 0) + ? temp + : (temp + 256)]; + + } + } + } + + + + private static uint ReverseBits(uint data) + { + unchecked + { + uint ret = data; + ret = (ret & 0x55555555) << 1 | (ret >> 1) & 0x55555555; + ret = (ret & 0x33333333) << 2 | (ret >> 2) & 0x33333333; + ret = (ret & 0x0F0F0F0F) << 4 | (ret >> 4) & 0x0F0F0F0F; + ret = (ret << 24) | ((ret & 0xFF00) << 8) | ((ret >> 8) & 0xFF00) | (ret >> 24); + return ret; + } + } + + private static byte ReverseBits(byte data) + { + unchecked + { + uint u = (uint)data * 0x00020202; + uint m = 0x01044010; + uint s = u & m; + uint t = (u << 2) & (m << 1); + return (byte)((0x01001001 * (s + t)) >> 24); + } + } + + + + private void GenerateLookupTable() + { + crc32Table = new UInt32[256]; + unchecked + { + UInt32 dwCrc; + byte i = 0; + do + { + dwCrc = i; + for (byte j = 8; j > 0; j--) + { + if ((dwCrc & 1) == 1) + { + dwCrc = (dwCrc >> 1) ^ dwPolynomial; + } + else + { + dwCrc >>= 1; + } + } + if (reverseBits) + { + crc32Table[ReverseBits(i)] = ReverseBits(dwCrc); + } + else + { + crc32Table[i] = dwCrc; + } + i++; + } while (i!=0); + } + +#if VERBOSE + Console.WriteLine(); + Console.WriteLine("private static readonly UInt32[] crc32Table = {"); + for (int i = 0; i < crc32Table.Length; i+=4) + { + Console.Write(" "); + for (int j=0; j < 4; j++) + { + Console.Write(" 0x{0:X8}U,", crc32Table[i+j]); + } + Console.WriteLine(); + } + Console.WriteLine("};"); + Console.WriteLine(); +#endif + } + + + private uint gf2_matrix_times(uint[] matrix, uint vec) + { + uint sum = 0; + int i=0; + while (vec != 0) + { + if ((vec & 0x01)== 0x01) + sum ^= matrix[i]; + vec >>= 1; + i++; + } + return sum; + } + + private void gf2_matrix_square(uint[] square, uint[] mat) + { + for (int i = 0; i < 32; i++) + square[i] = gf2_matrix_times(mat, mat[i]); + } + + + + /// + /// Combines the given CRC32 value with the current running total. + /// + /// + /// This is useful when using a divide-and-conquer approach to + /// calculating a CRC. Multiple threads can each calculate a + /// CRC32 on a segment of the data, and then combine the + /// individual CRC32 values at the end. + /// + /// the crc value to be combined with this one + /// the length of data the CRC value was calculated on + public void Combine(int crc, int length) + { + uint[] even = new uint[32]; // even-power-of-two zeros operator + uint[] odd = new uint[32]; // odd-power-of-two zeros operator + + if (length == 0) + return; + + uint crc1= ~_register; + uint crc2= (uint) crc; + + // put operator for one zero bit in odd + odd[0] = this.dwPolynomial; // the CRC-32 polynomial + uint row = 1; + for (int i = 1; i < 32; i++) + { + odd[i] = row; + row <<= 1; + } + + // put operator for two zero bits in even + gf2_matrix_square(even, odd); + + // put operator for four zero bits in odd + gf2_matrix_square(odd, even); + + uint len2 = (uint) length; + + // apply len2 zeros to crc1 (first square will put the operator for one + // zero byte, eight zero bits, in even) + do { + // apply zeros operator for this bit of len2 + gf2_matrix_square(even, odd); + + if ((len2 & 1)== 1) + crc1 = gf2_matrix_times(even, crc1); + len2 >>= 1; + + if (len2 == 0) + break; + + // another iteration of the loop with odd and even swapped + gf2_matrix_square(odd, even); + if ((len2 & 1)==1) + crc1 = gf2_matrix_times(odd, crc1); + len2 >>= 1; + + + } while (len2 != 0); + + crc1 ^= crc2; + + _register= ~crc1; + + //return (int) crc1; + return; + } + + + /// + /// Create an instance of the CRC32 class using the default settings: no + /// bit reversal, and a polynomial of 0xEDB88320. + /// + public CRC32() : this(false) + { + } + + /// + /// Create an instance of the CRC32 class, specifying whether to reverse + /// data bits or not. + /// + /// + /// specify true if the instance should reverse data bits. + /// + /// + /// + /// In the CRC-32 used by BZip2, the bits are reversed. Therefore if you + /// want a CRC32 with compatibility with BZip2, you should pass true + /// here. In the CRC-32 used by GZIP and PKZIP, the bits are not + /// reversed; Therefore if you want a CRC32 with compatibility with + /// those, you should pass false. + /// + /// + public CRC32(bool reverseBits) : + this( unchecked((int)0xEDB88320), reverseBits) + { + } + + + /// + /// Create an instance of the CRC32 class, specifying the polynomial and + /// whether to reverse data bits or not. + /// + /// + /// The polynomial to use for the CRC, expressed in the reversed (LSB) + /// format: the highest ordered bit in the polynomial value is the + /// coefficient of the 0th power; the second-highest order bit is the + /// coefficient of the 1 power, and so on. Expressed this way, the + /// polynomial for the CRC-32C used in IEEE 802.3, is 0xEDB88320. + /// + /// + /// specify true if the instance should reverse data bits. + /// + /// + /// + /// + /// In the CRC-32 used by BZip2, the bits are reversed. Therefore if you + /// want a CRC32 with compatibility with BZip2, you should pass true + /// here for the reverseBits parameter. In the CRC-32 used by + /// GZIP and PKZIP, the bits are not reversed; Therefore if you want a + /// CRC32 with compatibility with those, you should pass false for the + /// reverseBits parameter. + /// + /// + public CRC32(int polynomial, bool reverseBits) + { + this.reverseBits = reverseBits; + this.dwPolynomial = (uint) polynomial; + this.GenerateLookupTable(); + } + + /// + /// Reset the CRC-32 class - clear the CRC "remainder register." + /// + /// + /// + /// Use this when employing a single instance of this class to compute + /// multiple, distinct CRCs on multiple, distinct data blocks. + /// + /// + public void Reset() + { + _register = 0xFFFFFFFFU; + } + + // private member vars + private UInt32 dwPolynomial; + private Int64 _TotalBytesRead; + private bool reverseBits; + private UInt32[] crc32Table; + private const int BUFFER_SIZE = 8192; + private UInt32 _register = 0xFFFFFFFFU; + } + + + /// + /// A Stream that calculates a CRC32 (a checksum) on all bytes read, + /// or on all bytes written. + /// + /// + /// + /// + /// This class can be used to verify the CRC of a ZipEntry when + /// reading from a stream, or to calculate a CRC when writing to a + /// stream. The stream should be used to either read, or write, but + /// not both. If you intermix reads and writes, the results are not + /// defined. + /// + /// + /// + /// This class is intended primarily for use internally by the + /// DotNetZip library. + /// + /// + public class CrcCalculatorStream : System.IO.Stream, System.IDisposable + { + private static readonly Int64 UnsetLengthLimit = -99; + + internal System.IO.Stream _innerStream; + private CRC32 _Crc32; + private Int64 _lengthLimit = -99; + private bool _leaveOpen; + + /// + /// The default constructor. + /// + /// + /// + /// Instances returned from this constructor will leave the underlying + /// stream open upon Close(). The stream uses the default CRC32 + /// algorithm, which implies a polynomial of 0xEDB88320. + /// + /// + /// The underlying stream + public CrcCalculatorStream(System.IO.Stream stream) + : this(true, CrcCalculatorStream.UnsetLengthLimit, stream, null) + { + } + + /// + /// The constructor allows the caller to specify how to handle the + /// underlying stream at close. + /// + /// + /// + /// The stream uses the default CRC32 algorithm, which implies a + /// polynomial of 0xEDB88320. + /// + /// + /// The underlying stream + /// true to leave the underlying stream + /// open upon close of the CrcCalculatorStream; false otherwise. + public CrcCalculatorStream(System.IO.Stream stream, bool leaveOpen) + : this(leaveOpen, CrcCalculatorStream.UnsetLengthLimit, stream, null) + { + } + + /// + /// A constructor allowing the specification of the length of the stream + /// to read. + /// + /// + /// + /// The stream uses the default CRC32 algorithm, which implies a + /// polynomial of 0xEDB88320. + /// + /// + /// Instances returned from this constructor will leave the underlying + /// stream open upon Close(). + /// + /// + /// The underlying stream + /// The length of the stream to slurp + public CrcCalculatorStream(System.IO.Stream stream, Int64 length) + : this(true, length, stream, null) + { + if (length < 0) + throw new ArgumentException("length"); + } + + /// + /// A constructor allowing the specification of the length of the stream + /// to read, as well as whether to keep the underlying stream open upon + /// Close(). + /// + /// + /// + /// The stream uses the default CRC32 algorithm, which implies a + /// polynomial of 0xEDB88320. + /// + /// + /// The underlying stream + /// The length of the stream to slurp + /// true to leave the underlying stream + /// open upon close of the CrcCalculatorStream; false otherwise. + public CrcCalculatorStream(System.IO.Stream stream, Int64 length, bool leaveOpen) + : this(leaveOpen, length, stream, null) + { + if (length < 0) + throw new ArgumentException("length"); + } + + /// + /// A constructor allowing the specification of the length of the stream + /// to read, as well as whether to keep the underlying stream open upon + /// Close(), and the CRC32 instance to use. + /// + /// + /// + /// The stream uses the specified CRC32 instance, which allows the + /// application to specify how the CRC gets calculated. + /// + /// + /// The underlying stream + /// The length of the stream to slurp + /// true to leave the underlying stream + /// open upon close of the CrcCalculatorStream; false otherwise. + /// the CRC32 instance to use to calculate the CRC32 + public CrcCalculatorStream(System.IO.Stream stream, Int64 length, bool leaveOpen, + CRC32 crc32) + : this(leaveOpen, length, stream, crc32) + { + if (length < 0) + throw new ArgumentException("length"); + } + + + // This ctor is private - no validation is done here. This is to allow the use + // of a (specific) negative value for the _lengthLimit, to indicate that there + // is no length set. So we validate the length limit in those ctors that use an + // explicit param, otherwise we don't validate, because it could be our special + // value. + private CrcCalculatorStream + (bool leaveOpen, Int64 length, System.IO.Stream stream, CRC32 crc32) + : base() + { + _innerStream = stream; + _Crc32 = crc32 ?? new CRC32(); + _lengthLimit = length; + _leaveOpen = leaveOpen; + } + + + /// + /// Gets the total number of bytes run through the CRC32 calculator. + /// + /// + /// + /// This is either the total number of bytes read, or the total number of + /// bytes written, depending on the direction of this stream. + /// + public Int64 TotalBytesSlurped + { + get { return _Crc32.TotalBytesRead; } + } + + /// + /// Provides the current CRC for all blocks slurped in. + /// + /// + /// + /// The running total of the CRC is kept as data is written or read + /// through the stream. read this property after all reads or writes to + /// get an accurate CRC for the entire stream. + /// + /// + public Int32 Crc + { + get { return _Crc32.Crc32Result; } + } + + /// + /// Indicates whether the underlying stream will be left open when the + /// CrcCalculatorStream is Closed. + /// + /// + /// + /// Set this at any point before calling . + /// + /// + public bool LeaveOpen + { + get { return _leaveOpen; } + set { _leaveOpen = value; } + } + + /// + /// Read from the stream + /// + /// the buffer to read + /// the offset at which to start + /// the number of bytes to read + /// the number of bytes actually read + public override int Read(byte[] buffer, int offset, int count) + { + int bytesToRead = count; + + // Need to limit the # of bytes returned, if the stream is intended to have + // a definite length. This is especially useful when returning a stream for + // the uncompressed data directly to the application. The app won't + // necessarily read only the UncompressedSize number of bytes. For example + // wrapping the stream returned from OpenReader() into a StreadReader() and + // calling ReadToEnd() on it, We can "over-read" the zip data and get a + // corrupt string. The length limits that, prevents that problem. + + if (_lengthLimit != CrcCalculatorStream.UnsetLengthLimit) + { + if (_Crc32.TotalBytesRead >= _lengthLimit) return 0; // EOF + Int64 bytesRemaining = _lengthLimit - _Crc32.TotalBytesRead; + if (bytesRemaining < count) bytesToRead = (int)bytesRemaining; + } + int n = _innerStream.Read(buffer, offset, bytesToRead); + if (n > 0) _Crc32.SlurpBlock(buffer, offset, n); + return n; + } + + /// + /// Write to the stream. + /// + /// the buffer from which to write + /// the offset at which to start writing + /// the number of bytes to write + public override void Write(byte[] buffer, int offset, int count) + { + if (count > 0) _Crc32.SlurpBlock(buffer, offset, count); + _innerStream.Write(buffer, offset, count); + } + + /// + /// Indicates whether the stream supports reading. + /// + public override bool CanRead + { + get { return _innerStream.CanRead; } + } + + /// + /// Indicates whether the stream supports seeking. + /// + /// + /// + /// Always returns false. + /// + /// + public override bool CanSeek + { + get { return false; } + } + + /// + /// Indicates whether the stream supports writing. + /// + public override bool CanWrite + { + get { return _innerStream.CanWrite; } + } + + /// + /// Flush the stream. + /// + public override void Flush() + { + _innerStream.Flush(); + } + + /// + /// Returns the length of the underlying stream. + /// + public override long Length + { + get + { + if (_lengthLimit == CrcCalculatorStream.UnsetLengthLimit) + return _innerStream.Length; + else return _lengthLimit; + } + } + + /// + /// The getter for this property returns the total bytes read. + /// If you use the setter, it will throw + /// . + /// + public override long Position + { + get { return _Crc32.TotalBytesRead; } + set { throw new NotSupportedException(); } + } + + /// + /// Seeking is not supported on this stream. This method always throws + /// + /// + /// N/A + /// N/A + /// N/A + public override long Seek(long offset, System.IO.SeekOrigin origin) + { + throw new NotSupportedException(); + } + + /// + /// This method always throws + /// + /// + /// N/A + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + + void IDisposable.Dispose() + { + Close(); + } + + /// + /// Closes the stream. + /// + public override void Close() + { + base.Close(); + if (!_leaveOpen) + _innerStream.Close(); + } + + } + +} \ No newline at end of file diff --git a/src/ZlibAndroidSdkStyle/Deflate.cs b/src/ZlibAndroidSdkStyle/Deflate.cs new file mode 100644 index 00000000..96722507 --- /dev/null +++ b/src/ZlibAndroidSdkStyle/Deflate.cs @@ -0,0 +1,1879 @@ +// Deflate.cs +// ------------------------------------------------------------------ +// +// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. +// All rights reserved. +// +// This code module is part of DotNetZip, a zipfile class library. +// +// ------------------------------------------------------------------ +// +// This code is licensed under the Microsoft Public License. +// See the file License.txt for the license details. +// More info on: http://dotnetzip.codeplex.com +// +// ------------------------------------------------------------------ +// +// last saved (in emacs): +// Time-stamp: <2011-August-03 19:52:15> +// +// ------------------------------------------------------------------ +// +// This module defines logic for handling the Deflate or compression. +// +// This code is based on multiple sources: +// - the original zlib v1.2.3 source, which is Copyright (C) 1995-2005 Jean-loup Gailly. +// - the original jzlib, which is Copyright (c) 2000-2003 ymnk, JCraft,Inc. +// +// However, this code is significantly different from both. +// The object model is not the same, and many of the behaviors are different. +// +// In keeping with the license for these other works, the copyrights for +// jzlib and zlib are here. +// +// ----------------------------------------------------------------------- +// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the distribution. +// +// 3. The names of the authors may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------- +// +// This program is based on zlib-1.1.3; credit to authors +// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) +// and contributors of zlib. +// +// ----------------------------------------------------------------------- + + +using System; + +namespace Ionic.Zlib +{ + + internal enum BlockState + { + NeedMore = 0, // block not completed, need more input or more output + BlockDone, // block flush performed + FinishStarted, // finish started, need only more output at next deflate + FinishDone // finish done, accept no more input or output + } + + internal enum DeflateFlavor + { + Store, + Fast, + Slow + } + + internal sealed class DeflateManager + { + private static readonly int MEM_LEVEL_MAX = 9; + private static readonly int MEM_LEVEL_DEFAULT = 8; + + internal delegate BlockState CompressFunc(FlushType flush); + + internal class Config + { + // Use a faster search when the previous match is longer than this + internal int GoodLength; // reduce lazy search above this match length + + // Attempt to find a better match only when the current match is + // strictly smaller than this value. This mechanism is used only for + // compression levels >= 4. For levels 1,2,3: MaxLazy is actually + // MaxInsertLength. (See DeflateFast) + + internal int MaxLazy; // do not perform lazy search above this match length + + internal int NiceLength; // quit search above this match length + + // To speed up deflation, hash chains are never searched beyond this + // length. A higher limit improves compression ratio but degrades the speed. + + internal int MaxChainLength; + + internal DeflateFlavor Flavor; + + private Config(int goodLength, int maxLazy, int niceLength, int maxChainLength, DeflateFlavor flavor) + { + this.GoodLength = goodLength; + this.MaxLazy = maxLazy; + this.NiceLength = niceLength; + this.MaxChainLength = maxChainLength; + this.Flavor = flavor; + } + + public static Config Lookup(CompressionLevel level) + { + return Table[(int)level]; + } + + + static Config() + { + Table = new Config[] { + new Config(0, 0, 0, 0, DeflateFlavor.Store), + new Config(4, 4, 8, 4, DeflateFlavor.Fast), + new Config(4, 5, 16, 8, DeflateFlavor.Fast), + new Config(4, 6, 32, 32, DeflateFlavor.Fast), + + new Config(4, 4, 16, 16, DeflateFlavor.Slow), + new Config(8, 16, 32, 32, DeflateFlavor.Slow), + new Config(8, 16, 128, 128, DeflateFlavor.Slow), + new Config(8, 32, 128, 256, DeflateFlavor.Slow), + new Config(32, 128, 258, 1024, DeflateFlavor.Slow), + new Config(32, 258, 258, 4096, DeflateFlavor.Slow), + }; + } + + private static readonly Config[] Table; + } + + + private CompressFunc DeflateFunction; + + private static readonly System.String[] _ErrorMessage = new System.String[] + { + "need dictionary", + "stream end", + "", + "file error", + "stream error", + "data error", + "insufficient memory", + "buffer error", + "incompatible version", + "" + }; + + // preset dictionary flag in zlib header + private static readonly int PRESET_DICT = 0x20; + + private static readonly int INIT_STATE = 42; + private static readonly int BUSY_STATE = 113; + private static readonly int FINISH_STATE = 666; + + // The deflate compression method + private static readonly int Z_DEFLATED = 8; + + private static readonly int STORED_BLOCK = 0; + private static readonly int STATIC_TREES = 1; + private static readonly int DYN_TREES = 2; + + // The three kinds of block type + private static readonly int Z_BINARY = 0; + private static readonly int Z_ASCII = 1; + private static readonly int Z_UNKNOWN = 2; + + private static readonly int Buf_size = 8 * 2; + + private static readonly int MIN_MATCH = 3; + private static readonly int MAX_MATCH = 258; + + private static readonly int MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1); + + private static readonly int HEAP_SIZE = (2 * InternalConstants.L_CODES + 1); + + private static readonly int END_BLOCK = 256; + + internal ZlibCodec _codec; // the zlib encoder/decoder + internal int status; // as the name implies + internal byte[] pending; // output still pending - waiting to be compressed + internal int nextPending; // index of next pending byte to output to the stream + internal int pendingCount; // number of bytes in the pending buffer + + internal sbyte data_type; // UNKNOWN, BINARY or ASCII + internal int last_flush; // value of flush param for previous deflate call + + internal int w_size; // LZ77 window size (32K by default) + internal int w_bits; // log2(w_size) (8..16) + internal int w_mask; // w_size - 1 + + //internal byte[] dictionary; + internal byte[] window; + + // Sliding window. Input bytes are read into the second half of the window, + // and move to the first half later to keep a dictionary of at least wSize + // bytes. With this organization, matches are limited to a distance of + // wSize-MAX_MATCH bytes, but this ensures that IO is always + // performed with a length multiple of the block size. + // + // To do: use the user input buffer as sliding window. + + internal int window_size; + // Actual size of window: 2*wSize, except when the user input buffer + // is directly used as sliding window. + + internal short[] prev; + // Link to older string with same hash index. To limit the size of this + // array to 64K, this link is maintained only for the last 32K strings. + // An index in this array is thus a window index modulo 32K. + + internal short[] head; // Heads of the hash chains or NIL. + + internal int ins_h; // hash index of string to be inserted + internal int hash_size; // number of elements in hash table + internal int hash_bits; // log2(hash_size) + internal int hash_mask; // hash_size-1 + + // Number of bits by which ins_h must be shifted at each input + // step. It must be such that after MIN_MATCH steps, the oldest + // byte no longer takes part in the hash key, that is: + // hash_shift * MIN_MATCH >= hash_bits + internal int hash_shift; + + // Window position at the beginning of the current output block. Gets + // negative when the window is moved backwards. + + internal int block_start; + + Config config; + internal int match_length; // length of best match + internal int prev_match; // previous match + internal int match_available; // set if previous match exists + internal int strstart; // start of string to insert into.....???? + internal int match_start; // start of matching string + internal int lookahead; // number of valid bytes ahead in window + + // Length of the best match at previous step. Matches not greater than this + // are discarded. This is used in the lazy match evaluation. + internal int prev_length; + + // Insert new strings in the hash table only if the match length is not + // greater than this length. This saves time but degrades compression. + // max_insert_length is used only for compression levels <= 3. + + internal CompressionLevel compressionLevel; // compression level (1..9) + internal CompressionStrategy compressionStrategy; // favor or force Huffman coding + + + internal short[] dyn_ltree; // literal and length tree + internal short[] dyn_dtree; // distance tree + internal short[] bl_tree; // Huffman tree for bit lengths + + internal Tree treeLiterals = new Tree(); // desc for literal tree + internal Tree treeDistances = new Tree(); // desc for distance tree + internal Tree treeBitLengths = new Tree(); // desc for bit length tree + + // number of codes at each bit length for an optimal tree + internal short[] bl_count = new short[InternalConstants.MAX_BITS + 1]; + + // heap used to build the Huffman trees + internal int[] heap = new int[2 * InternalConstants.L_CODES + 1]; + + internal int heap_len; // number of elements in the heap + internal int heap_max; // element of largest frequency + + // The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + // The same heap array is used to build all trees. + + // Depth of each subtree used as tie breaker for trees of equal frequency + internal sbyte[] depth = new sbyte[2 * InternalConstants.L_CODES + 1]; + + internal int _lengthOffset; // index for literals or lengths + + + // Size of match buffer for literals/lengths. There are 4 reasons for + // limiting lit_bufsize to 64K: + // - frequencies can be kept in 16 bit counters + // - if compression is not successful for the first block, all input + // data is still in the window so we can still emit a stored block even + // when input comes from standard input. (This can also be done for + // all blocks if lit_bufsize is not greater than 32K.) + // - if compression is not successful for a file smaller than 64K, we can + // even emit a stored file instead of a stored block (saving 5 bytes). + // This is applicable only for zip (not gzip or zlib). + // - creating new Huffman trees less frequently may not provide fast + // adaptation to changes in the input data statistics. (Take for + // example a binary file with poorly compressible code followed by + // a highly compressible string table.) Smaller buffer sizes give + // fast adaptation but have of course the overhead of transmitting + // trees more frequently. + + internal int lit_bufsize; + + internal int last_lit; // running index in l_buf + + // Buffer for distances. To simplify the code, d_buf and l_buf have + // the same number of elements. To use different lengths, an extra flag + // array would be necessary. + + internal int _distanceOffset; // index into pending; points to distance data?? + + internal int opt_len; // bit length of current block with optimal trees + internal int static_len; // bit length of current block with static trees + internal int matches; // number of string matches in current block + internal int last_eob_len; // bit length of EOB code for last block + + // Output buffer. bits are inserted starting at the bottom (least + // significant bits). + internal short bi_buf; + + // Number of valid bits in bi_buf. All bits above the last valid bit + // are always zero. + internal int bi_valid; + + + internal DeflateManager() + { + dyn_ltree = new short[HEAP_SIZE * 2]; + dyn_dtree = new short[(2 * InternalConstants.D_CODES + 1) * 2]; // distance tree + bl_tree = new short[(2 * InternalConstants.BL_CODES + 1) * 2]; // Huffman tree for bit lengths + } + + + // lm_init + private void _InitializeLazyMatch() + { + window_size = 2 * w_size; + + // clear the hash - workitem 9063 + Array.Clear(head, 0, hash_size); + //for (int i = 0; i < hash_size; i++) head[i] = 0; + + config = Config.Lookup(compressionLevel); + SetDeflater(); + + strstart = 0; + block_start = 0; + lookahead = 0; + match_length = prev_length = MIN_MATCH - 1; + match_available = 0; + ins_h = 0; + } + + // Initialize the tree data structures for a new zlib stream. + private void _InitializeTreeData() + { + treeLiterals.dyn_tree = dyn_ltree; + treeLiterals.staticTree = StaticTree.Literals; + + treeDistances.dyn_tree = dyn_dtree; + treeDistances.staticTree = StaticTree.Distances; + + treeBitLengths.dyn_tree = bl_tree; + treeBitLengths.staticTree = StaticTree.BitLengths; + + bi_buf = 0; + bi_valid = 0; + last_eob_len = 8; // enough lookahead for inflate + + // Initialize the first block of the first file: + _InitializeBlocks(); + } + + internal void _InitializeBlocks() + { + // Initialize the trees. + for (int i = 0; i < InternalConstants.L_CODES; i++) + dyn_ltree[i * 2] = 0; + for (int i = 0; i < InternalConstants.D_CODES; i++) + dyn_dtree[i * 2] = 0; + for (int i = 0; i < InternalConstants.BL_CODES; i++) + bl_tree[i * 2] = 0; + + dyn_ltree[END_BLOCK * 2] = 1; + opt_len = static_len = 0; + last_lit = matches = 0; + } + + // Restore the heap property by moving down the tree starting at node k, + // exchanging a node with the smallest of its two sons if necessary, stopping + // when the heap property is re-established (each father smaller than its + // two sons). + internal void pqdownheap(short[] tree, int k) + { + int v = heap[k]; + int j = k << 1; // left son of k + while (j <= heap_len) + { + // Set j to the smallest of the two sons: + if (j < heap_len && _IsSmaller(tree, heap[j + 1], heap[j], depth)) + { + j++; + } + // Exit if v is smaller than both sons + if (_IsSmaller(tree, v, heap[j], depth)) + break; + + // Exchange v with the smallest son + heap[k] = heap[j]; k = j; + // And continue down the tree, setting j to the left son of k + j <<= 1; + } + heap[k] = v; + } + + internal static bool _IsSmaller(short[] tree, int n, int m, sbyte[] depth) + { + short tn2 = tree[n * 2]; + short tm2 = tree[m * 2]; + return (tn2 < tm2 || (tn2 == tm2 && depth[n] <= depth[m])); + } + + + // Scan a literal or distance tree to determine the frequencies of the codes + // in the bit length tree. + internal void scan_tree(short[] tree, int max_code) + { + int n; // iterates over all tree elements + int prevlen = -1; // last emitted length + int curlen; // length of current code + int nextlen = (int)tree[0 * 2 + 1]; // length of next code + int count = 0; // repeat count of the current code + int max_count = 7; // max repeat count + int min_count = 4; // min repeat count + + if (nextlen == 0) + { + max_count = 138; min_count = 3; + } + tree[(max_code + 1) * 2 + 1] = (short)0x7fff; // guard //?? + + for (n = 0; n <= max_code; n++) + { + curlen = nextlen; nextlen = (int)tree[(n + 1) * 2 + 1]; + if (++count < max_count && curlen == nextlen) + { + continue; + } + else if (count < min_count) + { + bl_tree[curlen * 2] = (short)(bl_tree[curlen * 2] + count); + } + else if (curlen != 0) + { + if (curlen != prevlen) + bl_tree[curlen * 2]++; + bl_tree[InternalConstants.REP_3_6 * 2]++; + } + else if (count <= 10) + { + bl_tree[InternalConstants.REPZ_3_10 * 2]++; + } + else + { + bl_tree[InternalConstants.REPZ_11_138 * 2]++; + } + count = 0; prevlen = curlen; + if (nextlen == 0) + { + max_count = 138; min_count = 3; + } + else if (curlen == nextlen) + { + max_count = 6; min_count = 3; + } + else + { + max_count = 7; min_count = 4; + } + } + } + + // Construct the Huffman tree for the bit lengths and return the index in + // bl_order of the last bit length code to send. + internal int build_bl_tree() + { + int max_blindex; // index of last bit length code of non zero freq + + // Determine the bit length frequencies for literal and distance trees + scan_tree(dyn_ltree, treeLiterals.max_code); + scan_tree(dyn_dtree, treeDistances.max_code); + + // Build the bit length tree: + treeBitLengths.build_tree(this); + // opt_len now includes the length of the tree representations, except + // the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + + // Determine the number of bit length codes to send. The pkzip format + // requires that at least 4 bit length codes be sent. (appnote.txt says + // 3 but the actual value used is 4.) + for (max_blindex = InternalConstants.BL_CODES - 1; max_blindex >= 3; max_blindex--) + { + if (bl_tree[Tree.bl_order[max_blindex] * 2 + 1] != 0) + break; + } + // Update opt_len to include the bit length tree and counts + opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; + + return max_blindex; + } + + + // Send the header for a block using dynamic Huffman trees: the counts, the + // lengths of the bit length codes, the literal tree and the distance tree. + // IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + internal void send_all_trees(int lcodes, int dcodes, int blcodes) + { + int rank; // index in bl_order + + send_bits(lcodes - 257, 5); // not +255 as stated in appnote.txt + send_bits(dcodes - 1, 5); + send_bits(blcodes - 4, 4); // not -3 as stated in appnote.txt + for (rank = 0; rank < blcodes; rank++) + { + send_bits(bl_tree[Tree.bl_order[rank] * 2 + 1], 3); + } + send_tree(dyn_ltree, lcodes - 1); // literal tree + send_tree(dyn_dtree, dcodes - 1); // distance tree + } + + // Send a literal or distance tree in compressed form, using the codes in + // bl_tree. + internal void send_tree(short[] tree, int max_code) + { + int n; // iterates over all tree elements + int prevlen = -1; // last emitted length + int curlen; // length of current code + int nextlen = tree[0 * 2 + 1]; // length of next code + int count = 0; // repeat count of the current code + int max_count = 7; // max repeat count + int min_count = 4; // min repeat count + + if (nextlen == 0) + { + max_count = 138; min_count = 3; + } + + for (n = 0; n <= max_code; n++) + { + curlen = nextlen; nextlen = tree[(n + 1) * 2 + 1]; + if (++count < max_count && curlen == nextlen) + { + continue; + } + else if (count < min_count) + { + do + { + send_code(curlen, bl_tree); + } + while (--count != 0); + } + else if (curlen != 0) + { + if (curlen != prevlen) + { + send_code(curlen, bl_tree); count--; + } + send_code(InternalConstants.REP_3_6, bl_tree); + send_bits(count - 3, 2); + } + else if (count <= 10) + { + send_code(InternalConstants.REPZ_3_10, bl_tree); + send_bits(count - 3, 3); + } + else + { + send_code(InternalConstants.REPZ_11_138, bl_tree); + send_bits(count - 11, 7); + } + count = 0; prevlen = curlen; + if (nextlen == 0) + { + max_count = 138; min_count = 3; + } + else if (curlen == nextlen) + { + max_count = 6; min_count = 3; + } + else + { + max_count = 7; min_count = 4; + } + } + } + + // Output a block of bytes on the stream. + // IN assertion: there is enough room in pending_buf. + private void put_bytes(byte[] p, int start, int len) + { + Array.Copy(p, start, pending, pendingCount, len); + pendingCount += len; + } + +#if NOTNEEDED + private void put_byte(byte c) + { + pending[pendingCount++] = c; + } + internal void put_short(int b) + { + unchecked + { + pending[pendingCount++] = (byte)b; + pending[pendingCount++] = (byte)(b >> 8); + } + } + internal void putShortMSB(int b) + { + unchecked + { + pending[pendingCount++] = (byte)(b >> 8); + pending[pendingCount++] = (byte)b; + } + } +#endif + + internal void send_code(int c, short[] tree) + { + int c2 = c * 2; + send_bits((tree[c2] & 0xffff), (tree[c2 + 1] & 0xffff)); + } + + internal void send_bits(int value, int length) + { + int len = length; + unchecked + { + if (bi_valid > (int)Buf_size - len) + { + //int val = value; + // bi_buf |= (val << bi_valid); + + bi_buf |= (short)((value << bi_valid) & 0xffff); + //put_short(bi_buf); + pending[pendingCount++] = (byte)bi_buf; + pending[pendingCount++] = (byte)(bi_buf >> 8); + + + bi_buf = (short)((uint)value >> (Buf_size - bi_valid)); + bi_valid += len - Buf_size; + } + else + { + // bi_buf |= (value) << bi_valid; + bi_buf |= (short)((value << bi_valid) & 0xffff); + bi_valid += len; + } + } + } + + // Send one empty static block to give enough lookahead for inflate. + // This takes 10 bits, of which 7 may remain in the bit buffer. + // The current inflate code requires 9 bits of lookahead. If the + // last two codes for the previous block (real code plus EOB) were coded + // on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode + // the last real code. In this case we send two empty static blocks instead + // of one. (There are no problems if the previous block is stored or fixed.) + // To simplify the code, we assume the worst case of last real code encoded + // on one bit only. + internal void _tr_align() + { + send_bits(STATIC_TREES << 1, 3); + send_code(END_BLOCK, StaticTree.lengthAndLiteralsTreeCodes); + + bi_flush(); + + // Of the 10 bits for the empty block, we have already sent + // (10 - bi_valid) bits. The lookahead for the last real code (before + // the EOB of the previous block) was thus at least one plus the length + // of the EOB plus what we have just sent of the empty static block. + if (1 + last_eob_len + 10 - bi_valid < 9) + { + send_bits(STATIC_TREES << 1, 3); + send_code(END_BLOCK, StaticTree.lengthAndLiteralsTreeCodes); + bi_flush(); + } + last_eob_len = 7; + } + + + // Save the match info and tally the frequency counts. Return true if + // the current block must be flushed. + internal bool _tr_tally(int dist, int lc) + { + pending[_distanceOffset + last_lit * 2] = unchecked((byte) ( (uint)dist >> 8 ) ); + pending[_distanceOffset + last_lit * 2 + 1] = unchecked((byte)dist); + pending[_lengthOffset + last_lit] = unchecked((byte)lc); + last_lit++; + + if (dist == 0) + { + // lc is the unmatched char + dyn_ltree[lc * 2]++; + } + else + { + matches++; + // Here, lc is the match length - MIN_MATCH + dist--; // dist = match distance - 1 + dyn_ltree[(Tree.LengthCode[lc] + InternalConstants.LITERALS + 1) * 2]++; + dyn_dtree[Tree.DistanceCode(dist) * 2]++; + } + + if ((last_lit & 0x1fff) == 0 && (int)compressionLevel > 2) + { + // Compute an upper bound for the compressed length + int out_length = last_lit << 3; + int in_length = strstart - block_start; + int dcode; + for (dcode = 0; dcode < InternalConstants.D_CODES; dcode++) + { + out_length = (int)(out_length + (int)dyn_dtree[dcode * 2] * (5L + Tree.ExtraDistanceBits[dcode])); + } + out_length >>= 3; + if ((matches < (last_lit / 2)) && out_length < in_length / 2) + return true; + } + + return (last_lit == lit_bufsize - 1) || (last_lit == lit_bufsize); + // dinoch - wraparound? + // We avoid equality with lit_bufsize because of wraparound at 64K + // on 16 bit machines and because stored blocks are restricted to + // 64K-1 bytes. + } + + + + // Send the block data compressed using the given Huffman trees + internal void send_compressed_block(short[] ltree, short[] dtree) + { + int distance; // distance of matched string + int lc; // match length or unmatched char (if dist == 0) + int lx = 0; // running index in l_buf + int code; // the code to send + int extra; // number of extra bits to send + + if (last_lit != 0) + { + do + { + int ix = _distanceOffset + lx * 2; + distance = ((pending[ix] << 8) & 0xff00) | + (pending[ix + 1] & 0xff); + lc = (pending[_lengthOffset + lx]) & 0xff; + lx++; + + if (distance == 0) + { + send_code(lc, ltree); // send a literal byte + } + else + { + // literal or match pair + // Here, lc is the match length - MIN_MATCH + code = Tree.LengthCode[lc]; + + // send the length code + send_code(code + InternalConstants.LITERALS + 1, ltree); + extra = Tree.ExtraLengthBits[code]; + if (extra != 0) + { + // send the extra length bits + lc -= Tree.LengthBase[code]; + send_bits(lc, extra); + } + distance--; // dist is now the match distance - 1 + code = Tree.DistanceCode(distance); + + // send the distance code + send_code(code, dtree); + + extra = Tree.ExtraDistanceBits[code]; + if (extra != 0) + { + // send the extra distance bits + distance -= Tree.DistanceBase[code]; + send_bits(distance, extra); + } + } + + // Check that the overlay between pending and d_buf+l_buf is ok: + } + while (lx < last_lit); + } + + send_code(END_BLOCK, ltree); + last_eob_len = ltree[END_BLOCK * 2 + 1]; + } + + + + // Set the data type to ASCII or BINARY, using a crude approximation: + // binary if more than 20% of the bytes are <= 6 or >= 128, ascii otherwise. + // IN assertion: the fields freq of dyn_ltree are set and the total of all + // frequencies does not exceed 64K (to fit in an int on 16 bit machines). + internal void set_data_type() + { + int n = 0; + int ascii_freq = 0; + int bin_freq = 0; + while (n < 7) + { + bin_freq += dyn_ltree[n * 2]; n++; + } + while (n < 128) + { + ascii_freq += dyn_ltree[n * 2]; n++; + } + while (n < InternalConstants.LITERALS) + { + bin_freq += dyn_ltree[n * 2]; n++; + } + data_type = (sbyte)(bin_freq > (ascii_freq >> 2) ? Z_BINARY : Z_ASCII); + } + + + + // Flush the bit buffer, keeping at most 7 bits in it. + internal void bi_flush() + { + if (bi_valid == 16) + { + pending[pendingCount++] = (byte)bi_buf; + pending[pendingCount++] = (byte)(bi_buf >> 8); + bi_buf = 0; + bi_valid = 0; + } + else if (bi_valid >= 8) + { + //put_byte((byte)bi_buf); + pending[pendingCount++] = (byte)bi_buf; + bi_buf >>= 8; + bi_valid -= 8; + } + } + + // Flush the bit buffer and align the output on a byte boundary + internal void bi_windup() + { + if (bi_valid > 8) + { + pending[pendingCount++] = (byte)bi_buf; + pending[pendingCount++] = (byte)(bi_buf >> 8); + } + else if (bi_valid > 0) + { + //put_byte((byte)bi_buf); + pending[pendingCount++] = (byte)bi_buf; + } + bi_buf = 0; + bi_valid = 0; + } + + // Copy a stored block, storing first the length and its + // one's complement if requested. + internal void copy_block(int buf, int len, bool header) + { + bi_windup(); // align on byte boundary + last_eob_len = 8; // enough lookahead for inflate + + if (header) + unchecked + { + //put_short((short)len); + pending[pendingCount++] = (byte)len; + pending[pendingCount++] = (byte)(len >> 8); + //put_short((short)~len); + pending[pendingCount++] = (byte)~len; + pending[pendingCount++] = (byte)(~len >> 8); + } + + put_bytes(window, buf, len); + } + + internal void flush_block_only(bool eof) + { + _tr_flush_block(block_start >= 0 ? block_start : -1, strstart - block_start, eof); + block_start = strstart; + _codec.flush_pending(); + } + + // Copy without compression as much as possible from the input stream, return + // the current block state. + // This function does not insert new strings in the dictionary since + // uncompressible data is probably not useful. This function is used + // only for the level=0 compression option. + // NOTE: this function should be optimized to avoid extra copying from + // window to pending_buf. + internal BlockState DeflateNone(FlushType flush) + { + // Stored blocks are limited to 0xffff bytes, pending is limited + // to pending_buf_size, and each stored block has a 5 byte header: + + int max_block_size = 0xffff; + int max_start; + + if (max_block_size > pending.Length - 5) + { + max_block_size = pending.Length - 5; + } + + // Copy as much as possible from input to output: + while (true) + { + // Fill the window as much as possible: + if (lookahead <= 1) + { + _fillWindow(); + if (lookahead == 0 && flush == FlushType.None) + return BlockState.NeedMore; + if (lookahead == 0) + break; // flush the current block + } + + strstart += lookahead; + lookahead = 0; + + // Emit a stored block if pending will be full: + max_start = block_start + max_block_size; + if (strstart == 0 || strstart >= max_start) + { + // strstart == 0 is possible when wraparound on 16-bit machine + lookahead = (int)(strstart - max_start); + strstart = (int)max_start; + + flush_block_only(false); + if (_codec.AvailableBytesOut == 0) + return BlockState.NeedMore; + } + + // Flush if we may have to slide, otherwise block_start may become + // negative and the data will be gone: + if (strstart - block_start >= w_size - MIN_LOOKAHEAD) + { + flush_block_only(false); + if (_codec.AvailableBytesOut == 0) + return BlockState.NeedMore; + } + } + + flush_block_only(flush == FlushType.Finish); + if (_codec.AvailableBytesOut == 0) + return (flush == FlushType.Finish) ? BlockState.FinishStarted : BlockState.NeedMore; + + return flush == FlushType.Finish ? BlockState.FinishDone : BlockState.BlockDone; + } + + + // Send a stored block + internal void _tr_stored_block(int buf, int stored_len, bool eof) + { + send_bits((STORED_BLOCK << 1) + (eof ? 1 : 0), 3); // send block type + copy_block(buf, stored_len, true); // with header + } + + // Determine the best encoding for the current block: dynamic trees, static + // trees or store, and output the encoded block to the zip file. + internal void _tr_flush_block(int buf, int stored_len, bool eof) + { + int opt_lenb, static_lenb; // opt_len and static_len in bytes + int max_blindex = 0; // index of last bit length code of non zero freq + + // Build the Huffman trees unless a stored block is forced + if (compressionLevel > 0) + { + // Check if the file is ascii or binary + if (data_type == Z_UNKNOWN) + set_data_type(); + + // Construct the literal and distance trees + treeLiterals.build_tree(this); + + treeDistances.build_tree(this); + + // At this point, opt_len and static_len are the total bit lengths of + // the compressed block data, excluding the tree representations. + + // Build the bit length tree for the above two trees, and get the index + // in bl_order of the last bit length code to send. + max_blindex = build_bl_tree(); + + // Determine the best encoding. Compute first the block length in bytes + opt_lenb = (opt_len + 3 + 7) >> 3; + static_lenb = (static_len + 3 + 7) >> 3; + + if (static_lenb <= opt_lenb) + opt_lenb = static_lenb; + } + else + { + opt_lenb = static_lenb = stored_len + 5; // force a stored block + } + + if (stored_len + 4 <= opt_lenb && buf != -1) + { + // 4: two words for the lengths + // The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + // Otherwise we can't have processed more than WSIZE input bytes since + // the last block flush, because compression would have been + // successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + // transform a block into a stored block. + _tr_stored_block(buf, stored_len, eof); + } + else if (static_lenb == opt_lenb) + { + send_bits((STATIC_TREES << 1) + (eof ? 1 : 0), 3); + send_compressed_block(StaticTree.lengthAndLiteralsTreeCodes, StaticTree.distTreeCodes); + } + else + { + send_bits((DYN_TREES << 1) + (eof ? 1 : 0), 3); + send_all_trees(treeLiterals.max_code + 1, treeDistances.max_code + 1, max_blindex + 1); + send_compressed_block(dyn_ltree, dyn_dtree); + } + + // The above check is made mod 2^32, for files larger than 512 MB + // and uLong implemented on 32 bits. + + _InitializeBlocks(); + + if (eof) + { + bi_windup(); + } + } + + // Fill the window when the lookahead becomes insufficient. + // Updates strstart and lookahead. + // + // IN assertion: lookahead < MIN_LOOKAHEAD + // OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + // At least one byte has been read, or avail_in == 0; reads are + // performed for at least two bytes (required for the zip translate_eol + // option -- not supported here). + private void _fillWindow() + { + int n, m; + int p; + int more; // Amount of free space at the end of the window. + + do + { + more = (window_size - lookahead - strstart); + + // Deal with !@#$% 64K limit: + if (more == 0 && strstart == 0 && lookahead == 0) + { + more = w_size; + } + else if (more == -1) + { + // Very unlikely, but possible on 16 bit machine if strstart == 0 + // and lookahead == 1 (input done one byte at time) + more--; + + // If the window is almost full and there is insufficient lookahead, + // move the upper half to the lower one to make room in the upper half. + } + else if (strstart >= w_size + w_size - MIN_LOOKAHEAD) + { + Array.Copy(window, w_size, window, 0, w_size); + match_start -= w_size; + strstart -= w_size; // we now have strstart >= MAX_DIST + block_start -= w_size; + + // Slide the hash table (could be avoided with 32 bit values + // at the expense of memory usage). We slide even when level == 0 + // to keep the hash table consistent if we switch back to level > 0 + // later. (Using level 0 permanently is not an optimal usage of + // zlib, so we don't care about this pathological case.) + + n = hash_size; + p = n; + do + { + m = (head[--p] & 0xffff); + head[p] = (short)((m >= w_size) ? (m - w_size) : 0); + } + while (--n != 0); + + n = w_size; + p = n; + do + { + m = (prev[--p] & 0xffff); + prev[p] = (short)((m >= w_size) ? (m - w_size) : 0); + // If n is not on any hash chain, prev[n] is garbage but + // its value will never be used. + } + while (--n != 0); + more += w_size; + } + + if (_codec.AvailableBytesIn == 0) + return; + + // If there was no sliding: + // strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + // more == window_size - lookahead - strstart + // => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + // => more >= window_size - 2*WSIZE + 2 + // In the BIG_MEM or MMAP case (not yet supported), + // window_size == input_size + MIN_LOOKAHEAD && + // strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + // Otherwise, window_size == 2*WSIZE so more >= 2. + // If there was sliding, more >= WSIZE. So in all cases, more >= 2. + + n = _codec.read_buf(window, strstart + lookahead, more); + lookahead += n; + + // Initialize the hash value now that we have some input: + if (lookahead >= MIN_MATCH) + { + ins_h = window[strstart] & 0xff; + ins_h = (((ins_h) << hash_shift) ^ (window[strstart + 1] & 0xff)) & hash_mask; + } + // If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + // but this is not important since only literal bytes will be emitted. + } + while (lookahead < MIN_LOOKAHEAD && _codec.AvailableBytesIn != 0); + } + + // Compress as much as possible from the input stream, return the current + // block state. + // This function does not perform lazy evaluation of matches and inserts + // new strings in the dictionary only for unmatched strings or for short + // matches. It is used only for the fast compression options. + internal BlockState DeflateFast(FlushType flush) + { + // short hash_head = 0; // head of the hash chain + int hash_head = 0; // head of the hash chain + bool bflush; // set if current block must be flushed + + while (true) + { + // Make sure that we always have enough lookahead, except + // at the end of the input file. We need MAX_MATCH bytes + // for the next match, plus MIN_MATCH bytes to insert the + // string following the next match. + if (lookahead < MIN_LOOKAHEAD) + { + _fillWindow(); + if (lookahead < MIN_LOOKAHEAD && flush == FlushType.None) + { + return BlockState.NeedMore; + } + if (lookahead == 0) + break; // flush the current block + } + + // Insert the string window[strstart .. strstart+2] in the + // dictionary, and set hash_head to the head of the hash chain: + if (lookahead >= MIN_MATCH) + { + ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask; + + // prev[strstart&w_mask]=hash_head=head[ins_h]; + hash_head = (head[ins_h] & 0xffff); + prev[strstart & w_mask] = head[ins_h]; + head[ins_h] = unchecked((short)strstart); + } + + // Find the longest match, discarding those <= prev_length. + // At this point we have always match_length < MIN_MATCH + + if (hash_head != 0L && ((strstart - hash_head) & 0xffff) <= w_size - MIN_LOOKAHEAD) + { + // To simplify the code, we prevent matches with the string + // of window index 0 (in particular we have to avoid a match + // of the string with itself at the start of the input file). + if (compressionStrategy != CompressionStrategy.HuffmanOnly) + { + match_length = longest_match(hash_head); + } + // longest_match() sets match_start + } + if (match_length >= MIN_MATCH) + { + // check_match(strstart, match_start, match_length); + + bflush = _tr_tally(strstart - match_start, match_length - MIN_MATCH); + + lookahead -= match_length; + + // Insert new strings in the hash table only if the match length + // is not too large. This saves time but degrades compression. + if (match_length <= config.MaxLazy && lookahead >= MIN_MATCH) + { + match_length--; // string at strstart already in hash table + do + { + strstart++; + + ins_h = ((ins_h << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask; + // prev[strstart&w_mask]=hash_head=head[ins_h]; + hash_head = (head[ins_h] & 0xffff); + prev[strstart & w_mask] = head[ins_h]; + head[ins_h] = unchecked((short)strstart); + + // strstart never exceeds WSIZE-MAX_MATCH, so there are + // always MIN_MATCH bytes ahead. + } + while (--match_length != 0); + strstart++; + } + else + { + strstart += match_length; + match_length = 0; + ins_h = window[strstart] & 0xff; + + ins_h = (((ins_h) << hash_shift) ^ (window[strstart + 1] & 0xff)) & hash_mask; + // If lookahead < MIN_MATCH, ins_h is garbage, but it does not + // matter since it will be recomputed at next deflate call. + } + } + else + { + // No match, output a literal byte + + bflush = _tr_tally(0, window[strstart] & 0xff); + lookahead--; + strstart++; + } + if (bflush) + { + flush_block_only(false); + if (_codec.AvailableBytesOut == 0) + return BlockState.NeedMore; + } + } + + flush_block_only(flush == FlushType.Finish); + if (_codec.AvailableBytesOut == 0) + { + if (flush == FlushType.Finish) + return BlockState.FinishStarted; + else + return BlockState.NeedMore; + } + return flush == FlushType.Finish ? BlockState.FinishDone : BlockState.BlockDone; + } + + // Same as above, but achieves better compression. We use a lazy + // evaluation for matches: a match is finally adopted only if there is + // no better match at the next window position. + internal BlockState DeflateSlow(FlushType flush) + { + // short hash_head = 0; // head of hash chain + int hash_head = 0; // head of hash chain + bool bflush; // set if current block must be flushed + + // Process the input block. + while (true) + { + // Make sure that we always have enough lookahead, except + // at the end of the input file. We need MAX_MATCH bytes + // for the next match, plus MIN_MATCH bytes to insert the + // string following the next match. + + if (lookahead < MIN_LOOKAHEAD) + { + _fillWindow(); + if (lookahead < MIN_LOOKAHEAD && flush == FlushType.None) + return BlockState.NeedMore; + + if (lookahead == 0) + break; // flush the current block + } + + // Insert the string window[strstart .. strstart+2] in the + // dictionary, and set hash_head to the head of the hash chain: + + if (lookahead >= MIN_MATCH) + { + ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask; + // prev[strstart&w_mask]=hash_head=head[ins_h]; + hash_head = (head[ins_h] & 0xffff); + prev[strstart & w_mask] = head[ins_h]; + head[ins_h] = unchecked((short)strstart); + } + + // Find the longest match, discarding those <= prev_length. + prev_length = match_length; + prev_match = match_start; + match_length = MIN_MATCH - 1; + + if (hash_head != 0 && prev_length < config.MaxLazy && + ((strstart - hash_head) & 0xffff) <= w_size - MIN_LOOKAHEAD) + { + // To simplify the code, we prevent matches with the string + // of window index 0 (in particular we have to avoid a match + // of the string with itself at the start of the input file). + + if (compressionStrategy != CompressionStrategy.HuffmanOnly) + { + match_length = longest_match(hash_head); + } + // longest_match() sets match_start + + if (match_length <= 5 && (compressionStrategy == CompressionStrategy.Filtered || + (match_length == MIN_MATCH && strstart - match_start > 4096))) + { + + // If prev_match is also MIN_MATCH, match_start is garbage + // but we will ignore the current match anyway. + match_length = MIN_MATCH - 1; + } + } + + // If there was a match at the previous step and the current + // match is not better, output the previous match: + if (prev_length >= MIN_MATCH && match_length <= prev_length) + { + int max_insert = strstart + lookahead - MIN_MATCH; + // Do not insert strings in hash table beyond this. + + // check_match(strstart-1, prev_match, prev_length); + + bflush = _tr_tally(strstart - 1 - prev_match, prev_length - MIN_MATCH); + + // Insert in hash table all strings up to the end of the match. + // strstart-1 and strstart are already inserted. If there is not + // enough lookahead, the last two strings are not inserted in + // the hash table. + lookahead -= (prev_length - 1); + prev_length -= 2; + do + { + if (++strstart <= max_insert) + { + ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask; + //prev[strstart&w_mask]=hash_head=head[ins_h]; + hash_head = (head[ins_h] & 0xffff); + prev[strstart & w_mask] = head[ins_h]; + head[ins_h] = unchecked((short)strstart); + } + } + while (--prev_length != 0); + match_available = 0; + match_length = MIN_MATCH - 1; + strstart++; + + if (bflush) + { + flush_block_only(false); + if (_codec.AvailableBytesOut == 0) + return BlockState.NeedMore; + } + } + else if (match_available != 0) + { + + // If there was no match at the previous position, output a + // single literal. If there was a match but the current match + // is longer, truncate the previous match to a single literal. + + bflush = _tr_tally(0, window[strstart - 1] & 0xff); + + if (bflush) + { + flush_block_only(false); + } + strstart++; + lookahead--; + if (_codec.AvailableBytesOut == 0) + return BlockState.NeedMore; + } + else + { + // There is no previous match to compare with, wait for + // the next step to decide. + + match_available = 1; + strstart++; + lookahead--; + } + } + + if (match_available != 0) + { + bflush = _tr_tally(0, window[strstart - 1] & 0xff); + match_available = 0; + } + flush_block_only(flush == FlushType.Finish); + + if (_codec.AvailableBytesOut == 0) + { + if (flush == FlushType.Finish) + return BlockState.FinishStarted; + else + return BlockState.NeedMore; + } + + return flush == FlushType.Finish ? BlockState.FinishDone : BlockState.BlockDone; + } + + + internal int longest_match(int cur_match) + { + int chain_length = config.MaxChainLength; // max hash chain length + int scan = strstart; // current string + int match; // matched string + int len; // length of current match + int best_len = prev_length; // best match length so far + int limit = strstart > (w_size - MIN_LOOKAHEAD) ? strstart - (w_size - MIN_LOOKAHEAD) : 0; + + int niceLength = config.NiceLength; + + // Stop when cur_match becomes <= limit. To simplify the code, + // we prevent matches with the string of window index 0. + + int wmask = w_mask; + + int strend = strstart + MAX_MATCH; + byte scan_end1 = window[scan + best_len - 1]; + byte scan_end = window[scan + best_len]; + + // The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + // It is easy to get rid of this optimization if necessary. + + // Do not waste too much time if we already have a good match: + if (prev_length >= config.GoodLength) + { + chain_length >>= 2; + } + + // Do not look for matches beyond the end of the input. This is necessary + // to make deflate deterministic. + if (niceLength > lookahead) + niceLength = lookahead; + + do + { + match = cur_match; + + // Skip to next match if the match length cannot increase + // or if the match length is less than 2: + if (window[match + best_len] != scan_end || + window[match + best_len - 1] != scan_end1 || + window[match] != window[scan] || + window[++match] != window[scan + 1]) + continue; + + // The check at best_len-1 can be removed because it will be made + // again later. (This heuristic is not always a win.) + // It is not necessary to compare scan[2] and match[2] since they + // are always equal when the other bytes match, given that + // the hash keys are equal and that HASH_BITS >= 8. + scan += 2; match++; + + // We check for insufficient lookahead only every 8th comparison; + // the 256th check will be made at strstart+258. + do + { + } + while (window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && scan < strend); + + len = MAX_MATCH - (int)(strend - scan); + scan = strend - MAX_MATCH; + + if (len > best_len) + { + match_start = cur_match; + best_len = len; + if (len >= niceLength) + break; + scan_end1 = window[scan + best_len - 1]; + scan_end = window[scan + best_len]; + } + } + while ((cur_match = (prev[cur_match & wmask] & 0xffff)) > limit && --chain_length != 0); + + if (best_len <= lookahead) + return best_len; + return lookahead; + } + + + private bool Rfc1950BytesEmitted = false; + private bool _WantRfc1950HeaderBytes = true; + internal bool WantRfc1950HeaderBytes + { + get { return _WantRfc1950HeaderBytes; } + set { _WantRfc1950HeaderBytes = value; } + } + + + internal int Initialize(ZlibCodec codec, CompressionLevel level) + { + return Initialize(codec, level, ZlibConstants.WindowBitsMax); + } + + internal int Initialize(ZlibCodec codec, CompressionLevel level, int bits) + { + return Initialize(codec, level, bits, MEM_LEVEL_DEFAULT, CompressionStrategy.Default); + } + + internal int Initialize(ZlibCodec codec, CompressionLevel level, int bits, CompressionStrategy compressionStrategy) + { + return Initialize(codec, level, bits, MEM_LEVEL_DEFAULT, compressionStrategy); + } + + internal int Initialize(ZlibCodec codec, CompressionLevel level, int windowBits, int memLevel, CompressionStrategy strategy) + { + _codec = codec; + _codec.Message = null; + + // validation + if (windowBits < 9 || windowBits > 15) + throw new ZlibException("windowBits must be in the range 9..15."); + + if (memLevel < 1 || memLevel > MEM_LEVEL_MAX) + throw new ZlibException(String.Format("memLevel must be in the range 1.. {0}", MEM_LEVEL_MAX)); + + _codec.dstate = this; + + w_bits = windowBits; + w_size = 1 << w_bits; + w_mask = w_size - 1; + + hash_bits = memLevel + 7; + hash_size = 1 << hash_bits; + hash_mask = hash_size - 1; + hash_shift = ((hash_bits + MIN_MATCH - 1) / MIN_MATCH); + + window = new byte[w_size * 2]; + prev = new short[w_size]; + head = new short[hash_size]; + + // for memLevel==8, this will be 16384, 16k + lit_bufsize = 1 << (memLevel + 6); + + // Use a single array as the buffer for data pending compression, + // the output distance codes, and the output length codes (aka tree). + // orig comment: This works just fine since the average + // output size for (length,distance) codes is <= 24 bits. + pending = new byte[lit_bufsize * 4]; + _distanceOffset = lit_bufsize; + _lengthOffset = (1 + 2) * lit_bufsize; + + // So, for memLevel 8, the length of the pending buffer is 65536. 64k. + // The first 16k are pending bytes. + // The middle slice, of 32k, is used for distance codes. + // The final 16k are length codes. + + this.compressionLevel = level; + this.compressionStrategy = strategy; + + Reset(); + return ZlibConstants.Z_OK; + } + + + internal void Reset() + { + _codec.TotalBytesIn = _codec.TotalBytesOut = 0; + _codec.Message = null; + //strm.data_type = Z_UNKNOWN; + + pendingCount = 0; + nextPending = 0; + + Rfc1950BytesEmitted = false; + + status = (WantRfc1950HeaderBytes) ? INIT_STATE : BUSY_STATE; + _codec._Adler32 = Adler.Adler32(0, null, 0, 0); + + last_flush = (int)FlushType.None; + + _InitializeTreeData(); + _InitializeLazyMatch(); + } + + + internal int End() + { + if (status != INIT_STATE && status != BUSY_STATE && status != FINISH_STATE) + { + return ZlibConstants.Z_STREAM_ERROR; + } + // Deallocate in reverse order of allocations: + pending = null; + head = null; + prev = null; + window = null; + // free + // dstate=null; + return status == BUSY_STATE ? ZlibConstants.Z_DATA_ERROR : ZlibConstants.Z_OK; + } + + + private void SetDeflater() + { + switch (config.Flavor) + { + case DeflateFlavor.Store: + DeflateFunction = DeflateNone; + break; + case DeflateFlavor.Fast: + DeflateFunction = DeflateFast; + break; + case DeflateFlavor.Slow: + DeflateFunction = DeflateSlow; + break; + } + } + + + internal int SetParams(CompressionLevel level, CompressionStrategy strategy) + { + int result = ZlibConstants.Z_OK; + + if (compressionLevel != level) + { + Config newConfig = Config.Lookup(level); + + // change in the deflate flavor (Fast vs slow vs none)? + if (newConfig.Flavor != config.Flavor && _codec.TotalBytesIn != 0) + { + // Flush the last buffer: + result = _codec.Deflate(FlushType.Partial); + } + + compressionLevel = level; + config = newConfig; + SetDeflater(); + } + + // no need to flush with change in strategy? Really? + compressionStrategy = strategy; + + return result; + } + + + internal int SetDictionary(byte[] dictionary) + { + int length = dictionary.Length; + int index = 0; + + if (dictionary == null || status != INIT_STATE) + throw new ZlibException("Stream error."); + + _codec._Adler32 = Adler.Adler32(_codec._Adler32, dictionary, 0, dictionary.Length); + + if (length < MIN_MATCH) + return ZlibConstants.Z_OK; + if (length > w_size - MIN_LOOKAHEAD) + { + length = w_size - MIN_LOOKAHEAD; + index = dictionary.Length - length; // use the tail of the dictionary + } + Array.Copy(dictionary, index, window, 0, length); + strstart = length; + block_start = length; + + // Insert all strings in the hash table (except for the last two bytes). + // s->lookahead stays null, so s->ins_h will be recomputed at the next + // call of fill_window. + + ins_h = window[0] & 0xff; + ins_h = (((ins_h) << hash_shift) ^ (window[1] & 0xff)) & hash_mask; + + for (int n = 0; n <= length - MIN_MATCH; n++) + { + ins_h = (((ins_h) << hash_shift) ^ (window[(n) + (MIN_MATCH - 1)] & 0xff)) & hash_mask; + prev[n & w_mask] = head[ins_h]; + head[ins_h] = (short)n; + } + return ZlibConstants.Z_OK; + } + + + + internal int Deflate(FlushType flush) + { + int old_flush; + + if (_codec.OutputBuffer == null || + (_codec.InputBuffer == null && _codec.AvailableBytesIn != 0) || + (status == FINISH_STATE && flush != FlushType.Finish)) + { + _codec.Message = _ErrorMessage[ZlibConstants.Z_NEED_DICT - (ZlibConstants.Z_STREAM_ERROR)]; + throw new ZlibException(String.Format("Something is fishy. [{0}]", _codec.Message)); + } + if (_codec.AvailableBytesOut == 0) + { + _codec.Message = _ErrorMessage[ZlibConstants.Z_NEED_DICT - (ZlibConstants.Z_BUF_ERROR)]; + throw new ZlibException("OutputBuffer is full (AvailableBytesOut == 0)"); + } + + old_flush = last_flush; + last_flush = (int)flush; + + // Write the zlib (rfc1950) header bytes + if (status == INIT_STATE) + { + int header = (Z_DEFLATED + ((w_bits - 8) << 4)) << 8; + int level_flags = (((int)compressionLevel - 1) & 0xff) >> 1; + + if (level_flags > 3) + level_flags = 3; + header |= (level_flags << 6); + if (strstart != 0) + header |= PRESET_DICT; + header += 31 - (header % 31); + + status = BUSY_STATE; + //putShortMSB(header); + unchecked + { + pending[pendingCount++] = (byte)(header >> 8); + pending[pendingCount++] = (byte)header; + } + // Save the adler32 of the preset dictionary: + if (strstart != 0) + { + pending[pendingCount++] = (byte)((_codec._Adler32 & 0xFF000000) >> 24); + pending[pendingCount++] = (byte)((_codec._Adler32 & 0x00FF0000) >> 16); + pending[pendingCount++] = (byte)((_codec._Adler32 & 0x0000FF00) >> 8); + pending[pendingCount++] = (byte)(_codec._Adler32 & 0x000000FF); + } + _codec._Adler32 = Adler.Adler32(0, null, 0, 0); + } + + // Flush as much pending output as possible + if (pendingCount != 0) + { + _codec.flush_pending(); + if (_codec.AvailableBytesOut == 0) + { + //System.out.println(" avail_out==0"); + // Since avail_out is 0, deflate will be called again with + // more output space, but possibly with both pending and + // avail_in equal to zero. There won't be anything to do, + // but this is not an error situation so make sure we + // return OK instead of BUF_ERROR at next call of deflate: + last_flush = -1; + return ZlibConstants.Z_OK; + } + + // Make sure there is something to do and avoid duplicate consecutive + // flushes. For repeated and useless calls with Z_FINISH, we keep + // returning Z_STREAM_END instead of Z_BUFF_ERROR. + } + else if (_codec.AvailableBytesIn == 0 && + (int)flush <= old_flush && + flush != FlushType.Finish) + { + // workitem 8557 + // + // Not sure why this needs to be an error. pendingCount == 0, which + // means there's nothing to deflate. And the caller has not asked + // for a FlushType.Finish, but... that seems very non-fatal. We + // can just say "OK" and do nothing. + + // _codec.Message = z_errmsg[ZlibConstants.Z_NEED_DICT - (ZlibConstants.Z_BUF_ERROR)]; + // throw new ZlibException("AvailableBytesIn == 0 && flush<=old_flush && flush != FlushType.Finish"); + + return ZlibConstants.Z_OK; + } + + // User must not provide more input after the first FINISH: + if (status == FINISH_STATE && _codec.AvailableBytesIn != 0) + { + _codec.Message = _ErrorMessage[ZlibConstants.Z_NEED_DICT - (ZlibConstants.Z_BUF_ERROR)]; + throw new ZlibException("status == FINISH_STATE && _codec.AvailableBytesIn != 0"); + } + + // Start a new block or continue the current one. + if (_codec.AvailableBytesIn != 0 || lookahead != 0 || (flush != FlushType.None && status != FINISH_STATE)) + { + BlockState bstate = DeflateFunction(flush); + + if (bstate == BlockState.FinishStarted || bstate == BlockState.FinishDone) + { + status = FINISH_STATE; + } + if (bstate == BlockState.NeedMore || bstate == BlockState.FinishStarted) + { + if (_codec.AvailableBytesOut == 0) + { + last_flush = -1; // avoid BUF_ERROR next call, see above + } + return ZlibConstants.Z_OK; + // If flush != Z_NO_FLUSH && avail_out == 0, the next call + // of deflate should use the same flush parameter to make sure + // that the flush is complete. So we don't have to output an + // empty block here, this will be done at next call. This also + // ensures that for a very small output buffer, we emit at most + // one empty block. + } + + if (bstate == BlockState.BlockDone) + { + if (flush == FlushType.Partial) + { + _tr_align(); + } + else + { + // FlushType.Full or FlushType.Sync + _tr_stored_block(0, 0, false); + // For a full flush, this empty block will be recognized + // as a special marker by inflate_sync(). + if (flush == FlushType.Full) + { + // clear hash (forget the history) + for (int i = 0; i < hash_size; i++) + head[i] = 0; + } + } + _codec.flush_pending(); + if (_codec.AvailableBytesOut == 0) + { + last_flush = -1; // avoid BUF_ERROR at next call, see above + return ZlibConstants.Z_OK; + } + } + } + + if (flush != FlushType.Finish) + return ZlibConstants.Z_OK; + + if (!WantRfc1950HeaderBytes || Rfc1950BytesEmitted) + return ZlibConstants.Z_STREAM_END; + + // Write the zlib trailer (adler32) + pending[pendingCount++] = (byte)((_codec._Adler32 & 0xFF000000) >> 24); + pending[pendingCount++] = (byte)((_codec._Adler32 & 0x00FF0000) >> 16); + pending[pendingCount++] = (byte)((_codec._Adler32 & 0x0000FF00) >> 8); + pending[pendingCount++] = (byte)(_codec._Adler32 & 0x000000FF); + //putShortMSB((int)(SharedUtils.URShift(_codec._Adler32, 16))); + //putShortMSB((int)(_codec._Adler32 & 0xffff)); + + _codec.flush_pending(); + + // If avail_out is zero, the application will call deflate again + // to flush the rest. + + Rfc1950BytesEmitted = true; // write the trailer only once! + + return pendingCount != 0 ? ZlibConstants.Z_OK : ZlibConstants.Z_STREAM_END; + } + + } +} \ No newline at end of file diff --git a/src/ZlibAndroidSdkStyle/DeflateStream.cs b/src/ZlibAndroidSdkStyle/DeflateStream.cs new file mode 100644 index 00000000..05362d41 --- /dev/null +++ b/src/ZlibAndroidSdkStyle/DeflateStream.cs @@ -0,0 +1,740 @@ +// DeflateStream.cs +// ------------------------------------------------------------------ +// +// Copyright (c) 2009-2010 Dino Chiesa. +// All rights reserved. +// +// This code module is part of DotNetZip, a zipfile class library. +// +// ------------------------------------------------------------------ +// +// This code is licensed under the Microsoft Public License. +// See the file License.txt for the license details. +// More info on: http://dotnetzip.codeplex.com +// +// ------------------------------------------------------------------ +// +// last saved (in emacs): +// Time-stamp: <2011-July-31 14:48:11> +// +// ------------------------------------------------------------------ +// +// This module defines the DeflateStream class, which can be used as a replacement for +// the System.IO.Compression.DeflateStream class in the .NET BCL. +// +// ------------------------------------------------------------------ + + +using System; + +namespace Ionic.Zlib +{ + /// + /// A class for compressing and decompressing streams using the Deflate algorithm. + /// + /// + /// + /// + /// + /// The DeflateStream is a Decorator on a . It adds DEFLATE compression or decompression to any + /// stream. + /// + /// + /// + /// Using this stream, applications can compress or decompress data via stream + /// Read and Write operations. Either compresssion or decompression + /// can occur through either reading or writing. The compression format used is + /// DEFLATE, which is documented in IETF RFC 1951, "DEFLATE + /// Compressed Data Format Specification version 1.3.". + /// + /// + /// + /// This class is similar to , except that + /// ZlibStream adds the RFC + /// 1950 - ZLIB framing bytes to a compressed stream when compressing, or + /// expects the RFC1950 framing bytes when decompressing. The DeflateStream + /// does not. + /// + /// + /// + /// + /// + /// + public class DeflateStream : System.IO.Stream + { + internal ZlibBaseStream _baseStream; + internal System.IO.Stream _innerStream; + bool _disposed; + + /// + /// Create a DeflateStream using the specified CompressionMode. + /// + /// + /// + /// When mode is CompressionMode.Compress, the DeflateStream will use + /// the default compression level. The "captive" stream will be closed when + /// the DeflateStream is closed. + /// + /// + /// + /// This example uses a DeflateStream to compress data from a file, and writes + /// the compressed data to another file. + /// + /// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress)) + /// { + /// using (var raw = System.IO.File.Create(fileToCompress + ".deflated")) + /// { + /// using (Stream compressor = new DeflateStream(raw, CompressionMode.Compress)) + /// { + /// byte[] buffer = new byte[WORKING_BUFFER_SIZE]; + /// int n; + /// while ((n= input.Read(buffer, 0, buffer.Length)) != 0) + /// { + /// compressor.Write(buffer, 0, n); + /// } + /// } + /// } + /// } + /// + /// + /// + /// Using input As Stream = File.OpenRead(fileToCompress) + /// Using raw As FileStream = File.Create(fileToCompress & ".deflated") + /// Using compressor As Stream = New DeflateStream(raw, CompressionMode.Compress) + /// Dim buffer As Byte() = New Byte(4096) {} + /// Dim n As Integer = -1 + /// Do While (n <> 0) + /// If (n > 0) Then + /// compressor.Write(buffer, 0, n) + /// End If + /// n = input.Read(buffer, 0, buffer.Length) + /// Loop + /// End Using + /// End Using + /// End Using + /// + /// + /// The stream which will be read or written. + /// Indicates whether the DeflateStream will compress or decompress. + public DeflateStream(System.IO.Stream stream, CompressionMode mode) + : this(stream, mode, CompressionLevel.Default, false) + { + } + + /// + /// Create a DeflateStream using the specified CompressionMode and the specified CompressionLevel. + /// + /// + /// + /// + /// + /// When mode is CompressionMode.Decompress, the level parameter is + /// ignored. The "captive" stream will be closed when the DeflateStream is + /// closed. + /// + /// + /// + /// + /// + /// + /// This example uses a DeflateStream to compress data from a file, and writes + /// the compressed data to another file. + /// + /// + /// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress)) + /// { + /// using (var raw = System.IO.File.Create(fileToCompress + ".deflated")) + /// { + /// using (Stream compressor = new DeflateStream(raw, + /// CompressionMode.Compress, + /// CompressionLevel.BestCompression)) + /// { + /// byte[] buffer = new byte[WORKING_BUFFER_SIZE]; + /// int n= -1; + /// while (n != 0) + /// { + /// if (n > 0) + /// compressor.Write(buffer, 0, n); + /// n= input.Read(buffer, 0, buffer.Length); + /// } + /// } + /// } + /// } + /// + /// + /// + /// Using input As Stream = File.OpenRead(fileToCompress) + /// Using raw As FileStream = File.Create(fileToCompress & ".deflated") + /// Using compressor As Stream = New DeflateStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression) + /// Dim buffer As Byte() = New Byte(4096) {} + /// Dim n As Integer = -1 + /// Do While (n <> 0) + /// If (n > 0) Then + /// compressor.Write(buffer, 0, n) + /// End If + /// n = input.Read(buffer, 0, buffer.Length) + /// Loop + /// End Using + /// End Using + /// End Using + /// + /// + /// The stream to be read or written while deflating or inflating. + /// Indicates whether the DeflateStream will compress or decompress. + /// A tuning knob to trade speed for effectiveness. + public DeflateStream(System.IO.Stream stream, CompressionMode mode, CompressionLevel level) + : this(stream, mode, level, false) + { + } + + /// + /// Create a DeflateStream using the specified + /// CompressionMode, and explicitly specify whether the + /// stream should be left open after Deflation or Inflation. + /// + /// + /// + /// + /// + /// This constructor allows the application to request that the captive stream + /// remain open after the deflation or inflation occurs. By default, after + /// Close() is called on the stream, the captive stream is also + /// closed. In some cases this is not desired, for example if the stream is a + /// memory stream that will be re-read after compression. Specify true for + /// the parameter to leave the stream open. + /// + /// + /// + /// The DeflateStream will use the default compression level. + /// + /// + /// + /// See the other overloads of this constructor for example code. + /// + /// + /// + /// + /// The stream which will be read or written. This is called the + /// "captive" stream in other places in this documentation. + /// + /// + /// + /// Indicates whether the DeflateStream will compress or decompress. + /// + /// + /// true if the application would like the stream to + /// remain open after inflation/deflation. + public DeflateStream(System.IO.Stream stream, CompressionMode mode, bool leaveOpen) + : this(stream, mode, CompressionLevel.Default, leaveOpen) + { + } + + /// + /// Create a DeflateStream using the specified CompressionMode + /// and the specified CompressionLevel, and explicitly specify whether + /// the stream should be left open after Deflation or Inflation. + /// + /// + /// + /// + /// + /// When mode is CompressionMode.Decompress, the level parameter is ignored. + /// + /// + /// + /// This constructor allows the application to request that the captive stream + /// remain open after the deflation or inflation occurs. By default, after + /// Close() is called on the stream, the captive stream is also + /// closed. In some cases this is not desired, for example if the stream is a + /// that will be re-read after + /// compression. Specify true for the parameter + /// to leave the stream open. + /// + /// + /// + /// + /// + /// + /// This example shows how to use a DeflateStream to compress data from + /// a file, and store the compressed data into another file. + /// + /// + /// using (var output = System.IO.File.Create(fileToCompress + ".deflated")) + /// { + /// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress)) + /// { + /// using (Stream compressor = new DeflateStream(output, CompressionMode.Compress, CompressionLevel.BestCompression, true)) + /// { + /// byte[] buffer = new byte[WORKING_BUFFER_SIZE]; + /// int n= -1; + /// while (n != 0) + /// { + /// if (n > 0) + /// compressor.Write(buffer, 0, n); + /// n= input.Read(buffer, 0, buffer.Length); + /// } + /// } + /// } + /// // can write additional data to the output stream here + /// } + /// + /// + /// + /// Using output As FileStream = File.Create(fileToCompress & ".deflated") + /// Using input As Stream = File.OpenRead(fileToCompress) + /// Using compressor As Stream = New DeflateStream(output, CompressionMode.Compress, CompressionLevel.BestCompression, True) + /// Dim buffer As Byte() = New Byte(4096) {} + /// Dim n As Integer = -1 + /// Do While (n <> 0) + /// If (n > 0) Then + /// compressor.Write(buffer, 0, n) + /// End If + /// n = input.Read(buffer, 0, buffer.Length) + /// Loop + /// End Using + /// End Using + /// ' can write additional data to the output stream here. + /// End Using + /// + /// + /// The stream which will be read or written. + /// Indicates whether the DeflateStream will compress or decompress. + /// true if the application would like the stream to remain open after inflation/deflation. + /// A tuning knob to trade speed for effectiveness. + public DeflateStream(System.IO.Stream stream, CompressionMode mode, CompressionLevel level, bool leaveOpen) + { + _innerStream = stream; + _baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.DEFLATE, leaveOpen); + } + + #region Zlib properties + + /// + /// This property sets the flush behavior on the stream. + /// + /// See the ZLIB documentation for the meaning of the flush behavior. + /// + virtual public FlushType FlushMode + { + get { return (this._baseStream._flushMode); } + set + { + if (_disposed) throw new ObjectDisposedException("DeflateStream"); + this._baseStream._flushMode = value; + } + } + + /// + /// The size of the working buffer for the compression codec. + /// + /// + /// + /// + /// The working buffer is used for all stream operations. The default size is + /// 1024 bytes. The minimum size is 128 bytes. You may get better performance + /// with a larger buffer. Then again, you might not. You would have to test + /// it. + /// + /// + /// + /// Set this before the first call to Read() or Write() on the + /// stream. If you try to set it afterwards, it will throw. + /// + /// + public int BufferSize + { + get + { + return this._baseStream._bufferSize; + } + set + { + if (_disposed) throw new ObjectDisposedException("DeflateStream"); + if (this._baseStream._workingBuffer != null) + throw new ZlibException("The working buffer is already set."); + if (value < ZlibConstants.WorkingBufferSizeMin) + throw new ZlibException(String.Format("Don't be silly. {0} bytes?? Use a bigger buffer, at least {1}.", value, ZlibConstants.WorkingBufferSizeMin)); + this._baseStream._bufferSize = value; + } + } + + /// + /// The ZLIB strategy to be used during compression. + /// + /// + /// + /// By tweaking this parameter, you may be able to optimize the compression for + /// data with particular characteristics. + /// + public CompressionStrategy Strategy + { + get + { + return this._baseStream.Strategy; + } + set + { + if (_disposed) throw new ObjectDisposedException("DeflateStream"); + this._baseStream.Strategy = value; + } + } + + /// Returns the total number of bytes input so far. + virtual public long TotalIn + { + get + { + return this._baseStream._z.TotalBytesIn; + } + } + + /// Returns the total number of bytes output so far. + virtual public long TotalOut + { + get + { + return this._baseStream._z.TotalBytesOut; + } + } + + #endregion + + #region System.IO.Stream methods + /// + /// Dispose the stream. + /// + /// + /// + /// This may or may not result in a Close() call on the captive + /// stream. See the constructors that have a leaveOpen parameter + /// for more information. + /// + /// + /// Application code won't call this code directly. This method may be + /// invoked in two distinct scenarios. If disposing == true, the method + /// has been called directly or indirectly by a user's code, for example + /// via the public Dispose() method. In this case, both managed and + /// unmanaged resources can be referenced and disposed. If disposing == + /// false, the method has been called by the runtime from inside the + /// object finalizer and this method should not reference other objects; + /// in that case only unmanaged resources must be referenced or + /// disposed. + /// + /// + /// + /// true if the Dispose method was invoked by user code. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!_disposed) + { + if (disposing && (this._baseStream != null)) + this._baseStream.Close(); + _disposed = true; + } + } + finally + { + base.Dispose(disposing); + } + } + + + + /// + /// Indicates whether the stream can be read. + /// + /// + /// The return value depends on whether the captive stream supports reading. + /// + public override bool CanRead + { + get + { + if (_disposed) throw new ObjectDisposedException("DeflateStream"); + return _baseStream._stream.CanRead; + } + } + + /// + /// Indicates whether the stream supports Seek operations. + /// + /// + /// Always returns false. + /// + public override bool CanSeek + { + get { return false; } + } + + + /// + /// Indicates whether the stream can be written. + /// + /// + /// The return value depends on whether the captive stream supports writing. + /// + public override bool CanWrite + { + get + { + if (_disposed) throw new ObjectDisposedException("DeflateStream"); + return _baseStream._stream.CanWrite; + } + } + + /// + /// Flush the stream. + /// + public override void Flush() + { + if (_disposed) throw new ObjectDisposedException("DeflateStream"); + _baseStream.Flush(); + } + + /// + /// Reading this property always throws a . + /// + public override long Length + { + get { throw new NotImplementedException(); } + } + + /// + /// The position of the stream pointer. + /// + /// + /// + /// Setting this property always throws a . Reading will return the total bytes + /// written out, if used in writing, or the total bytes read in, if used in + /// reading. The count may refer to compressed bytes or uncompressed bytes, + /// depending on how you've used the stream. + /// + public override long Position + { + get + { + if (this._baseStream._streamMode == Ionic.Zlib.ZlibBaseStream.StreamMode.Writer) + return this._baseStream._z.TotalBytesOut; + if (this._baseStream._streamMode == Ionic.Zlib.ZlibBaseStream.StreamMode.Reader) + return this._baseStream._z.TotalBytesIn; + return 0; + } + set { throw new NotImplementedException(); } + } + + /// + /// Read data from the stream. + /// + /// + /// + /// + /// If you wish to use the DeflateStream to compress data while + /// reading, you can create a DeflateStream with + /// CompressionMode.Compress, providing an uncompressed data stream. + /// Then call Read() on that DeflateStream, and the data read will be + /// compressed as you read. If you wish to use the DeflateStream to + /// decompress data while reading, you can create a DeflateStream with + /// CompressionMode.Decompress, providing a readable compressed data + /// stream. Then call Read() on that DeflateStream, and the data read + /// will be decompressed as you read. + /// + /// + /// + /// A DeflateStream can be used for Read() or Write(), but not both. + /// + /// + /// + /// The buffer into which the read data should be placed. + /// the offset within that data array to put the first byte read. + /// the number of bytes to read. + /// the number of bytes actually read + public override int Read(byte[] buffer, int offset, int count) + { + if (_disposed) throw new ObjectDisposedException("DeflateStream"); + return _baseStream.Read(buffer, offset, count); + } + + + /// + /// Calling this method always throws a . + /// + /// this is irrelevant, since it will always throw! + /// this is irrelevant, since it will always throw! + /// irrelevant! + public override long Seek(long offset, System.IO.SeekOrigin origin) + { + throw new NotImplementedException(); + } + + /// + /// Calling this method always throws a . + /// + /// this is irrelevant, since it will always throw! + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + /// + /// Write data to the stream. + /// + /// + /// + /// + /// If you wish to use the DeflateStream to compress data while + /// writing, you can create a DeflateStream with + /// CompressionMode.Compress, and a writable output stream. Then call + /// Write() on that DeflateStream, providing uncompressed data + /// as input. The data sent to the output stream will be the compressed form + /// of the data written. If you wish to use the DeflateStream to + /// decompress data while writing, you can create a DeflateStream with + /// CompressionMode.Decompress, and a writable output stream. Then + /// call Write() on that stream, providing previously compressed + /// data. The data sent to the output stream will be the decompressed form of + /// the data written. + /// + /// + /// + /// A DeflateStream can be used for Read() or Write(), + /// but not both. + /// + /// + /// + /// + /// The buffer holding data to write to the stream. + /// the offset within that data array to find the first byte to write. + /// the number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + if (_disposed) throw new ObjectDisposedException("DeflateStream"); + _baseStream.Write(buffer, offset, count); + } + #endregion + + + + + /// + /// Compress a string into a byte array using DEFLATE (RFC 1951). + /// + /// + /// + /// Uncompress it with . + /// + /// + /// DeflateStream.UncompressString(byte[]) + /// DeflateStream.CompressBuffer(byte[]) + /// GZipStream.CompressString(string) + /// ZlibStream.CompressString(string) + /// + /// + /// A string to compress. The string will first be encoded + /// using UTF8, then compressed. + /// + /// + /// The string in compressed form + public static byte[] CompressString(String s) + { + using (var ms = new System.IO.MemoryStream()) + { + System.IO.Stream compressor = + new DeflateStream(ms, CompressionMode.Compress, CompressionLevel.BestCompression); + ZlibBaseStream.CompressString(s, compressor); + return ms.ToArray(); + } + } + + + /// + /// Compress a byte array into a new byte array using DEFLATE. + /// + /// + /// + /// Uncompress it with . + /// + /// + /// DeflateStream.CompressString(string) + /// DeflateStream.UncompressBuffer(byte[]) + /// GZipStream.CompressBuffer(byte[]) + /// ZlibStream.CompressBuffer(byte[]) + /// + /// + /// A buffer to compress. + /// + /// + /// The data in compressed form + public static byte[] CompressBuffer(byte[] b) + { + using (var ms = new System.IO.MemoryStream()) + { + System.IO.Stream compressor = + new DeflateStream( ms, CompressionMode.Compress, CompressionLevel.BestCompression ); + + ZlibBaseStream.CompressBuffer(b, compressor); + return ms.ToArray(); + } + } + + + /// + /// Uncompress a DEFLATE'd byte array into a single string. + /// + /// + /// DeflateStream.CompressString(String) + /// DeflateStream.UncompressBuffer(byte[]) + /// GZipStream.UncompressString(byte[]) + /// ZlibStream.UncompressString(byte[]) + /// + /// + /// A buffer containing DEFLATE-compressed data. + /// + /// + /// The uncompressed string + public static String UncompressString(byte[] compressed) + { + using (var input = new System.IO.MemoryStream(compressed)) + { + System.IO.Stream decompressor = + new DeflateStream(input, CompressionMode.Decompress); + + return ZlibBaseStream.UncompressString(compressed, decompressor); + } + } + + + /// + /// Uncompress a DEFLATE'd byte array into a byte array. + /// + /// + /// DeflateStream.CompressBuffer(byte[]) + /// DeflateStream.UncompressString(byte[]) + /// GZipStream.UncompressBuffer(byte[]) + /// ZlibStream.UncompressBuffer(byte[]) + /// + /// + /// A buffer containing data that has been compressed with DEFLATE. + /// + /// + /// The data in uncompressed form + public static byte[] UncompressBuffer(byte[] compressed) + { + using (var input = new System.IO.MemoryStream(compressed)) + { + System.IO.Stream decompressor = + new DeflateStream( input, CompressionMode.Decompress ); + + return ZlibBaseStream.UncompressBuffer(compressed, decompressor); + } + } + + } + +} + diff --git a/src/ZlibAndroidSdkStyle/GZipStream.cs b/src/ZlibAndroidSdkStyle/GZipStream.cs new file mode 100644 index 00000000..745e0963 --- /dev/null +++ b/src/ZlibAndroidSdkStyle/GZipStream.cs @@ -0,0 +1,1033 @@ +// GZipStream.cs +// ------------------------------------------------------------------ +// +// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. +// All rights reserved. +// +// This code module is part of DotNetZip, a zipfile class library. +// +// ------------------------------------------------------------------ +// +// This code is licensed under the Microsoft Public License. +// See the file License.txt for the license details. +// More info on: http://dotnetzip.codeplex.com +// +// ------------------------------------------------------------------ +// +// last saved (in emacs): +// Time-stamp: <2011-August-08 18:14:39> +// +// ------------------------------------------------------------------ +// +// This module defines the GZipStream class, which can be used as a replacement for +// the System.IO.Compression.GZipStream class in the .NET BCL. NB: The design is not +// completely OO clean: there is some intelligence in the ZlibBaseStream that reads the +// GZip header. +// +// ------------------------------------------------------------------ + + +using System; +using System.IO; + +namespace Ionic.Zlib +{ + /// + /// A class for compressing and decompressing GZIP streams. + /// + /// + /// + /// + /// The GZipStream is a Decorator on a + /// . It adds GZIP compression or decompression to any + /// stream. + /// + /// + /// + /// Like the System.IO.Compression.GZipStream in the .NET Base Class Library, the + /// Ionic.Zlib.GZipStream can compress while writing, or decompress while + /// reading, but not vice versa. The compression method used is GZIP, which is + /// documented in IETF RFC + /// 1952, "GZIP file format specification version 4.3". + /// + /// + /// A GZipStream can be used to decompress data (through Read()) or + /// to compress data (through Write()), but not both. + /// + /// + /// + /// If you wish to use the GZipStream to compress data, you must wrap it + /// around a write-able stream. As you call Write() on the GZipStream, the + /// data will be compressed into the GZIP format. If you want to decompress data, + /// you must wrap the GZipStream around a readable stream that contains an + /// IETF RFC 1952-compliant stream. The data will be decompressed as you call + /// Read() on the GZipStream. + /// + /// + /// + /// Though the GZIP format allows data from multiple files to be concatenated + /// together, this stream handles only a single segment of GZIP format, typically + /// representing a single file. + /// + /// + /// + /// This class is similar to and . + /// ZlibStream handles RFC1950-compliant streams. + /// handles RFC1951-compliant streams. This class handles RFC1952-compliant streams. + /// + /// + /// + /// + /// + /// + public class GZipStream : System.IO.Stream + { + // GZip format + // source: http://tools.ietf.org/html/rfc1952 + // + // header id: 2 bytes 1F 8B + // compress method 1 byte 8= DEFLATE (none other supported) + // flag 1 byte bitfield (See below) + // mtime 4 bytes time_t (seconds since jan 1, 1970 UTC of the file. + // xflg 1 byte 2 = max compress used , 4 = max speed (can be ignored) + // OS 1 byte OS for originating archive. set to 0xFF in compression. + // extra field length 2 bytes optional - only if FEXTRA is set. + // extra field varies + // filename varies optional - if FNAME is set. zero terminated. ISO-8859-1. + // file comment varies optional - if FCOMMENT is set. zero terminated. ISO-8859-1. + // crc16 1 byte optional - present only if FHCRC bit is set + // compressed data varies + // CRC32 4 bytes + // isize 4 bytes data size modulo 2^32 + // + // FLG (FLaGs) + // bit 0 FTEXT - indicates file is ASCII text (can be safely ignored) + // bit 1 FHCRC - there is a CRC16 for the header immediately following the header + // bit 2 FEXTRA - extra fields are present + // bit 3 FNAME - the zero-terminated filename is present. encoding; ISO-8859-1. + // bit 4 FCOMMENT - a zero-terminated file comment is present. encoding: ISO-8859-1 + // bit 5 reserved + // bit 6 reserved + // bit 7 reserved + // + // On consumption: + // Extra field is a bunch of nonsense and can be safely ignored. + // Header CRC and OS, likewise. + // + // on generation: + // all optional fields get 0, except for the OS, which gets 255. + // + + + + /// + /// The comment on the GZIP stream. + /// + /// + /// + /// + /// The GZIP format allows for each file to optionally have an associated + /// comment stored with the file. The comment is encoded with the ISO-8859-1 + /// code page. To include a comment in a GZIP stream you create, set this + /// property before calling Write() for the first time on the + /// GZipStream. + /// + /// + /// + /// When using GZipStream to decompress, you can retrieve this property + /// after the first call to Read(). If no comment has been set in the + /// GZIP bytestream, the Comment property will return null + /// (Nothing in VB). + /// + /// + public String Comment + { + get + { + return _Comment; + } + set + { + if (_disposed) throw new ObjectDisposedException("GZipStream"); + _Comment = value; + } + } + + /// + /// The FileName for the GZIP stream. + /// + /// + /// + /// + /// + /// The GZIP format optionally allows each file to have an associated + /// filename. When compressing data (through Write()), set this + /// FileName before calling Write() the first time on the GZipStream. + /// The actual filename is encoded into the GZIP bytestream with the + /// ISO-8859-1 code page, according to RFC 1952. It is the application's + /// responsibility to insure that the FileName can be encoded and decoded + /// correctly with this code page. + /// + /// + /// + /// When decompressing (through Read()), you can retrieve this value + /// any time after the first Read(). In the case where there was no filename + /// encoded into the GZIP bytestream, the property will return null (Nothing + /// in VB). + /// + /// + public String FileName + { + get { return _FileName; } + set + { + if (_disposed) throw new ObjectDisposedException("GZipStream"); + _FileName = value; + if (_FileName == null) return; + if (_FileName.IndexOf("/") != -1) + { + _FileName = _FileName.Replace("/", "\\"); + } + if (_FileName.EndsWith("\\")) + throw new Exception("Illegal filename"); + if (_FileName.IndexOf("\\") != -1) + { + // trim any leading path + _FileName = Path.GetFileName(_FileName); + } + } + } + + /// + /// The last modified time for the GZIP stream. + /// + /// + /// + /// GZIP allows the storage of a last modified time with each GZIP entry. + /// When compressing data, you can set this before the first call to + /// Write(). When decompressing, you can retrieve this value any time + /// after the first call to Read(). + /// + public DateTime? LastModified; + + /// + /// The CRC on the GZIP stream. + /// + /// + /// This is used for internal error checking. You probably don't need to look at this property. + /// + public int Crc32 { get { return _Crc32; } } + + private int _headerByteCount; + internal ZlibBaseStream _baseStream; + bool _disposed; + bool _firstReadDone; + string _FileName; + string _Comment; + int _Crc32; + + + /// + /// Create a GZipStream using the specified CompressionMode. + /// + /// + /// + /// + /// When mode is CompressionMode.Compress, the GZipStream will use the + /// default compression level. + /// + /// + /// + /// As noted in the class documentation, the CompressionMode (Compress + /// or Decompress) also establishes the "direction" of the stream. A + /// GZipStream with CompressionMode.Compress works only through + /// Write(). A GZipStream with + /// CompressionMode.Decompress works only through Read(). + /// + /// + /// + /// + /// + /// This example shows how to use a GZipStream to compress data. + /// + /// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress)) + /// { + /// using (var raw = System.IO.File.Create(outputFile)) + /// { + /// using (Stream compressor = new GZipStream(raw, CompressionMode.Compress)) + /// { + /// byte[] buffer = new byte[WORKING_BUFFER_SIZE]; + /// int n; + /// while ((n= input.Read(buffer, 0, buffer.Length)) != 0) + /// { + /// compressor.Write(buffer, 0, n); + /// } + /// } + /// } + /// } + /// + /// + /// Dim outputFile As String = (fileToCompress & ".compressed") + /// Using input As Stream = File.OpenRead(fileToCompress) + /// Using raw As FileStream = File.Create(outputFile) + /// Using compressor As Stream = New GZipStream(raw, CompressionMode.Compress) + /// Dim buffer As Byte() = New Byte(4096) {} + /// Dim n As Integer = -1 + /// Do While (n <> 0) + /// If (n > 0) Then + /// compressor.Write(buffer, 0, n) + /// End If + /// n = input.Read(buffer, 0, buffer.Length) + /// Loop + /// End Using + /// End Using + /// End Using + /// + /// + /// + /// + /// This example shows how to use a GZipStream to uncompress a file. + /// + /// private void GunZipFile(string filename) + /// { + /// if (!filename.EndsWith(".gz)) + /// throw new ArgumentException("filename"); + /// var DecompressedFile = filename.Substring(0,filename.Length-3); + /// byte[] working = new byte[WORKING_BUFFER_SIZE]; + /// int n= 1; + /// using (System.IO.Stream input = System.IO.File.OpenRead(filename)) + /// { + /// using (Stream decompressor= new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, true)) + /// { + /// using (var output = System.IO.File.Create(DecompressedFile)) + /// { + /// while (n !=0) + /// { + /// n= decompressor.Read(working, 0, working.Length); + /// if (n > 0) + /// { + /// output.Write(working, 0, n); + /// } + /// } + /// } + /// } + /// } + /// } + /// + /// + /// + /// Private Sub GunZipFile(ByVal filename as String) + /// If Not (filename.EndsWith(".gz)) Then + /// Throw New ArgumentException("filename") + /// End If + /// Dim DecompressedFile as String = filename.Substring(0,filename.Length-3) + /// Dim working(WORKING_BUFFER_SIZE) as Byte + /// Dim n As Integer = 1 + /// Using input As Stream = File.OpenRead(filename) + /// Using decompressor As Stream = new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, True) + /// Using output As Stream = File.Create(UncompressedFile) + /// Do + /// n= decompressor.Read(working, 0, working.Length) + /// If n > 0 Then + /// output.Write(working, 0, n) + /// End IF + /// Loop While (n > 0) + /// End Using + /// End Using + /// End Using + /// End Sub + /// + /// + /// + /// The stream which will be read or written. + /// Indicates whether the GZipStream will compress or decompress. + public GZipStream(Stream stream, CompressionMode mode) + : this(stream, mode, CompressionLevel.Default, false) + { + } + + /// + /// Create a GZipStream using the specified CompressionMode and + /// the specified CompressionLevel. + /// + /// + /// + /// + /// The CompressionMode (Compress or Decompress) also establishes the + /// "direction" of the stream. A GZipStream with + /// CompressionMode.Compress works only through Write(). A + /// GZipStream with CompressionMode.Decompress works only + /// through Read(). + /// + /// + /// + /// + /// + /// + /// This example shows how to use a GZipStream to compress a file into a .gz file. + /// + /// + /// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress)) + /// { + /// using (var raw = System.IO.File.Create(fileToCompress + ".gz")) + /// { + /// using (Stream compressor = new GZipStream(raw, + /// CompressionMode.Compress, + /// CompressionLevel.BestCompression)) + /// { + /// byte[] buffer = new byte[WORKING_BUFFER_SIZE]; + /// int n; + /// while ((n= input.Read(buffer, 0, buffer.Length)) != 0) + /// { + /// compressor.Write(buffer, 0, n); + /// } + /// } + /// } + /// } + /// + /// + /// + /// Using input As Stream = File.OpenRead(fileToCompress) + /// Using raw As FileStream = File.Create(fileToCompress & ".gz") + /// Using compressor As Stream = New GZipStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression) + /// Dim buffer As Byte() = New Byte(4096) {} + /// Dim n As Integer = -1 + /// Do While (n <> 0) + /// If (n > 0) Then + /// compressor.Write(buffer, 0, n) + /// End If + /// n = input.Read(buffer, 0, buffer.Length) + /// Loop + /// End Using + /// End Using + /// End Using + /// + /// + /// The stream to be read or written while deflating or inflating. + /// Indicates whether the GZipStream will compress or decompress. + /// A tuning knob to trade speed for effectiveness. + public GZipStream(Stream stream, CompressionMode mode, CompressionLevel level) + : this(stream, mode, level, false) + { + } + + /// + /// Create a GZipStream using the specified CompressionMode, and + /// explicitly specify whether the stream should be left open after Deflation + /// or Inflation. + /// + /// + /// + /// + /// This constructor allows the application to request that the captive stream + /// remain open after the deflation or inflation occurs. By default, after + /// Close() is called on the stream, the captive stream is also + /// closed. In some cases this is not desired, for example if the stream is a + /// memory stream that will be re-read after compressed data has been written + /// to it. Specify true for the parameter to leave + /// the stream open. + /// + /// + /// + /// The (Compress or Decompress) also + /// establishes the "direction" of the stream. A GZipStream with + /// CompressionMode.Compress works only through Write(). A GZipStream + /// with CompressionMode.Decompress works only through Read(). + /// + /// + /// + /// The GZipStream will use the default compression level. If you want + /// to specify the compression level, see . + /// + /// + /// + /// See the other overloads of this constructor for example code. + /// + /// + /// + /// + /// + /// The stream which will be read or written. This is called the "captive" + /// stream in other places in this documentation. + /// + /// + /// Indicates whether the GZipStream will compress or decompress. + /// + /// + /// + /// true if the application would like the base stream to remain open after + /// inflation/deflation. + /// + public GZipStream(Stream stream, CompressionMode mode, bool leaveOpen) + : this(stream, mode, CompressionLevel.Default, leaveOpen) + { + } + + /// + /// Create a GZipStream using the specified CompressionMode and the + /// specified CompressionLevel, and explicitly specify whether the + /// stream should be left open after Deflation or Inflation. + /// + /// + /// + /// + /// + /// This constructor allows the application to request that the captive stream + /// remain open after the deflation or inflation occurs. By default, after + /// Close() is called on the stream, the captive stream is also + /// closed. In some cases this is not desired, for example if the stream is a + /// memory stream that will be re-read after compressed data has been written + /// to it. Specify true for the parameter to + /// leave the stream open. + /// + /// + /// + /// As noted in the class documentation, the CompressionMode (Compress + /// or Decompress) also establishes the "direction" of the stream. A + /// GZipStream with CompressionMode.Compress works only through + /// Write(). A GZipStream with CompressionMode.Decompress works only + /// through Read(). + /// + /// + /// + /// + /// + /// This example shows how to use a GZipStream to compress data. + /// + /// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress)) + /// { + /// using (var raw = System.IO.File.Create(outputFile)) + /// { + /// using (Stream compressor = new GZipStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression, true)) + /// { + /// byte[] buffer = new byte[WORKING_BUFFER_SIZE]; + /// int n; + /// while ((n= input.Read(buffer, 0, buffer.Length)) != 0) + /// { + /// compressor.Write(buffer, 0, n); + /// } + /// } + /// } + /// } + /// + /// + /// Dim outputFile As String = (fileToCompress & ".compressed") + /// Using input As Stream = File.OpenRead(fileToCompress) + /// Using raw As FileStream = File.Create(outputFile) + /// Using compressor As Stream = New GZipStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression, True) + /// Dim buffer As Byte() = New Byte(4096) {} + /// Dim n As Integer = -1 + /// Do While (n <> 0) + /// If (n > 0) Then + /// compressor.Write(buffer, 0, n) + /// End If + /// n = input.Read(buffer, 0, buffer.Length) + /// Loop + /// End Using + /// End Using + /// End Using + /// + /// + /// The stream which will be read or written. + /// Indicates whether the GZipStream will compress or decompress. + /// true if the application would like the stream to remain open after inflation/deflation. + /// A tuning knob to trade speed for effectiveness. + public GZipStream(Stream stream, CompressionMode mode, CompressionLevel level, bool leaveOpen) + { + _baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.GZIP, leaveOpen); + } + + #region Zlib properties + + /// + /// This property sets the flush behavior on the stream. + /// + virtual public FlushType FlushMode + { + get { return (this._baseStream._flushMode); } + set { + if (_disposed) throw new ObjectDisposedException("GZipStream"); + this._baseStream._flushMode = value; + } + } + + /// + /// The size of the working buffer for the compression codec. + /// + /// + /// + /// + /// The working buffer is used for all stream operations. The default size is + /// 1024 bytes. The minimum size is 128 bytes. You may get better performance + /// with a larger buffer. Then again, you might not. You would have to test + /// it. + /// + /// + /// + /// Set this before the first call to Read() or Write() on the + /// stream. If you try to set it afterwards, it will throw. + /// + /// + public int BufferSize + { + get + { + return this._baseStream._bufferSize; + } + set + { + if (_disposed) throw new ObjectDisposedException("GZipStream"); + if (this._baseStream._workingBuffer != null) + throw new ZlibException("The working buffer is already set."); + if (value < ZlibConstants.WorkingBufferSizeMin) + throw new ZlibException(String.Format("Don't be silly. {0} bytes?? Use a bigger buffer, at least {1}.", value, ZlibConstants.WorkingBufferSizeMin)); + this._baseStream._bufferSize = value; + } + } + + + /// Returns the total number of bytes input so far. + virtual public long TotalIn + { + get + { + return this._baseStream._z.TotalBytesIn; + } + } + + /// Returns the total number of bytes output so far. + virtual public long TotalOut + { + get + { + return this._baseStream._z.TotalBytesOut; + } + } + + #endregion + + #region Stream methods + + /// + /// Dispose the stream. + /// + /// + /// + /// This may or may not result in a Close() call on the captive + /// stream. See the constructors that have a leaveOpen parameter + /// for more information. + /// + /// + /// This method may be invoked in two distinct scenarios. If disposing + /// == true, the method has been called directly or indirectly by a + /// user's code, for example via the public Dispose() method. In this + /// case, both managed and unmanaged resources can be referenced and + /// disposed. If disposing == false, the method has been called by the + /// runtime from inside the object finalizer and this method should not + /// reference other objects; in that case only unmanaged resources must + /// be referenced or disposed. + /// + /// + /// + /// indicates whether the Dispose method was invoked by user code. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!_disposed) + { + if (disposing && (this._baseStream != null)) + { + this._baseStream.Close(); + this._Crc32 = _baseStream.Crc32; + } + _disposed = true; + } + } + finally + { + base.Dispose(disposing); + } + } + + + /// + /// Indicates whether the stream can be read. + /// + /// + /// The return value depends on whether the captive stream supports reading. + /// + public override bool CanRead + { + get + { + if (_disposed) throw new ObjectDisposedException("GZipStream"); + return _baseStream._stream.CanRead; + } + } + + /// + /// Indicates whether the stream supports Seek operations. + /// + /// + /// Always returns false. + /// + public override bool CanSeek + { + get { return false; } + } + + + /// + /// Indicates whether the stream can be written. + /// + /// + /// The return value depends on whether the captive stream supports writing. + /// + public override bool CanWrite + { + get + { + if (_disposed) throw new ObjectDisposedException("GZipStream"); + return _baseStream._stream.CanWrite; + } + } + + /// + /// Flush the stream. + /// + public override void Flush() + { + if (_disposed) throw new ObjectDisposedException("GZipStream"); + _baseStream.Flush(); + } + + /// + /// Reading this property always throws a . + /// + public override long Length + { + get { throw new NotImplementedException(); } + } + + /// + /// The position of the stream pointer. + /// + /// + /// + /// Setting this property always throws a . Reading will return the total bytes + /// written out, if used in writing, or the total bytes read in, if used in + /// reading. The count may refer to compressed bytes or uncompressed bytes, + /// depending on how you've used the stream. + /// + public override long Position + { + get + { + if (this._baseStream._streamMode == Ionic.Zlib.ZlibBaseStream.StreamMode.Writer) + return this._baseStream._z.TotalBytesOut + _headerByteCount; + if (this._baseStream._streamMode == Ionic.Zlib.ZlibBaseStream.StreamMode.Reader) + return this._baseStream._z.TotalBytesIn + this._baseStream._gzipHeaderByteCount; + return 0; + } + + set { throw new NotImplementedException(); } + } + + /// + /// Read and decompress data from the source stream. + /// + /// + /// + /// With a GZipStream, decompression is done through reading. + /// + /// + /// + /// + /// byte[] working = new byte[WORKING_BUFFER_SIZE]; + /// using (System.IO.Stream input = System.IO.File.OpenRead(_CompressedFile)) + /// { + /// using (Stream decompressor= new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, true)) + /// { + /// using (var output = System.IO.File.Create(_DecompressedFile)) + /// { + /// int n; + /// while ((n= decompressor.Read(working, 0, working.Length)) !=0) + /// { + /// output.Write(working, 0, n); + /// } + /// } + /// } + /// } + /// + /// + /// The buffer into which the decompressed data should be placed. + /// the offset within that data array to put the first byte read. + /// the number of bytes to read. + /// the number of bytes actually read + public override int Read(byte[] buffer, int offset, int count) + { + if (_disposed) throw new ObjectDisposedException("GZipStream"); + int n = _baseStream.Read(buffer, offset, count); + + // Console.WriteLine("GZipStream::Read(buffer, off({0}), c({1}) = {2}", offset, count, n); + // Console.WriteLine( Util.FormatByteArray(buffer, offset, n) ); + + if (!_firstReadDone) + { + _firstReadDone = true; + FileName = _baseStream._GzipFileName; + Comment = _baseStream._GzipComment; + } + return n; + } + + + + /// + /// Calling this method always throws a . + /// + /// irrelevant; it will always throw! + /// irrelevant; it will always throw! + /// irrelevant! + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + /// + /// Calling this method always throws a . + /// + /// irrelevant; this method will always throw! + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + /// + /// Write data to the stream. + /// + /// + /// + /// + /// If you wish to use the GZipStream to compress data while writing, + /// you can create a GZipStream with CompressionMode.Compress, and a + /// writable output stream. Then call Write() on that GZipStream, + /// providing uncompressed data as input. The data sent to the output stream + /// will be the compressed form of the data written. + /// + /// + /// + /// A GZipStream can be used for Read() or Write(), but not + /// both. Writing implies compression. Reading implies decompression. + /// + /// + /// + /// The buffer holding data to write to the stream. + /// the offset within that data array to find the first byte to write. + /// the number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + if (_disposed) throw new ObjectDisposedException("GZipStream"); + if (_baseStream._streamMode == Ionic.Zlib.ZlibBaseStream.StreamMode.Undefined) + { + //Console.WriteLine("GZipStream: First write"); + if (_baseStream._wantCompress) + { + // first write in compression, therefore, emit the GZIP header + _headerByteCount = EmitHeader(); + } + else + { + throw new InvalidOperationException(); + } + } + + _baseStream.Write(buffer, offset, count); + } + #endregion + + + internal static readonly System.DateTime _unixEpoch = new System.DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); +#if SILVERLIGHT || NETCF + internal static readonly System.Text.Encoding iso8859dash1 = new Ionic.Encoding.Iso8859Dash1Encoding(); +#else + internal static readonly System.Text.Encoding iso8859dash1 = System.Text.Encoding.GetEncoding("iso-8859-1"); +#endif + + + private int EmitHeader() + { + byte[] commentBytes = (Comment == null) ? null : iso8859dash1.GetBytes(Comment); + byte[] filenameBytes = (FileName == null) ? null : iso8859dash1.GetBytes(FileName); + + int cbLength = (Comment == null) ? 0 : commentBytes.Length + 1; + int fnLength = (FileName == null) ? 0 : filenameBytes.Length + 1; + + int bufferLength = 10 + cbLength + fnLength; + byte[] header = new byte[bufferLength]; + int i = 0; + // ID + header[i++] = 0x1F; + header[i++] = 0x8B; + + // compression method + header[i++] = 8; + byte flag = 0; + if (Comment != null) + flag ^= 0x10; + if (FileName != null) + flag ^= 0x8; + + // flag + header[i++] = flag; + + // mtime + if (!LastModified.HasValue) LastModified = DateTime.Now; + System.TimeSpan delta = LastModified.Value - _unixEpoch; + Int32 timet = (Int32)delta.TotalSeconds; + Array.Copy(BitConverter.GetBytes(timet), 0, header, i, 4); + i += 4; + + // xflg + header[i++] = 0; // this field is totally useless + // OS + header[i++] = 0xFF; // 0xFF == unspecified + + // extra field length - only if FEXTRA is set, which it is not. + //header[i++]= 0; + //header[i++]= 0; + + // filename + if (fnLength != 0) + { + Array.Copy(filenameBytes, 0, header, i, fnLength - 1); + i += fnLength - 1; + header[i++] = 0; // terminate + } + + // comment + if (cbLength != 0) + { + Array.Copy(commentBytes, 0, header, i, cbLength - 1); + i += cbLength - 1; + header[i++] = 0; // terminate + } + + _baseStream._stream.Write(header, 0, header.Length); + + return header.Length; // bytes written + } + + + + /// + /// Compress a string into a byte array using GZip. + /// + /// + /// + /// Uncompress it with . + /// + /// + /// + /// + /// + /// + /// A string to compress. The string will first be encoded + /// using UTF8, then compressed. + /// + /// + /// The string in compressed form + public static byte[] CompressString(String s) + { + using (var ms = new MemoryStream()) + { + System.IO.Stream compressor = + new GZipStream(ms, CompressionMode.Compress, CompressionLevel.BestCompression); + ZlibBaseStream.CompressString(s, compressor); + return ms.ToArray(); + } + } + + + /// + /// Compress a byte array into a new byte array using GZip. + /// + /// + /// + /// Uncompress it with . + /// + /// + /// + /// + /// + /// + /// A buffer to compress. + /// + /// + /// The data in compressed form + public static byte[] CompressBuffer(byte[] b) + { + using (var ms = new MemoryStream()) + { + System.IO.Stream compressor = + new GZipStream( ms, CompressionMode.Compress, CompressionLevel.BestCompression ); + + ZlibBaseStream.CompressBuffer(b, compressor); + return ms.ToArray(); + } + } + + + /// + /// Uncompress a GZip'ed byte array into a single string. + /// + /// + /// + /// + /// + /// + /// A buffer containing GZIP-compressed data. + /// + /// + /// The uncompressed string + public static String UncompressString(byte[] compressed) + { + using (var input = new MemoryStream(compressed)) + { + Stream decompressor = new GZipStream(input, CompressionMode.Decompress); + return ZlibBaseStream.UncompressString(compressed, decompressor); + } + } + + + /// + /// Uncompress a GZip'ed byte array into a byte array. + /// + /// + /// + /// + /// + /// + /// A buffer containing data that has been compressed with GZip. + /// + /// + /// The data in uncompressed form + public static byte[] UncompressBuffer(byte[] compressed) + { + using (var input = new System.IO.MemoryStream(compressed)) + { + System.IO.Stream decompressor = + new GZipStream( input, CompressionMode.Decompress ); + + return ZlibBaseStream.UncompressBuffer(compressed, decompressor); + } + } + + + } +} diff --git a/src/ZlibAndroidSdkStyle/InfTree.cs b/src/ZlibAndroidSdkStyle/InfTree.cs new file mode 100644 index 00000000..416b143a --- /dev/null +++ b/src/ZlibAndroidSdkStyle/InfTree.cs @@ -0,0 +1,436 @@ +// Inftree.cs +// ------------------------------------------------------------------ +// +// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. +// All rights reserved. +// +// This code module is part of DotNetZip, a zipfile class library. +// +// ------------------------------------------------------------------ +// +// This code is licensed under the Microsoft Public License. +// See the file License.txt for the license details. +// More info on: http://dotnetzip.codeplex.com +// +// ------------------------------------------------------------------ +// +// last saved (in emacs): +// Time-stamp: <2009-October-28 12:43:54> +// +// ------------------------------------------------------------------ +// +// This module defines classes used in decompression. This code is derived +// from the jzlib implementation of zlib. In keeping with the license for jzlib, +// the copyright to that code is below. +// +// ------------------------------------------------------------------ +// +// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the distribution. +// +// 3. The names of the authors may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------- +// +// This program is based on zlib-1.1.3; credit to authors +// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) +// and contributors of zlib. +// +// ----------------------------------------------------------------------- + + + +using System; +namespace Ionic.Zlib +{ + + sealed class InfTree + { + + private const int MANY = 1440; + + private const int Z_OK = 0; + private const int Z_STREAM_END = 1; + private const int Z_NEED_DICT = 2; + private const int Z_ERRNO = - 1; + private const int Z_STREAM_ERROR = - 2; + private const int Z_DATA_ERROR = - 3; + private const int Z_MEM_ERROR = - 4; + private const int Z_BUF_ERROR = - 5; + private const int Z_VERSION_ERROR = - 6; + + internal const int fixed_bl = 9; + internal const int fixed_bd = 5; + + //UPGRADE_NOTE: Final was removed from the declaration of 'fixed_tl'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + internal static readonly int[] fixed_tl = new int[]{96, 7, 256, 0, 8, 80, 0, 8, 16, 84, 8, 115, 82, 7, 31, 0, 8, 112, 0, 8, 48, 0, 9, 192, 80, 7, 10, 0, 8, 96, 0, 8, 32, 0, 9, 160, 0, 8, 0, 0, 8, 128, 0, 8, 64, 0, 9, 224, 80, 7, 6, 0, 8, 88, 0, 8, 24, 0, 9, 144, 83, 7, 59, 0, 8, 120, 0, 8, 56, 0, 9, 208, 81, 7, 17, 0, 8, 104, 0, 8, 40, 0, 9, 176, 0, 8, 8, 0, 8, 136, 0, 8, 72, 0, 9, 240, 80, 7, 4, 0, 8, 84, 0, 8, 20, 85, 8, 227, 83, 7, 43, 0, 8, 116, 0, 8, 52, 0, 9, 200, 81, 7, 13, 0, 8, 100, 0, 8, 36, 0, 9, 168, 0, 8, 4, 0, 8, 132, 0, 8, 68, 0, 9, 232, 80, 7, 8, 0, 8, 92, 0, 8, 28, 0, 9, 152, 84, 7, 83, 0, 8, 124, 0, 8, 60, 0, 9, 216, 82, 7, 23, 0, 8, 108, 0, 8, 44, 0, 9, 184, 0, 8, 12, 0, 8, 140, 0, 8, 76, 0, 9, 248, 80, 7, 3, 0, 8, 82, 0, 8, 18, 85, 8, 163, 83, 7, 35, 0, 8, 114, 0, 8, 50, 0, 9, 196, 81, 7, 11, 0, 8, 98, 0, 8, 34, 0, 9, 164, 0, 8, 2, 0, 8, 130, 0, 8, 66, 0, 9, 228, 80, 7, 7, 0, 8, 90, 0, 8, 26, 0, 9, 148, 84, 7, 67, 0, 8, 122, 0, 8, 58, 0, 9, 212, 82, 7, 19, 0, 8, 106, 0, 8, 42, 0, 9, 180, 0, 8, 10, 0, 8, 138, 0, 8, 74, 0, 9, 244, 80, 7, 5, 0, 8, 86, 0, 8, 22, 192, 8, 0, 83, 7, 51, 0, 8, 118, 0, 8, 54, 0, 9, 204, 81, 7, 15, 0, 8, 102, 0, 8, 38, 0, 9, 172, 0, 8, 6, 0, 8, 134, 0, 8, 70, 0, 9, 236, 80, 7, 9, 0, 8, 94, 0, 8, 30, 0, 9, 156, 84, 7, 99, 0, 8, 126, 0, 8, 62, 0, 9, 220, 82, 7, 27, 0, 8, 110, 0, 8, 46, 0, 9, 188, 0, 8, 14, 0, 8, 142, 0, 8, 78, 0, 9, 252, 96, 7, 256, 0, 8, 81, 0, 8, 17, 85, 8, 131, 82, 7, 31, 0, 8, 113, 0, 8, 49, 0, 9, 194, 80, 7, 10, 0, 8, 97, 0, 8, 33, 0, 9, 162, 0, 8, 1, 0, 8, 129, 0, 8, 65, 0, 9, 226, 80, 7, 6, 0, 8, 89, 0, 8, 25, 0, 9, 146, 83, 7, 59, 0, 8, 121, 0, 8, 57, 0, 9, 210, 81, 7, 17, 0, 8, 105, 0, 8, 41, 0, 9, 178, 0, 8, 9, 0, 8, 137, 0, 8, 73, 0, 9, 242, 80, 7, 4, 0, 8, 85, 0, 8, 21, 80, 8, 258, 83, 7, 43, 0, 8, 117, 0, 8, 53, 0, 9, 202, 81, 7, 13, 0, 8, 101, 0, 8, 37, 0, 9, 170, 0, 8, 5, 0, 8, 133, 0, 8, 69, 0, 9, 234, 80, 7, 8, 0, 8, 93, 0, 8, 29, 0, 9, 154, 84, 7, 83, 0, 8, 125, 0, 8, 61, 0, 9, 218, 82, 7, 23, 0, 8, 109, 0, 8, 45, 0, 9, 186, + 0, 8, 13, 0, 8, 141, 0, 8, 77, 0, 9, 250, 80, 7, 3, 0, 8, 83, 0, 8, 19, 85, 8, 195, 83, 7, 35, 0, 8, 115, 0, 8, 51, 0, 9, 198, 81, 7, 11, 0, 8, 99, 0, 8, 35, 0, 9, 166, 0, 8, 3, 0, 8, 131, 0, 8, 67, 0, 9, 230, 80, 7, 7, 0, 8, 91, 0, 8, 27, 0, 9, 150, 84, 7, 67, 0, 8, 123, 0, 8, 59, 0, 9, 214, 82, 7, 19, 0, 8, 107, 0, 8, 43, 0, 9, 182, 0, 8, 11, 0, 8, 139, 0, 8, 75, 0, 9, 246, 80, 7, 5, 0, 8, 87, 0, 8, 23, 192, 8, 0, 83, 7, 51, 0, 8, 119, 0, 8, 55, 0, 9, 206, 81, 7, 15, 0, 8, 103, 0, 8, 39, 0, 9, 174, 0, 8, 7, 0, 8, 135, 0, 8, 71, 0, 9, 238, 80, 7, 9, 0, 8, 95, 0, 8, 31, 0, 9, 158, 84, 7, 99, 0, 8, 127, 0, 8, 63, 0, 9, 222, 82, 7, 27, 0, 8, 111, 0, 8, 47, 0, 9, 190, 0, 8, 15, 0, 8, 143, 0, 8, 79, 0, 9, 254, 96, 7, 256, 0, 8, 80, 0, 8, 16, 84, 8, 115, 82, 7, 31, 0, 8, 112, 0, 8, 48, 0, 9, 193, 80, 7, 10, 0, 8, 96, 0, 8, 32, 0, 9, 161, 0, 8, 0, 0, 8, 128, 0, 8, 64, 0, 9, 225, 80, 7, 6, 0, 8, 88, 0, 8, 24, 0, 9, 145, 83, 7, 59, 0, 8, 120, 0, 8, 56, 0, 9, 209, 81, 7, 17, 0, 8, 104, 0, 8, 40, 0, 9, 177, 0, 8, 8, 0, 8, 136, 0, 8, 72, 0, 9, 241, 80, 7, 4, 0, 8, 84, 0, 8, 20, 85, 8, 227, 83, 7, 43, 0, 8, 116, 0, 8, 52, 0, 9, 201, 81, 7, 13, 0, 8, 100, 0, 8, 36, 0, 9, 169, 0, 8, 4, 0, 8, 132, 0, 8, 68, 0, 9, 233, 80, 7, 8, 0, 8, 92, 0, 8, 28, 0, 9, 153, 84, 7, 83, 0, 8, 124, 0, 8, 60, 0, 9, 217, 82, 7, 23, 0, 8, 108, 0, 8, 44, 0, 9, 185, 0, 8, 12, 0, 8, 140, 0, 8, 76, 0, 9, 249, 80, 7, 3, 0, 8, 82, 0, 8, 18, 85, 8, 163, 83, 7, 35, 0, 8, 114, 0, 8, 50, 0, 9, 197, 81, 7, 11, 0, 8, 98, 0, 8, 34, 0, 9, 165, 0, 8, 2, 0, 8, 130, 0, 8, 66, 0, 9, 229, 80, 7, 7, 0, 8, 90, 0, 8, 26, 0, 9, 149, 84, 7, 67, 0, 8, 122, 0, 8, 58, 0, 9, 213, 82, 7, 19, 0, 8, 106, 0, 8, 42, 0, 9, 181, 0, 8, 10, 0, 8, 138, 0, 8, 74, 0, 9, 245, 80, 7, 5, 0, 8, 86, 0, 8, 22, 192, 8, 0, 83, 7, 51, 0, 8, 118, 0, 8, 54, 0, 9, 205, 81, 7, 15, 0, 8, 102, 0, 8, 38, 0, 9, 173, 0, 8, 6, 0, 8, 134, 0, 8, 70, 0, 9, 237, 80, 7, 9, 0, 8, 94, 0, 8, 30, 0, 9, 157, 84, 7, 99, 0, 8, 126, 0, 8, 62, 0, 9, 221, 82, 7, 27, 0, 8, 110, 0, 8, 46, 0, 9, 189, 0, 8, + 14, 0, 8, 142, 0, 8, 78, 0, 9, 253, 96, 7, 256, 0, 8, 81, 0, 8, 17, 85, 8, 131, 82, 7, 31, 0, 8, 113, 0, 8, 49, 0, 9, 195, 80, 7, 10, 0, 8, 97, 0, 8, 33, 0, 9, 163, 0, 8, 1, 0, 8, 129, 0, 8, 65, 0, 9, 227, 80, 7, 6, 0, 8, 89, 0, 8, 25, 0, 9, 147, 83, 7, 59, 0, 8, 121, 0, 8, 57, 0, 9, 211, 81, 7, 17, 0, 8, 105, 0, 8, 41, 0, 9, 179, 0, 8, 9, 0, 8, 137, 0, 8, 73, 0, 9, 243, 80, 7, 4, 0, 8, 85, 0, 8, 21, 80, 8, 258, 83, 7, 43, 0, 8, 117, 0, 8, 53, 0, 9, 203, 81, 7, 13, 0, 8, 101, 0, 8, 37, 0, 9, 171, 0, 8, 5, 0, 8, 133, 0, 8, 69, 0, 9, 235, 80, 7, 8, 0, 8, 93, 0, 8, 29, 0, 9, 155, 84, 7, 83, 0, 8, 125, 0, 8, 61, 0, 9, 219, 82, 7, 23, 0, 8, 109, 0, 8, 45, 0, 9, 187, 0, 8, 13, 0, 8, 141, 0, 8, 77, 0, 9, 251, 80, 7, 3, 0, 8, 83, 0, 8, 19, 85, 8, 195, 83, 7, 35, 0, 8, 115, 0, 8, 51, 0, 9, 199, 81, 7, 11, 0, 8, 99, 0, 8, 35, 0, 9, 167, 0, 8, 3, 0, 8, 131, 0, 8, 67, 0, 9, 231, 80, 7, 7, 0, 8, 91, 0, 8, 27, 0, 9, 151, 84, 7, 67, 0, 8, 123, 0, 8, 59, 0, 9, 215, 82, 7, 19, 0, 8, 107, 0, 8, 43, 0, 9, 183, 0, 8, 11, 0, 8, 139, 0, 8, 75, 0, 9, 247, 80, 7, 5, 0, 8, 87, 0, 8, 23, 192, 8, 0, 83, 7, 51, 0, 8, 119, 0, 8, 55, 0, 9, 207, 81, 7, 15, 0, 8, 103, 0, 8, 39, 0, 9, 175, 0, 8, 7, 0, 8, 135, 0, 8, 71, 0, 9, 239, 80, 7, 9, 0, 8, 95, 0, 8, 31, 0, 9, 159, 84, 7, 99, 0, 8, 127, 0, 8, 63, 0, 9, 223, 82, 7, 27, 0, 8, 111, 0, 8, 47, 0, 9, 191, 0, 8, 15, 0, 8, 143, 0, 8, 79, 0, 9, 255}; + //UPGRADE_NOTE: Final was removed from the declaration of 'fixed_td'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + internal static readonly int[] fixed_td = new int[]{80, 5, 1, 87, 5, 257, 83, 5, 17, 91, 5, 4097, 81, 5, 5, 89, 5, 1025, 85, 5, 65, 93, 5, 16385, 80, 5, 3, 88, 5, 513, 84, 5, 33, 92, 5, 8193, 82, 5, 9, 90, 5, 2049, 86, 5, 129, 192, 5, 24577, 80, 5, 2, 87, 5, 385, 83, 5, 25, 91, 5, 6145, 81, 5, 7, 89, 5, 1537, 85, 5, 97, 93, 5, 24577, 80, 5, 4, 88, 5, 769, 84, 5, 49, 92, 5, 12289, 82, 5, 13, 90, 5, 3073, 86, 5, 193, 192, 5, 24577}; + + // Tables for deflate from PKZIP's appnote.txt. + //UPGRADE_NOTE: Final was removed from the declaration of 'cplens'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + internal static readonly int[] cplens = new int[]{3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; + + // see note #13 above about 258 + //UPGRADE_NOTE: Final was removed from the declaration of 'cplext'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + internal static readonly int[] cplext = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112}; + + //UPGRADE_NOTE: Final was removed from the declaration of 'cpdist'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + internal static readonly int[] cpdist = new int[]{1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; + + //UPGRADE_NOTE: Final was removed from the declaration of 'cpdext'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + internal static readonly int[] cpdext = new int[]{0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + + // If BMAX needs to be larger than 16, then h and x[] should be uLong. + internal const int BMAX = 15; // maximum bit length of any code + + internal int[] hn = null; // hufts used in space + internal int[] v = null; // work area for huft_build + internal int[] c = null; // bit length count table + internal int[] r = null; // table entry for structure assignment + internal int[] u = null; // table stack + internal int[] x = null; // bit offsets, then code stack + + private int huft_build(int[] b, int bindex, int n, int s, int[] d, int[] e, int[] t, int[] m, int[] hp, int[] hn, int[] v) + { + // Given a list of code lengths and a maximum table size, make a set of + // tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR + // if the given code set is incomplete (the tables are still built in this + // case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of + // lengths), or Z_MEM_ERROR if not enough memory. + + int a; // counter for codes of length k + int f; // i repeats in table every f entries + int g; // maximum code length + int h; // table level + int i; // counter, current code + int j; // counter + int k; // number of bits in current code + int l; // bits per table (returned in m) + int mask; // (1 << w) - 1, to avoid cc -O bug on HP + int p; // pointer into c[], b[], or v[] + int q; // points to current table + int w; // bits before this table == (l * h) + int xp; // pointer into x + int y; // number of dummy codes added + int z; // number of entries in current table + + // Generate counts for each bit length + + p = 0; i = n; + do + { + c[b[bindex + p]]++; p++; i--; // assume all entries <= BMAX + } + while (i != 0); + + if (c[0] == n) + { + // null input--all zero length codes + t[0] = - 1; + m[0] = 0; + return Z_OK; + } + + // Find minimum and maximum length, bound *m by those + l = m[0]; + for (j = 1; j <= BMAX; j++) + if (c[j] != 0) + break; + k = j; // minimum code length + if (l < j) + { + l = j; + } + for (i = BMAX; i != 0; i--) + { + if (c[i] != 0) + break; + } + g = i; // maximum code length + if (l > i) + { + l = i; + } + m[0] = l; + + // Adjust last length count to fill out codes, if needed + for (y = 1 << j; j < i; j++, y <<= 1) + { + if ((y -= c[j]) < 0) + { + return Z_DATA_ERROR; + } + } + if ((y -= c[i]) < 0) + { + return Z_DATA_ERROR; + } + c[i] += y; + + // Generate starting offsets into the value table for each length + x[1] = j = 0; + p = 1; xp = 2; + while (--i != 0) + { + // note that i == g from above + x[xp] = (j += c[p]); + xp++; + p++; + } + + // Make a table of values in order of bit lengths + i = 0; p = 0; + do + { + if ((j = b[bindex + p]) != 0) + { + v[x[j]++] = i; + } + p++; + } + while (++i < n); + n = x[g]; // set n to length of v + + // Generate the Huffman codes and for each, make the table entries + x[0] = i = 0; // first Huffman code is zero + p = 0; // grab values in bit order + h = - 1; // no tables yet--level -1 + w = - l; // bits decoded == (l * h) + u[0] = 0; // just to keep compilers happy + q = 0; // ditto + z = 0; // ditto + + // go through the bit lengths (k already is bits in shortest code) + for (; k <= g; k++) + { + a = c[k]; + while (a-- != 0) + { + // here i is the Huffman code of length k bits for value *p + // make tables up to required level + while (k > w + l) + { + h++; + w += l; // previous table always l bits + // compute minimum size table less than or equal to l bits + z = g - w; + z = (z > l)?l:z; // table size upper limit + if ((f = 1 << (j = k - w)) > a + 1) + { + // try a k-w bit table + // too few codes for k-w bit table + f -= (a + 1); // deduct codes from patterns left + xp = k; + if (j < z) + { + while (++j < z) + { + // try smaller tables up to z bits + if ((f <<= 1) <= c[++xp]) + break; // enough codes to use up j bits + f -= c[xp]; // else deduct codes from patterns + } + } + } + z = 1 << j; // table entries for j-bit table + + // allocate new table + if (hn[0] + z > MANY) + { + // (note: doesn't matter for fixed) + return Z_DATA_ERROR; // overflow of MANY + } + u[h] = q = hn[0]; // DEBUG + hn[0] += z; + + // connect to last table, if there is one + if (h != 0) + { + x[h] = i; // save pattern for backing up + r[0] = (sbyte) j; // bits in this table + r[1] = (sbyte) l; // bits to dump before this table + j = SharedUtils.URShift(i, (w - l)); + r[2] = (int) (q - u[h - 1] - j); // offset to this table + Array.Copy(r, 0, hp, (u[h - 1] + j) * 3, 3); // connect to last table + } + else + { + t[0] = q; // first table is returned result + } + } + + // set up table entry in r + r[1] = (sbyte) (k - w); + if (p >= n) + { + r[0] = 128 + 64; // out of values--invalid code + } + else if (v[p] < s) + { + r[0] = (sbyte) (v[p] < 256?0:32 + 64); // 256 is end-of-block + r[2] = v[p++]; // simple code is just the value + } + else + { + r[0] = (sbyte) (e[v[p] - s] + 16 + 64); // non-simple--look up in lists + r[2] = d[v[p++] - s]; + } + + // fill code-like entries with r + f = 1 << (k - w); + for (j = SharedUtils.URShift(i, w); j < z; j += f) + { + Array.Copy(r, 0, hp, (q + j) * 3, 3); + } + + // backwards increment the k-bit code i + for (j = 1 << (k - 1); (i & j) != 0; j = SharedUtils.URShift(j, 1)) + { + i ^= j; + } + i ^= j; + + // backup over finished tables + mask = (1 << w) - 1; // needed on HP, cc -O bug + while ((i & mask) != x[h]) + { + h--; // don't need to update q + w -= l; + mask = (1 << w) - 1; + } + } + } + // Return Z_BUF_ERROR if we were given an incomplete table + return y != 0 && g != 1?Z_BUF_ERROR:Z_OK; + } + + internal int inflate_trees_bits(int[] c, int[] bb, int[] tb, int[] hp, ZlibCodec z) + { + int result; + initWorkArea(19); + hn[0] = 0; + result = huft_build(c, 0, 19, 19, null, null, tb, bb, hp, hn, v); + + if (result == Z_DATA_ERROR) + { + z.Message = "oversubscribed dynamic bit lengths tree"; + } + else if (result == Z_BUF_ERROR || bb[0] == 0) + { + z.Message = "incomplete dynamic bit lengths tree"; + result = Z_DATA_ERROR; + } + return result; + } + + internal int inflate_trees_dynamic(int nl, int nd, int[] c, int[] bl, int[] bd, int[] tl, int[] td, int[] hp, ZlibCodec z) + { + int result; + + // build literal/length tree + initWorkArea(288); + hn[0] = 0; + result = huft_build(c, 0, nl, 257, cplens, cplext, tl, bl, hp, hn, v); + if (result != Z_OK || bl[0] == 0) + { + if (result == Z_DATA_ERROR) + { + z.Message = "oversubscribed literal/length tree"; + } + else if (result != Z_MEM_ERROR) + { + z.Message = "incomplete literal/length tree"; + result = Z_DATA_ERROR; + } + return result; + } + + // build distance tree + initWorkArea(288); + result = huft_build(c, nl, nd, 0, cpdist, cpdext, td, bd, hp, hn, v); + + if (result != Z_OK || (bd[0] == 0 && nl > 257)) + { + if (result == Z_DATA_ERROR) + { + z.Message = "oversubscribed distance tree"; + } + else if (result == Z_BUF_ERROR) + { + z.Message = "incomplete distance tree"; + result = Z_DATA_ERROR; + } + else if (result != Z_MEM_ERROR) + { + z.Message = "empty distance tree with lengths"; + result = Z_DATA_ERROR; + } + return result; + } + + return Z_OK; + } + + internal static int inflate_trees_fixed(int[] bl, int[] bd, int[][] tl, int[][] td, ZlibCodec z) + { + bl[0] = fixed_bl; + bd[0] = fixed_bd; + tl[0] = fixed_tl; + td[0] = fixed_td; + return Z_OK; + } + + private void initWorkArea(int vsize) + { + if (hn == null) + { + hn = new int[1]; + v = new int[vsize]; + c = new int[BMAX + 1]; + r = new int[3]; + u = new int[BMAX]; + x = new int[BMAX + 1]; + } + else + { + if (v.Length < vsize) + { + v = new int[vsize]; + } + Array.Clear(v,0,vsize); + Array.Clear(c,0,BMAX+1); + r[0]=0; r[1]=0; r[2]=0; + // for(int i=0; i +// +// ------------------------------------------------------------------ +// +// This module defines classes for decompression. This code is derived +// from the jzlib implementation of zlib, but significantly modified. +// The object model is not the same, and many of the behaviors are +// different. Nonetheless, in keeping with the license for jzlib, I am +// reproducing the copyright to that code here. +// +// ------------------------------------------------------------------ +// +// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the distribution. +// +// 3. The names of the authors may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------- +// +// This program is based on zlib-1.1.3; credit to authors +// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) +// and contributors of zlib. +// +// ----------------------------------------------------------------------- + + +using System; +namespace Ionic.Zlib +{ + sealed class InflateBlocks + { + private const int MANY = 1440; + + // Table for deflate from PKZIP's appnote.txt. + internal static readonly int[] border = new int[] + { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + + private enum InflateBlockMode + { + TYPE = 0, // get type bits (3, including end bit) + LENS = 1, // get lengths for stored + STORED = 2, // processing stored block + TABLE = 3, // get table lengths + BTREE = 4, // get bit lengths tree for a dynamic block + DTREE = 5, // get length, distance trees for a dynamic block + CODES = 6, // processing fixed or dynamic block + DRY = 7, // output remaining window bytes + DONE = 8, // finished last block, done + BAD = 9, // ot a data error--stuck here + } + + private InflateBlockMode mode; // current inflate_block mode + + internal int left; // if STORED, bytes left to copy + + internal int table; // table lengths (14 bits) + internal int index; // index into blens (or border) + internal int[] blens; // bit lengths of codes + internal int[] bb = new int[1]; // bit length tree depth + internal int[] tb = new int[1]; // bit length decoding tree + + internal InflateCodes codes = new InflateCodes(); // if CODES, current state + + internal int last; // true if this block is the last block + + internal ZlibCodec _codec; // pointer back to this zlib stream + + // mode independent information + internal int bitk; // bits in bit buffer + internal int bitb; // bit buffer + internal int[] hufts; // single malloc for tree space + internal byte[] window; // sliding window + internal int end; // one byte after sliding window + internal int readAt; // window read pointer + internal int writeAt; // window write pointer + internal System.Object checkfn; // check function + internal uint check; // check on output + + internal InfTree inftree = new InfTree(); + + internal InflateBlocks(ZlibCodec codec, System.Object checkfn, int w) + { + _codec = codec; + hufts = new int[MANY * 3]; + window = new byte[w]; + end = w; + this.checkfn = checkfn; + mode = InflateBlockMode.TYPE; + Reset(); + } + + internal uint Reset() + { + uint oldCheck = check; + mode = InflateBlockMode.TYPE; + bitk = 0; + bitb = 0; + readAt = writeAt = 0; + + if (checkfn != null) + _codec._Adler32 = check = Adler.Adler32(0, null, 0, 0); + return oldCheck; + } + + + internal int Process(int r) + { + int t; // temporary storage + int b; // bit buffer + int k; // bits in bit buffer + int p; // input data pointer + int n; // bytes available there + int q; // output window write pointer + int m; // bytes to end of window or read pointer + + // copy input/output information to locals (UPDATE macro restores) + + p = _codec.NextIn; + n = _codec.AvailableBytesIn; + b = bitb; + k = bitk; + + q = writeAt; + m = (int)(q < readAt ? readAt - q - 1 : end - q); + + + // process input based on current state + while (true) + { + switch (mode) + { + case InflateBlockMode.TYPE: + + while (k < (3)) + { + if (n != 0) + { + r = ZlibConstants.Z_OK; + } + else + { + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + + n--; + b |= (_codec.InputBuffer[p++] & 0xff) << k; + k += 8; + } + t = (int)(b & 7); + last = t & 1; + + switch ((uint)t >> 1) + { + case 0: // stored + b >>= 3; k -= (3); + t = k & 7; // go to byte boundary + b >>= t; k -= t; + mode = InflateBlockMode.LENS; // get length of stored block + break; + + case 1: // fixed + int[] bl = new int[1]; + int[] bd = new int[1]; + int[][] tl = new int[1][]; + int[][] td = new int[1][]; + InfTree.inflate_trees_fixed(bl, bd, tl, td, _codec); + codes.Init(bl[0], bd[0], tl[0], 0, td[0], 0); + b >>= 3; k -= 3; + mode = InflateBlockMode.CODES; + break; + + case 2: // dynamic + b >>= 3; k -= 3; + mode = InflateBlockMode.TABLE; + break; + + case 3: // illegal + b >>= 3; k -= 3; + mode = InflateBlockMode.BAD; + _codec.Message = "invalid block type"; + r = ZlibConstants.Z_DATA_ERROR; + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + break; + + case InflateBlockMode.LENS: + + while (k < (32)) + { + if (n != 0) + { + r = ZlibConstants.Z_OK; + } + else + { + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + ; + n--; + b |= (_codec.InputBuffer[p++] & 0xff) << k; + k += 8; + } + + if ( ( ((~b)>>16) & 0xffff) != (b & 0xffff)) + { + mode = InflateBlockMode.BAD; + _codec.Message = "invalid stored block lengths"; + r = ZlibConstants.Z_DATA_ERROR; + + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + left = (b & 0xffff); + b = k = 0; // dump bits + mode = left != 0 ? InflateBlockMode.STORED : (last != 0 ? InflateBlockMode.DRY : InflateBlockMode.TYPE); + break; + + case InflateBlockMode.STORED: + if (n == 0) + { + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + + if (m == 0) + { + if (q == end && readAt != 0) + { + q = 0; m = (int)(q < readAt ? readAt - q - 1 : end - q); + } + if (m == 0) + { + writeAt = q; + r = Flush(r); + q = writeAt; m = (int)(q < readAt ? readAt - q - 1 : end - q); + if (q == end && readAt != 0) + { + q = 0; m = (int)(q < readAt ? readAt - q - 1 : end - q); + } + if (m == 0) + { + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + } + } + r = ZlibConstants.Z_OK; + + t = left; + if (t > n) + t = n; + if (t > m) + t = m; + Array.Copy(_codec.InputBuffer, p, window, q, t); + p += t; n -= t; + q += t; m -= t; + if ((left -= t) != 0) + break; + mode = last != 0 ? InflateBlockMode.DRY : InflateBlockMode.TYPE; + break; + + case InflateBlockMode.TABLE: + + while (k < (14)) + { + if (n != 0) + { + r = ZlibConstants.Z_OK; + } + else + { + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + + n--; + b |= (_codec.InputBuffer[p++] & 0xff) << k; + k += 8; + } + + table = t = (b & 0x3fff); + if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29) + { + mode = InflateBlockMode.BAD; + _codec.Message = "too many length or distance symbols"; + r = ZlibConstants.Z_DATA_ERROR; + + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); + if (blens == null || blens.Length < t) + { + blens = new int[t]; + } + else + { + Array.Clear(blens, 0, t); + // for (int i = 0; i < t; i++) + // { + // blens[i] = 0; + // } + } + + b >>= 14; + k -= 14; + + + index = 0; + mode = InflateBlockMode.BTREE; + goto case InflateBlockMode.BTREE; + + case InflateBlockMode.BTREE: + while (index < 4 + (table >> 10)) + { + while (k < (3)) + { + if (n != 0) + { + r = ZlibConstants.Z_OK; + } + else + { + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + + n--; + b |= (_codec.InputBuffer[p++] & 0xff) << k; + k += 8; + } + + blens[border[index++]] = b & 7; + + b >>= 3; k -= 3; + } + + while (index < 19) + { + blens[border[index++]] = 0; + } + + bb[0] = 7; + t = inftree.inflate_trees_bits(blens, bb, tb, hufts, _codec); + if (t != ZlibConstants.Z_OK) + { + r = t; + if (r == ZlibConstants.Z_DATA_ERROR) + { + blens = null; + mode = InflateBlockMode.BAD; + } + + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + + index = 0; + mode = InflateBlockMode.DTREE; + goto case InflateBlockMode.DTREE; + + case InflateBlockMode.DTREE: + while (true) + { + t = table; + if (!(index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f))) + { + break; + } + + int i, j, c; + + t = bb[0]; + + while (k < t) + { + if (n != 0) + { + r = ZlibConstants.Z_OK; + } + else + { + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + + n--; + b |= (_codec.InputBuffer[p++] & 0xff) << k; + k += 8; + } + + t = hufts[(tb[0] + (b & InternalInflateConstants.InflateMask[t])) * 3 + 1]; + c = hufts[(tb[0] + (b & InternalInflateConstants.InflateMask[t])) * 3 + 2]; + + if (c < 16) + { + b >>= t; k -= t; + blens[index++] = c; + } + else + { + // c == 16..18 + i = c == 18 ? 7 : c - 14; + j = c == 18 ? 11 : 3; + + while (k < (t + i)) + { + if (n != 0) + { + r = ZlibConstants.Z_OK; + } + else + { + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + + n--; + b |= (_codec.InputBuffer[p++] & 0xff) << k; + k += 8; + } + + b >>= t; k -= t; + + j += (b & InternalInflateConstants.InflateMask[i]); + + b >>= i; k -= i; + + i = index; + t = table; + if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || (c == 16 && i < 1)) + { + blens = null; + mode = InflateBlockMode.BAD; + _codec.Message = "invalid bit length repeat"; + r = ZlibConstants.Z_DATA_ERROR; + + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + + c = (c == 16) ? blens[i-1] : 0; + do + { + blens[i++] = c; + } + while (--j != 0); + index = i; + } + } + + tb[0] = -1; + { + int[] bl = new int[] { 9 }; // must be <= 9 for lookahead assumptions + int[] bd = new int[] { 6 }; // must be <= 9 for lookahead assumptions + int[] tl = new int[1]; + int[] td = new int[1]; + + t = table; + t = inftree.inflate_trees_dynamic(257 + (t & 0x1f), 1 + ((t >> 5) & 0x1f), blens, bl, bd, tl, td, hufts, _codec); + + if (t != ZlibConstants.Z_OK) + { + if (t == ZlibConstants.Z_DATA_ERROR) + { + blens = null; + mode = InflateBlockMode.BAD; + } + r = t; + + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + codes.Init(bl[0], bd[0], hufts, tl[0], hufts, td[0]); + } + mode = InflateBlockMode.CODES; + goto case InflateBlockMode.CODES; + + case InflateBlockMode.CODES: + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + + r = codes.Process(this, r); + if (r != ZlibConstants.Z_STREAM_END) + { + return Flush(r); + } + + r = ZlibConstants.Z_OK; + p = _codec.NextIn; + n = _codec.AvailableBytesIn; + b = bitb; + k = bitk; + q = writeAt; + m = (int)(q < readAt ? readAt - q - 1 : end - q); + + if (last == 0) + { + mode = InflateBlockMode.TYPE; + break; + } + mode = InflateBlockMode.DRY; + goto case InflateBlockMode.DRY; + + case InflateBlockMode.DRY: + writeAt = q; + r = Flush(r); + q = writeAt; m = (int)(q < readAt ? readAt - q - 1 : end - q); + if (readAt != writeAt) + { + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + mode = InflateBlockMode.DONE; + goto case InflateBlockMode.DONE; + + case InflateBlockMode.DONE: + r = ZlibConstants.Z_STREAM_END; + bitb = b; + bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + + case InflateBlockMode.BAD: + r = ZlibConstants.Z_DATA_ERROR; + + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + + + default: + r = ZlibConstants.Z_STREAM_ERROR; + + bitb = b; bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + } + } + + + internal void Free() + { + Reset(); + window = null; + hufts = null; + } + + internal void SetDictionary(byte[] d, int start, int n) + { + Array.Copy(d, start, window, 0, n); + readAt = writeAt = n; + } + + // Returns true if inflate is currently at the end of a block generated + // by Z_SYNC_FLUSH or Z_FULL_FLUSH. + internal int SyncPoint() + { + return mode == InflateBlockMode.LENS ? 1 : 0; + } + + // copy as much as possible from the sliding window to the output area + internal int Flush(int r) + { + int nBytes; + + for (int pass=0; pass < 2; pass++) + { + if (pass==0) + { + // compute number of bytes to copy as far as end of window + nBytes = (int)((readAt <= writeAt ? writeAt : end) - readAt); + } + else + { + // compute bytes to copy + nBytes = writeAt - readAt; + } + + // workitem 8870 + if (nBytes == 0) + { + if (r == ZlibConstants.Z_BUF_ERROR) + r = ZlibConstants.Z_OK; + return r; + } + + if (nBytes > _codec.AvailableBytesOut) + nBytes = _codec.AvailableBytesOut; + + if (nBytes != 0 && r == ZlibConstants.Z_BUF_ERROR) + r = ZlibConstants.Z_OK; + + // update counters + _codec.AvailableBytesOut -= nBytes; + _codec.TotalBytesOut += nBytes; + + // update check information + if (checkfn != null) + _codec._Adler32 = check = Adler.Adler32(check, window, readAt, nBytes); + + // copy as far as end of window + Array.Copy(window, readAt, _codec.OutputBuffer, _codec.NextOut, nBytes); + _codec.NextOut += nBytes; + readAt += nBytes; + + // see if more to copy at beginning of window + if (readAt == end && pass == 0) + { + // wrap pointers + readAt = 0; + if (writeAt == end) + writeAt = 0; + } + else pass++; + } + + // done + return r; + } + } + + + internal static class InternalInflateConstants + { + // And'ing with mask[n] masks the lower n bits + internal static readonly int[] InflateMask = new int[] { + 0x00000000, 0x00000001, 0x00000003, 0x00000007, + 0x0000000f, 0x0000001f, 0x0000003f, 0x0000007f, + 0x000000ff, 0x000001ff, 0x000003ff, 0x000007ff, + 0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff }; + } + + + sealed class InflateCodes + { + // waiting for "i:"=input, + // "o:"=output, + // "x:"=nothing + private const int START = 0; // x: set up for LEN + private const int LEN = 1; // i: get length/literal/eob next + private const int LENEXT = 2; // i: getting length extra (have base) + private const int DIST = 3; // i: get distance next + private const int DISTEXT = 4; // i: getting distance extra + private const int COPY = 5; // o: copying bytes in window, waiting for space + private const int LIT = 6; // o: got literal, waiting for output space + private const int WASH = 7; // o: got eob, possibly still output waiting + private const int END = 8; // x: got eob and all data flushed + private const int BADCODE = 9; // x: got error + + internal int mode; // current inflate_codes mode + + // mode dependent information + internal int len; + + internal int[] tree; // pointer into tree + internal int tree_index = 0; + internal int need; // bits needed + + internal int lit; + + // if EXT or COPY, where and how much + internal int bitsToGet; // bits to get for extra + internal int dist; // distance back to copy from + + internal byte lbits; // ltree bits decoded per branch + internal byte dbits; // dtree bits decoder per branch + internal int[] ltree; // literal/length/eob tree + internal int ltree_index; // literal/length/eob tree + internal int[] dtree; // distance tree + internal int dtree_index; // distance tree + + internal InflateCodes() + { + } + + internal void Init(int bl, int bd, int[] tl, int tl_index, int[] td, int td_index) + { + mode = START; + lbits = (byte)bl; + dbits = (byte)bd; + ltree = tl; + ltree_index = tl_index; + dtree = td; + dtree_index = td_index; + tree = null; + } + + internal int Process(InflateBlocks blocks, int r) + { + int j; // temporary storage + int tindex; // temporary pointer + int e; // extra bits or operation + int b = 0; // bit buffer + int k = 0; // bits in bit buffer + int p = 0; // input data pointer + int n; // bytes available there + int q; // output window write pointer + int m; // bytes to end of window or read pointer + int f; // pointer to copy strings from + + ZlibCodec z = blocks._codec; + + // copy input/output information to locals (UPDATE macro restores) + p = z.NextIn; + n = z.AvailableBytesIn; + b = blocks.bitb; + k = blocks.bitk; + q = blocks.writeAt; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; + + // process input and output based on current state + while (true) + { + switch (mode) + { + // waiting for "i:"=input, "o:"=output, "x:"=nothing + case START: // x: set up for LEN + if (m >= 258 && n >= 10) + { + blocks.bitb = b; blocks.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + blocks.writeAt = q; + r = InflateFast(lbits, dbits, ltree, ltree_index, dtree, dtree_index, blocks, z); + + p = z.NextIn; + n = z.AvailableBytesIn; + b = blocks.bitb; + k = blocks.bitk; + q = blocks.writeAt; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; + + if (r != ZlibConstants.Z_OK) + { + mode = (r == ZlibConstants.Z_STREAM_END) ? WASH : BADCODE; + break; + } + } + need = lbits; + tree = ltree; + tree_index = ltree_index; + + mode = LEN; + goto case LEN; + + case LEN: // i: get length/literal/eob next + j = need; + + while (k < j) + { + if (n != 0) + r = ZlibConstants.Z_OK; + else + { + blocks.bitb = b; blocks.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); + } + n--; + b |= (z.InputBuffer[p++] & 0xff) << k; + k += 8; + } + + tindex = (tree_index + (b & InternalInflateConstants.InflateMask[j])) * 3; + + b >>= (tree[tindex + 1]); + k -= (tree[tindex + 1]); + + e = tree[tindex]; + + if (e == 0) + { + // literal + lit = tree[tindex + 2]; + mode = LIT; + break; + } + if ((e & 16) != 0) + { + // length + bitsToGet = e & 15; + len = tree[tindex + 2]; + mode = LENEXT; + break; + } + if ((e & 64) == 0) + { + // next table + need = e; + tree_index = tindex / 3 + tree[tindex + 2]; + break; + } + if ((e & 32) != 0) + { + // end of block + mode = WASH; + break; + } + mode = BADCODE; // invalid code + z.Message = "invalid literal/length code"; + r = ZlibConstants.Z_DATA_ERROR; + + blocks.bitb = b; blocks.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); + + + case LENEXT: // i: getting length extra (have base) + j = bitsToGet; + + while (k < j) + { + if (n != 0) + r = ZlibConstants.Z_OK; + else + { + blocks.bitb = b; blocks.bitk = k; + z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); + } + n--; b |= (z.InputBuffer[p++] & 0xff) << k; + k += 8; + } + + len += (b & InternalInflateConstants.InflateMask[j]); + + b >>= j; + k -= j; + + need = dbits; + tree = dtree; + tree_index = dtree_index; + mode = DIST; + goto case DIST; + + case DIST: // i: get distance next + j = need; + + while (k < j) + { + if (n != 0) + r = ZlibConstants.Z_OK; + else + { + blocks.bitb = b; blocks.bitk = k; + z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); + } + n--; b |= (z.InputBuffer[p++] & 0xff) << k; + k += 8; + } + + tindex = (tree_index + (b & InternalInflateConstants.InflateMask[j])) * 3; + + b >>= tree[tindex + 1]; + k -= tree[tindex + 1]; + + e = (tree[tindex]); + if ((e & 0x10) != 0) + { + // distance + bitsToGet = e & 15; + dist = tree[tindex + 2]; + mode = DISTEXT; + break; + } + if ((e & 64) == 0) + { + // next table + need = e; + tree_index = tindex / 3 + tree[tindex + 2]; + break; + } + mode = BADCODE; // invalid code + z.Message = "invalid distance code"; + r = ZlibConstants.Z_DATA_ERROR; + + blocks.bitb = b; blocks.bitk = k; + z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); + + + case DISTEXT: // i: getting distance extra + j = bitsToGet; + + while (k < j) + { + if (n != 0) + r = ZlibConstants.Z_OK; + else + { + blocks.bitb = b; blocks.bitk = k; + z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); + } + n--; b |= (z.InputBuffer[p++] & 0xff) << k; + k += 8; + } + + dist += (b & InternalInflateConstants.InflateMask[j]); + + b >>= j; + k -= j; + + mode = COPY; + goto case COPY; + + case COPY: // o: copying bytes in window, waiting for space + f = q - dist; + while (f < 0) + { + // modulo window size-"while" instead + f += blocks.end; // of "if" handles invalid distances + } + while (len != 0) + { + if (m == 0) + { + if (q == blocks.end && blocks.readAt != 0) + { + q = 0; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; + } + if (m == 0) + { + blocks.writeAt = q; r = blocks.Flush(r); + q = blocks.writeAt; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; + + if (q == blocks.end && blocks.readAt != 0) + { + q = 0; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; + } + + if (m == 0) + { + blocks.bitb = b; blocks.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); + } + } + } + + blocks.window[q++] = blocks.window[f++]; m--; + + if (f == blocks.end) + f = 0; + len--; + } + mode = START; + break; + + case LIT: // o: got literal, waiting for output space + if (m == 0) + { + if (q == blocks.end && blocks.readAt != 0) + { + q = 0; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; + } + if (m == 0) + { + blocks.writeAt = q; r = blocks.Flush(r); + q = blocks.writeAt; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; + + if (q == blocks.end && blocks.readAt != 0) + { + q = 0; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; + } + if (m == 0) + { + blocks.bitb = b; blocks.bitk = k; + z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); + } + } + } + r = ZlibConstants.Z_OK; + + blocks.window[q++] = (byte)lit; m--; + + mode = START; + break; + + case WASH: // o: got eob, possibly more output + if (k > 7) + { + // return unused byte, if any + k -= 8; + n++; + p--; // can always return one + } + + blocks.writeAt = q; r = blocks.Flush(r); + q = blocks.writeAt; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; + + if (blocks.readAt != blocks.writeAt) + { + blocks.bitb = b; blocks.bitk = k; + z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); + } + mode = END; + goto case END; + + case END: + r = ZlibConstants.Z_STREAM_END; + blocks.bitb = b; blocks.bitk = k; + z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); + + case BADCODE: // x: got error + + r = ZlibConstants.Z_DATA_ERROR; + + blocks.bitb = b; blocks.bitk = k; + z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); + + default: + r = ZlibConstants.Z_STREAM_ERROR; + + blocks.bitb = b; blocks.bitk = k; + z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); + } + } + } + + + // Called with number of bytes left to write in window at least 258 + // (the maximum string length) and number of input bytes available + // at least ten. The ten bytes are six bytes for the longest length/ + // distance pair plus four bytes for overloading the bit buffer. + + internal int InflateFast(int bl, int bd, int[] tl, int tl_index, int[] td, int td_index, InflateBlocks s, ZlibCodec z) + { + int t; // temporary pointer + int[] tp; // temporary pointer + int tp_index; // temporary pointer + int e; // extra bits or operation + int b; // bit buffer + int k; // bits in bit buffer + int p; // input data pointer + int n; // bytes available there + int q; // output window write pointer + int m; // bytes to end of window or read pointer + int ml; // mask for literal/length tree + int md; // mask for distance tree + int c; // bytes to copy + int d; // distance back to copy from + int r; // copy source pointer + + int tp_index_t_3; // (tp_index+t)*3 + + // load input, output, bit values + p = z.NextIn; n = z.AvailableBytesIn; b = s.bitb; k = s.bitk; + q = s.writeAt; m = q < s.readAt ? s.readAt - q - 1 : s.end - q; + + // initialize masks + ml = InternalInflateConstants.InflateMask[bl]; + md = InternalInflateConstants.InflateMask[bd]; + + // do until not enough input or output space for fast loop + do + { + // assume called with m >= 258 && n >= 10 + // get literal/length code + while (k < (20)) + { + // max bits for literal/length code + n--; + b |= (z.InputBuffer[p++] & 0xff) << k; k += 8; + } + + t = b & ml; + tp = tl; + tp_index = tl_index; + tp_index_t_3 = (tp_index + t) * 3; + if ((e = tp[tp_index_t_3]) == 0) + { + b >>= (tp[tp_index_t_3 + 1]); k -= (tp[tp_index_t_3 + 1]); + + s.window[q++] = (byte)tp[tp_index_t_3 + 2]; + m--; + continue; + } + do + { + + b >>= (tp[tp_index_t_3 + 1]); k -= (tp[tp_index_t_3 + 1]); + + if ((e & 16) != 0) + { + e &= 15; + c = tp[tp_index_t_3 + 2] + ((int)b & InternalInflateConstants.InflateMask[e]); + + b >>= e; k -= e; + + // decode distance base of block to copy + while (k < 15) + { + // max bits for distance code + n--; + b |= (z.InputBuffer[p++] & 0xff) << k; k += 8; + } + + t = b & md; + tp = td; + tp_index = td_index; + tp_index_t_3 = (tp_index + t) * 3; + e = tp[tp_index_t_3]; + + do + { + + b >>= (tp[tp_index_t_3 + 1]); k -= (tp[tp_index_t_3 + 1]); + + if ((e & 16) != 0) + { + // get extra bits to add to distance base + e &= 15; + while (k < e) + { + // get extra bits (up to 13) + n--; + b |= (z.InputBuffer[p++] & 0xff) << k; k += 8; + } + + d = tp[tp_index_t_3 + 2] + (b & InternalInflateConstants.InflateMask[e]); + + b >>= e; k -= e; + + // do the copy + m -= c; + if (q >= d) + { + // offset before dest + // just copy + r = q - d; + if (q - r > 0 && 2 > (q - r)) + { + s.window[q++] = s.window[r++]; // minimum count is three, + s.window[q++] = s.window[r++]; // so unroll loop a little + c -= 2; + } + else + { + Array.Copy(s.window, r, s.window, q, 2); + q += 2; r += 2; c -= 2; + } + } + else + { + // else offset after destination + r = q - d; + do + { + r += s.end; // force pointer in window + } + while (r < 0); // covers invalid distances + e = s.end - r; + if (c > e) + { + // if source crosses, + c -= e; // wrapped copy + if (q - r > 0 && e > (q - r)) + { + do + { + s.window[q++] = s.window[r++]; + } + while (--e != 0); + } + else + { + Array.Copy(s.window, r, s.window, q, e); + q += e; r += e; e = 0; + } + r = 0; // copy rest from start of window + } + } + + // copy all or what's left + if (q - r > 0 && c > (q - r)) + { + do + { + s.window[q++] = s.window[r++]; + } + while (--c != 0); + } + else + { + Array.Copy(s.window, r, s.window, q, c); + q += c; r += c; c = 0; + } + break; + } + else if ((e & 64) == 0) + { + t += tp[tp_index_t_3 + 2]; + t += (b & InternalInflateConstants.InflateMask[e]); + tp_index_t_3 = (tp_index + t) * 3; + e = tp[tp_index_t_3]; + } + else + { + z.Message = "invalid distance code"; + + c = z.AvailableBytesIn - n; c = (k >> 3) < c ? k >> 3 : c; n += c; p -= c; k -= (c << 3); + + s.bitb = b; s.bitk = k; + z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p; + s.writeAt = q; + + return ZlibConstants.Z_DATA_ERROR; + } + } + while (true); + break; + } + + if ((e & 64) == 0) + { + t += tp[tp_index_t_3 + 2]; + t += (b & InternalInflateConstants.InflateMask[e]); + tp_index_t_3 = (tp_index + t) * 3; + if ((e = tp[tp_index_t_3]) == 0) + { + b >>= (tp[tp_index_t_3 + 1]); k -= (tp[tp_index_t_3 + 1]); + s.window[q++] = (byte)tp[tp_index_t_3 + 2]; + m--; + break; + } + } + else if ((e & 32) != 0) + { + c = z.AvailableBytesIn - n; c = (k >> 3) < c ? k >> 3 : c; n += c; p -= c; k -= (c << 3); + + s.bitb = b; s.bitk = k; + z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p; + s.writeAt = q; + + return ZlibConstants.Z_STREAM_END; + } + else + { + z.Message = "invalid literal/length code"; + + c = z.AvailableBytesIn - n; c = (k >> 3) < c ? k >> 3 : c; n += c; p -= c; k -= (c << 3); + + s.bitb = b; s.bitk = k; + z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p; + s.writeAt = q; + + return ZlibConstants.Z_DATA_ERROR; + } + } + while (true); + } + while (m >= 258 && n >= 10); + + // not enough input or output--restore pointers and return + c = z.AvailableBytesIn - n; c = (k >> 3) < c ? k >> 3 : c; n += c; p -= c; k -= (c << 3); + + s.bitb = b; s.bitk = k; + z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p; + s.writeAt = q; + + return ZlibConstants.Z_OK; + } + } + + + internal sealed class InflateManager + { + // preset dictionary flag in zlib header + private const int PRESET_DICT = 0x20; + + private const int Z_DEFLATED = 8; + + private enum InflateManagerMode + { + METHOD = 0, // waiting for method byte + FLAG = 1, // waiting for flag byte + DICT4 = 2, // four dictionary check bytes to go + DICT3 = 3, // three dictionary check bytes to go + DICT2 = 4, // two dictionary check bytes to go + DICT1 = 5, // one dictionary check byte to go + DICT0 = 6, // waiting for inflateSetDictionary + BLOCKS = 7, // decompressing blocks + CHECK4 = 8, // four check bytes to go + CHECK3 = 9, // three check bytes to go + CHECK2 = 10, // two check bytes to go + CHECK1 = 11, // one check byte to go + DONE = 12, // finished check, done + BAD = 13, // got an error--stay here + } + + private InflateManagerMode mode; // current inflate mode + internal ZlibCodec _codec; // pointer back to this zlib stream + + // mode dependent information + internal int method; // if FLAGS, method byte + + // if CHECK, check values to compare + internal uint computedCheck; // computed check value + internal uint expectedCheck; // stream check value + + // if BAD, inflateSync's marker bytes count + internal int marker; + + // mode independent information + //internal int nowrap; // flag for no wrapper + private bool _handleRfc1950HeaderBytes = true; + internal bool HandleRfc1950HeaderBytes + { + get { return _handleRfc1950HeaderBytes; } + set { _handleRfc1950HeaderBytes = value; } + } + internal int wbits; // log2(window size) (8..15, defaults to 15) + + internal InflateBlocks blocks; // current inflate_blocks state + + public InflateManager() { } + + public InflateManager(bool expectRfc1950HeaderBytes) + { + _handleRfc1950HeaderBytes = expectRfc1950HeaderBytes; + } + + internal int Reset() + { + _codec.TotalBytesIn = _codec.TotalBytesOut = 0; + _codec.Message = null; + mode = HandleRfc1950HeaderBytes ? InflateManagerMode.METHOD : InflateManagerMode.BLOCKS; + blocks.Reset(); + return ZlibConstants.Z_OK; + } + + internal int End() + { + if (blocks != null) + blocks.Free(); + blocks = null; + return ZlibConstants.Z_OK; + } + + internal int Initialize(ZlibCodec codec, int w) + { + _codec = codec; + _codec.Message = null; + blocks = null; + + // handle undocumented nowrap option (no zlib header or check) + //nowrap = 0; + //if (w < 0) + //{ + // w = - w; + // nowrap = 1; + //} + + // set window size + if (w < 8 || w > 15) + { + End(); + throw new ZlibException("Bad window size."); + + //return ZlibConstants.Z_STREAM_ERROR; + } + wbits = w; + + blocks = new InflateBlocks(codec, + HandleRfc1950HeaderBytes ? this : null, + 1 << w); + + // reset state + Reset(); + return ZlibConstants.Z_OK; + } + + + internal int Inflate(FlushType flush) + { + int b; + + if (_codec.InputBuffer == null) + throw new ZlibException("InputBuffer is null. "); + +// int f = (flush == FlushType.Finish) +// ? ZlibConstants.Z_BUF_ERROR +// : ZlibConstants.Z_OK; + + // workitem 8870 + int f = ZlibConstants.Z_OK; + int r = ZlibConstants.Z_BUF_ERROR; + + while (true) + { + switch (mode) + { + case InflateManagerMode.METHOD: + if (_codec.AvailableBytesIn == 0) return r; + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + if (((method = _codec.InputBuffer[_codec.NextIn++]) & 0xf) != Z_DEFLATED) + { + mode = InflateManagerMode.BAD; + _codec.Message = String.Format("unknown compression method (0x{0:X2})", method); + marker = 5; // can't try inflateSync + break; + } + if ((method >> 4) + 8 > wbits) + { + mode = InflateManagerMode.BAD; + _codec.Message = String.Format("invalid window size ({0})", (method >> 4) + 8); + marker = 5; // can't try inflateSync + break; + } + mode = InflateManagerMode.FLAG; + break; + + + case InflateManagerMode.FLAG: + if (_codec.AvailableBytesIn == 0) return r; + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + b = (_codec.InputBuffer[_codec.NextIn++]) & 0xff; + + if ((((method << 8) + b) % 31) != 0) + { + mode = InflateManagerMode.BAD; + _codec.Message = "incorrect header check"; + marker = 5; // can't try inflateSync + break; + } + + mode = ((b & PRESET_DICT) == 0) + ? InflateManagerMode.BLOCKS + : InflateManagerMode.DICT4; + break; + + case InflateManagerMode.DICT4: + if (_codec.AvailableBytesIn == 0) return r; + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + expectedCheck = (uint)((_codec.InputBuffer[_codec.NextIn++] << 24) & 0xff000000); + mode = InflateManagerMode.DICT3; + break; + + case InflateManagerMode.DICT3: + if (_codec.AvailableBytesIn == 0) return r; + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + expectedCheck += (uint)((_codec.InputBuffer[_codec.NextIn++] << 16) & 0x00ff0000); + mode = InflateManagerMode.DICT2; + break; + + case InflateManagerMode.DICT2: + + if (_codec.AvailableBytesIn == 0) return r; + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + expectedCheck += (uint)((_codec.InputBuffer[_codec.NextIn++] << 8) & 0x0000ff00); + mode = InflateManagerMode.DICT1; + break; + + + case InflateManagerMode.DICT1: + if (_codec.AvailableBytesIn == 0) return r; + r = f; + _codec.AvailableBytesIn--; _codec.TotalBytesIn++; + expectedCheck += (uint)(_codec.InputBuffer[_codec.NextIn++] & 0x000000ff); + _codec._Adler32 = expectedCheck; + mode = InflateManagerMode.DICT0; + return ZlibConstants.Z_NEED_DICT; + + + case InflateManagerMode.DICT0: + mode = InflateManagerMode.BAD; + _codec.Message = "need dictionary"; + marker = 0; // can try inflateSync + return ZlibConstants.Z_STREAM_ERROR; + + + case InflateManagerMode.BLOCKS: + r = blocks.Process(r); + if (r == ZlibConstants.Z_DATA_ERROR) + { + mode = InflateManagerMode.BAD; + marker = 0; // can try inflateSync + break; + } + + if (r == ZlibConstants.Z_OK) r = f; + + if (r != ZlibConstants.Z_STREAM_END) + return r; + + r = f; + computedCheck = blocks.Reset(); + if (!HandleRfc1950HeaderBytes) + { + mode = InflateManagerMode.DONE; + return ZlibConstants.Z_STREAM_END; + } + mode = InflateManagerMode.CHECK4; + break; + + case InflateManagerMode.CHECK4: + if (_codec.AvailableBytesIn == 0) return r; + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + expectedCheck = (uint)((_codec.InputBuffer[_codec.NextIn++] << 24) & 0xff000000); + mode = InflateManagerMode.CHECK3; + break; + + case InflateManagerMode.CHECK3: + if (_codec.AvailableBytesIn == 0) return r; + r = f; + _codec.AvailableBytesIn--; _codec.TotalBytesIn++; + expectedCheck += (uint)((_codec.InputBuffer[_codec.NextIn++] << 16) & 0x00ff0000); + mode = InflateManagerMode.CHECK2; + break; + + case InflateManagerMode.CHECK2: + if (_codec.AvailableBytesIn == 0) return r; + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + expectedCheck += (uint)((_codec.InputBuffer[_codec.NextIn++] << 8) & 0x0000ff00); + mode = InflateManagerMode.CHECK1; + break; + + case InflateManagerMode.CHECK1: + if (_codec.AvailableBytesIn == 0) return r; + r = f; + _codec.AvailableBytesIn--; _codec.TotalBytesIn++; + expectedCheck += (uint)(_codec.InputBuffer[_codec.NextIn++] & 0x000000ff); + if (computedCheck != expectedCheck) + { + mode = InflateManagerMode.BAD; + _codec.Message = "incorrect data check"; + marker = 5; // can't try inflateSync + break; + } + mode = InflateManagerMode.DONE; + return ZlibConstants.Z_STREAM_END; + + case InflateManagerMode.DONE: + return ZlibConstants.Z_STREAM_END; + + case InflateManagerMode.BAD: + throw new ZlibException(String.Format("Bad state ({0})", _codec.Message)); + + default: + throw new ZlibException("Stream error."); + + } + } + } + + + + internal int SetDictionary(byte[] dictionary) + { + int index = 0; + int length = dictionary.Length; + if (mode != InflateManagerMode.DICT0) + throw new ZlibException("Stream error."); + + if (Adler.Adler32(1, dictionary, 0, dictionary.Length) != _codec._Adler32) + { + return ZlibConstants.Z_DATA_ERROR; + } + + _codec._Adler32 = Adler.Adler32(0, null, 0, 0); + + if (length >= (1 << wbits)) + { + length = (1 << wbits) - 1; + index = dictionary.Length - length; + } + blocks.SetDictionary(dictionary, index, length); + mode = InflateManagerMode.BLOCKS; + return ZlibConstants.Z_OK; + } + + + private static readonly byte[] mark = new byte[] { 0, 0, 0xff, 0xff }; + + internal int Sync() + { + int n; // number of bytes to look at + int p; // pointer to bytes + int m; // number of marker bytes found in a row + long r, w; // temporaries to save total_in and total_out + + // set up + if (mode != InflateManagerMode.BAD) + { + mode = InflateManagerMode.BAD; + marker = 0; + } + if ((n = _codec.AvailableBytesIn) == 0) + return ZlibConstants.Z_BUF_ERROR; + p = _codec.NextIn; + m = marker; + + // search + while (n != 0 && m < 4) + { + if (_codec.InputBuffer[p] == mark[m]) + { + m++; + } + else if (_codec.InputBuffer[p] != 0) + { + m = 0; + } + else + { + m = 4 - m; + } + p++; n--; + } + + // restore + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + _codec.AvailableBytesIn = n; + marker = m; + + // return no joy or set up to restart on a new block + if (m != 4) + { + return ZlibConstants.Z_DATA_ERROR; + } + r = _codec.TotalBytesIn; + w = _codec.TotalBytesOut; + Reset(); + _codec.TotalBytesIn = r; + _codec.TotalBytesOut = w; + mode = InflateManagerMode.BLOCKS; + return ZlibConstants.Z_OK; + } + + + // Returns true if inflate is currently at the end of a block generated + // by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + // implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH + // but removes the length bytes of the resulting empty stored block. When + // decompressing, PPP checks that at the end of input packet, inflate is + // waiting for these length bytes. + internal int SyncPoint(ZlibCodec z) + { + return blocks.SyncPoint(); + } + } +} \ No newline at end of file diff --git a/src/ZlibAndroidSdkStyle/Tree.cs b/src/ZlibAndroidSdkStyle/Tree.cs new file mode 100644 index 00000000..1db8c4f4 --- /dev/null +++ b/src/ZlibAndroidSdkStyle/Tree.cs @@ -0,0 +1,423 @@ +// Tree.cs +// ------------------------------------------------------------------ +// +// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. +// All rights reserved. +// +// This code module is part of DotNetZip, a zipfile class library. +// +// ------------------------------------------------------------------ +// +// This code is licensed under the Microsoft Public License. +// See the file License.txt for the license details. +// More info on: http://dotnetzip.codeplex.com +// +// ------------------------------------------------------------------ +// +// last saved (in emacs): +// Time-stamp: <2009-October-28 13:29:50> +// +// ------------------------------------------------------------------ +// +// This module defines classes for zlib compression and +// decompression. This code is derived from the jzlib implementation of +// zlib. In keeping with the license for jzlib, the copyright to that +// code is below. +// +// ------------------------------------------------------------------ +// +// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the distribution. +// +// 3. The names of the authors may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------- +// +// This program is based on zlib-1.1.3; credit to authors +// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) +// and contributors of zlib. +// +// ----------------------------------------------------------------------- + + +using System; + +namespace Ionic.Zlib +{ + sealed class Tree + { + private static readonly int HEAP_SIZE = (2 * InternalConstants.L_CODES + 1); + + // extra bits for each length code + internal static readonly int[] ExtraLengthBits = new int[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 + }; + + // extra bits for each distance code + internal static readonly int[] ExtraDistanceBits = new int[] + { + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 + }; + + // extra bits for each bit length code + internal static readonly int[] extra_blbits = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7}; + + internal static readonly sbyte[] bl_order = new sbyte[]{16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + + // The lengths of the bit length codes are sent in order of decreasing + // probability, to avoid transmitting the lengths for unused bit + // length codes. + + internal const int Buf_size = 8 * 2; + + // see definition of array dist_code below + //internal const int DIST_CODE_LEN = 512; + + private static readonly sbyte[] _dist_code = new sbyte[] + { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 0, 0, 16, 17, 18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 + }; + + internal static readonly sbyte[] LengthCode = new sbyte[] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 + }; + + + internal static readonly int[] LengthBase = new int[] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, + 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 0 + }; + + + internal static readonly int[] DistanceBase = new int[] + { + 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, + 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576 + }; + + + /// + /// Map from a distance to a distance code. + /// + /// + /// No side effects. _dist_code[256] and _dist_code[257] are never used. + /// + internal static int DistanceCode(int dist) + { + return (dist < 256) + ? _dist_code[dist] + : _dist_code[256 + SharedUtils.URShift(dist, 7)]; + } + + internal short[] dyn_tree; // the dynamic tree + internal int max_code; // largest code with non zero frequency + internal StaticTree staticTree; // the corresponding static tree + + // Compute the optimal bit lengths for a tree and update the total bit length + // for the current block. + // IN assertion: the fields freq and dad are set, heap[heap_max] and + // above are the tree nodes sorted by increasing frequency. + // OUT assertions: the field len is set to the optimal bit length, the + // array bl_count contains the frequencies for each bit length. + // The length opt_len is updated; static_len is also updated if stree is + // not null. + internal void gen_bitlen(DeflateManager s) + { + short[] tree = dyn_tree; + short[] stree = staticTree.treeCodes; + int[] extra = staticTree.extraBits; + int base_Renamed = staticTree.extraBase; + int max_length = staticTree.maxLength; + int h; // heap index + int n, m; // iterate over the tree elements + int bits; // bit length + int xbits; // extra bits + short f; // frequency + int overflow = 0; // number of elements with bit length too large + + for (bits = 0; bits <= InternalConstants.MAX_BITS; bits++) + s.bl_count[bits] = 0; + + // In a first pass, compute the optimal bit lengths (which may + // overflow in the case of the bit length tree). + tree[s.heap[s.heap_max] * 2 + 1] = 0; // root of the heap + + for (h = s.heap_max + 1; h < HEAP_SIZE; h++) + { + n = s.heap[h]; + bits = tree[tree[n * 2 + 1] * 2 + 1] + 1; + if (bits > max_length) + { + bits = max_length; overflow++; + } + tree[n * 2 + 1] = (short) bits; + // We overwrite tree[n*2+1] which is no longer needed + + if (n > max_code) + continue; // not a leaf node + + s.bl_count[bits]++; + xbits = 0; + if (n >= base_Renamed) + xbits = extra[n - base_Renamed]; + f = tree[n * 2]; + s.opt_len += f * (bits + xbits); + if (stree != null) + s.static_len += f * (stree[n * 2 + 1] + xbits); + } + if (overflow == 0) + return ; + + // This happens for example on obj2 and pic of the Calgary corpus + // Find the first bit length which could increase: + do + { + bits = max_length - 1; + while (s.bl_count[bits] == 0) + bits--; + s.bl_count[bits]--; // move one leaf down the tree + s.bl_count[bits + 1] = (short) (s.bl_count[bits + 1] + 2); // move one overflow item as its brother + s.bl_count[max_length]--; + // The brother of the overflow item also moves one step up, + // but this does not affect bl_count[max_length] + overflow -= 2; + } + while (overflow > 0); + + for (bits = max_length; bits != 0; bits--) + { + n = s.bl_count[bits]; + while (n != 0) + { + m = s.heap[--h]; + if (m > max_code) + continue; + if (tree[m * 2 + 1] != bits) + { + s.opt_len = (int) (s.opt_len + ((long) bits - (long) tree[m * 2 + 1]) * (long) tree[m * 2]); + tree[m * 2 + 1] = (short) bits; + } + n--; + } + } + } + + // Construct one Huffman tree and assigns the code bit strings and lengths. + // Update the total bit length for the current block. + // IN assertion: the field freq is set for all tree elements. + // OUT assertions: the fields len and code are set to the optimal bit length + // and corresponding code. The length opt_len is updated; static_len is + // also updated if stree is not null. The field max_code is set. + internal void build_tree(DeflateManager s) + { + short[] tree = dyn_tree; + short[] stree = staticTree.treeCodes; + int elems = staticTree.elems; + int n, m; // iterate over heap elements + int max_code = -1; // largest code with non zero frequency + int node; // new node being created + + // Construct the initial heap, with least frequent element in + // heap[1]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + // heap[0] is not used. + s.heap_len = 0; + s.heap_max = HEAP_SIZE; + + for (n = 0; n < elems; n++) + { + if (tree[n * 2] != 0) + { + s.heap[++s.heap_len] = max_code = n; + s.depth[n] = 0; + } + else + { + tree[n * 2 + 1] = 0; + } + } + + // The pkzip format requires that at least one distance code exists, + // and that at least one bit should be sent even if there is only one + // possible code. So to avoid special checks later on we force at least + // two codes of non zero frequency. + while (s.heap_len < 2) + { + node = s.heap[++s.heap_len] = (max_code < 2?++max_code:0); + tree[node * 2] = 1; + s.depth[node] = 0; + s.opt_len--; + if (stree != null) + s.static_len -= stree[node * 2 + 1]; + // node is 0 or 1 so it does not have extra bits + } + this.max_code = max_code; + + // The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + // establish sub-heaps of increasing lengths: + + for (n = s.heap_len / 2; n >= 1; n--) + s.pqdownheap(tree, n); + + // Construct the Huffman tree by repeatedly combining the least two + // frequent nodes. + + node = elems; // next internal node of the tree + do + { + // n = node of least frequency + n = s.heap[1]; + s.heap[1] = s.heap[s.heap_len--]; + s.pqdownheap(tree, 1); + m = s.heap[1]; // m = node of next least frequency + + s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency + s.heap[--s.heap_max] = m; + + // Create a new node father of n and m + tree[node * 2] = unchecked((short) (tree[n * 2] + tree[m * 2])); + s.depth[node] = (sbyte) (System.Math.Max((byte) s.depth[n], (byte) s.depth[m]) + 1); + tree[n * 2 + 1] = tree[m * 2 + 1] = (short) node; + + // and insert the new node in the heap + s.heap[1] = node++; + s.pqdownheap(tree, 1); + } + while (s.heap_len >= 2); + + s.heap[--s.heap_max] = s.heap[1]; + + // At this point, the fields freq and dad are set. We can now + // generate the bit lengths. + + gen_bitlen(s); + + // The field len is now set, we can generate the bit codes + gen_codes(tree, max_code, s.bl_count); + } + + // Generate the codes for a given tree and bit counts (which need not be + // optimal). + // IN assertion: the array bl_count contains the bit length statistics for + // the given tree and the field len is set for all tree elements. + // OUT assertion: the field code is set for all tree elements of non + // zero code length. + internal static void gen_codes(short[] tree, int max_code, short[] bl_count) + { + short[] next_code = new short[InternalConstants.MAX_BITS + 1]; // next code value for each bit length + short code = 0; // running code value + int bits; // bit index + int n; // code index + + // The distribution counts are first used to generate the code values + // without bit reversal. + for (bits = 1; bits <= InternalConstants.MAX_BITS; bits++) + unchecked { + next_code[bits] = code = (short) ((code + bl_count[bits - 1]) << 1); + } + + // Check that the bit counts in bl_count are consistent. The last code + // must be all ones. + //Assert (code + bl_count[MAX_BITS]-1 == (1<>= 1; //SharedUtils.URShift(code, 1); + res <<= 1; + } + while (--len > 0); + return res >> 1; + } + } +} \ No newline at end of file diff --git a/src/ZlibAndroidSdkStyle/Zlib.cs b/src/ZlibAndroidSdkStyle/Zlib.cs new file mode 100644 index 00000000..dcfe7252 --- /dev/null +++ b/src/ZlibAndroidSdkStyle/Zlib.cs @@ -0,0 +1,546 @@ +// Zlib.cs +// ------------------------------------------------------------------ +// +// Copyright (c) 2009-2011 Dino Chiesa and Microsoft Corporation. +// All rights reserved. +// +// This code module is part of DotNetZip, a zipfile class library. +// +// ------------------------------------------------------------------ +// +// This code is licensed under the Microsoft Public License. +// See the file License.txt for the license details. +// More info on: http://dotnetzip.codeplex.com +// +// ------------------------------------------------------------------ +// +// Last Saved: <2011-August-03 19:52:28> +// +// ------------------------------------------------------------------ +// +// This module defines classes for ZLIB compression and +// decompression. This code is derived from the jzlib implementation of +// zlib, but significantly modified. The object model is not the same, +// and many of the behaviors are new or different. Nonetheless, in +// keeping with the license for jzlib, the copyright to that code is +// included below. +// +// ------------------------------------------------------------------ +// +// The following notice applies to jzlib: +// +// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the distribution. +// +// 3. The names of the authors may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------- +// +// jzlib is based on zlib-1.1.3. +// +// The following notice applies to zlib: +// +// ----------------------------------------------------------------------- +// +// Copyright (C) 1995-2004 Jean-loup Gailly and Mark Adler +// +// The ZLIB software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// +// Jean-loup Gailly jloup@gzip.org +// Mark Adler madler@alumni.caltech.edu +// +// ----------------------------------------------------------------------- + + + +using System; +using Interop=System.Runtime.InteropServices; + +namespace Ionic.Zlib +{ + + /// + /// Describes how to flush the current deflate operation. + /// + /// + /// The different FlushType values are useful when using a Deflate in a streaming application. + /// + public enum FlushType + { + /// No flush at all. + None = 0, + + /// Closes the current block, but doesn't flush it to + /// the output. Used internally only in hypothetical + /// scenarios. This was supposed to be removed by Zlib, but it is + /// still in use in some edge cases. + /// + Partial, + + /// + /// Use this during compression to specify that all pending output should be + /// flushed to the output buffer and the output should be aligned on a byte + /// boundary. You might use this in a streaming communication scenario, so that + /// the decompressor can get all input data available so far. When using this + /// with a ZlibCodec, AvailableBytesIn will be zero after the call if + /// enough output space has been provided before the call. Flushing will + /// degrade compression and so it should be used only when necessary. + /// + Sync, + + /// + /// Use this during compression to specify that all output should be flushed, as + /// with FlushType.Sync, but also, the compression state should be reset + /// so that decompression can restart from this point if previous compressed + /// data has been damaged or if random access is desired. Using + /// FlushType.Full too often can significantly degrade the compression. + /// + Full, + + /// Signals the end of the compression/decompression stream. + Finish, + } + + + /// + /// The compression level to be used when using a DeflateStream or ZlibStream with CompressionMode.Compress. + /// + public enum CompressionLevel + { + /// + /// None means that the data will be simply stored, with no change at all. + /// If you are producing ZIPs for use on Mac OSX, be aware that archives produced with CompressionLevel.None + /// cannot be opened with the default zip reader. Use a different CompressionLevel. + /// + None= 0, + /// + /// Same as None. + /// + Level0 = 0, + + /// + /// The fastest but least effective compression. + /// + BestSpeed = 1, + + /// + /// A synonym for BestSpeed. + /// + Level1 = 1, + + /// + /// A little slower, but better, than level 1. + /// + Level2 = 2, + + /// + /// A little slower, but better, than level 2. + /// + Level3 = 3, + + /// + /// A little slower, but better, than level 3. + /// + Level4 = 4, + + /// + /// A little slower than level 4, but with better compression. + /// + Level5 = 5, + + /// + /// The default compression level, with a good balance of speed and compression efficiency. + /// + Default = 6, + /// + /// A synonym for Default. + /// + Level6 = 6, + + /// + /// Pretty good compression! + /// + Level7 = 7, + + /// + /// Better compression than Level7! + /// + Level8 = 8, + + /// + /// The "best" compression, where best means greatest reduction in size of the input data stream. + /// This is also the slowest compression. + /// + BestCompression = 9, + + /// + /// A synonym for BestCompression. + /// + Level9 = 9, + } + + /// + /// Describes options for how the compression algorithm is executed. Different strategies + /// work better on different sorts of data. The strategy parameter can affect the compression + /// ratio and the speed of compression but not the correctness of the compresssion. + /// + public enum CompressionStrategy + { + /// + /// The default strategy is probably the best for normal data. + /// + Default = 0, + + /// + /// The Filtered strategy is intended to be used most effectively with data produced by a + /// filter or predictor. By this definition, filtered data consists mostly of small + /// values with a somewhat random distribution. In this case, the compression algorithm + /// is tuned to compress them better. The effect of Filtered is to force more Huffman + /// coding and less string matching; it is a half-step between Default and HuffmanOnly. + /// + Filtered = 1, + + /// + /// Using HuffmanOnly will force the compressor to do Huffman encoding only, with no + /// string matching. + /// + HuffmanOnly = 2, + } + + + /// + /// An enum to specify the direction of transcoding - whether to compress or decompress. + /// + public enum CompressionMode + { + /// + /// Used to specify that the stream should compress the data. + /// + Compress= 0, + /// + /// Used to specify that the stream should decompress the data. + /// + Decompress = 1, + } + + + /// + /// A general purpose exception class for exceptions in the Zlib library. + /// + [Interop.GuidAttribute("ebc25cf6-9120-4283-b972-0e5520d0000E")] + public class ZlibException : System.Exception + { + /// + /// The ZlibException class captures exception information generated + /// by the Zlib library. + /// + public ZlibException() + : base() + { + } + + /// + /// This ctor collects a message attached to the exception. + /// + /// the message for the exception. + public ZlibException(System.String s) + : base(s) + { + } + } + + + internal class SharedUtils + { + /// + /// Performs an unsigned bitwise right shift with the specified number + /// + /// Number to operate on + /// Ammount of bits to shift + /// The resulting number from the shift operation + public static int URShift(int number, int bits) + { + return (int)((uint)number >> bits); + } + +#if NOT + /// + /// Performs an unsigned bitwise right shift with the specified number + /// + /// Number to operate on + /// Ammount of bits to shift + /// The resulting number from the shift operation + public static long URShift(long number, int bits) + { + return (long) ((UInt64)number >> bits); + } +#endif + + /// + /// Reads a number of characters from the current source TextReader and writes + /// the data to the target array at the specified index. + /// + /// + /// The source TextReader to read from + /// Contains the array of characteres read from the source TextReader. + /// The starting index of the target array. + /// The maximum number of characters to read from the source TextReader. + /// + /// + /// The number of characters read. The number will be less than or equal to + /// count depending on the data available in the source TextReader. Returns -1 + /// if the end of the stream is reached. + /// + public static System.Int32 ReadInput(System.IO.TextReader sourceTextReader, byte[] target, int start, int count) + { + // Returns 0 bytes if not enough space in target + if (target.Length == 0) return 0; + + char[] charArray = new char[target.Length]; + int bytesRead = sourceTextReader.Read(charArray, start, count); + + // Returns -1 if EOF + if (bytesRead == 0) return -1; + + for (int index = start; index < start + bytesRead; index++) + target[index] = (byte)charArray[index]; + + return bytesRead; + } + + + internal static byte[] ToByteArray(System.String sourceString) + { + return System.Text.UTF8Encoding.UTF8.GetBytes(sourceString); + } + + + internal static char[] ToCharArray(byte[] byteArray) + { + return System.Text.UTF8Encoding.UTF8.GetChars(byteArray); + } + } + + internal static class InternalConstants + { + internal static readonly int MAX_BITS = 15; + internal static readonly int BL_CODES = 19; + internal static readonly int D_CODES = 30; + internal static readonly int LITERALS = 256; + internal static readonly int LENGTH_CODES = 29; + internal static readonly int L_CODES = (LITERALS + 1 + LENGTH_CODES); + + // Bit length codes must not exceed MAX_BL_BITS bits + internal static readonly int MAX_BL_BITS = 7; + + // repeat previous bit length 3-6 times (2 bits of repeat count) + internal static readonly int REP_3_6 = 16; + + // repeat a zero length 3-10 times (3 bits of repeat count) + internal static readonly int REPZ_3_10 = 17; + + // repeat a zero length 11-138 times (7 bits of repeat count) + internal static readonly int REPZ_11_138 = 18; + + } + + internal sealed class StaticTree + { + internal static readonly short[] lengthAndLiteralsTreeCodes = new short[] { + 12, 8, 140, 8, 76, 8, 204, 8, 44, 8, 172, 8, 108, 8, 236, 8, + 28, 8, 156, 8, 92, 8, 220, 8, 60, 8, 188, 8, 124, 8, 252, 8, + 2, 8, 130, 8, 66, 8, 194, 8, 34, 8, 162, 8, 98, 8, 226, 8, + 18, 8, 146, 8, 82, 8, 210, 8, 50, 8, 178, 8, 114, 8, 242, 8, + 10, 8, 138, 8, 74, 8, 202, 8, 42, 8, 170, 8, 106, 8, 234, 8, + 26, 8, 154, 8, 90, 8, 218, 8, 58, 8, 186, 8, 122, 8, 250, 8, + 6, 8, 134, 8, 70, 8, 198, 8, 38, 8, 166, 8, 102, 8, 230, 8, + 22, 8, 150, 8, 86, 8, 214, 8, 54, 8, 182, 8, 118, 8, 246, 8, + 14, 8, 142, 8, 78, 8, 206, 8, 46, 8, 174, 8, 110, 8, 238, 8, + 30, 8, 158, 8, 94, 8, 222, 8, 62, 8, 190, 8, 126, 8, 254, 8, + 1, 8, 129, 8, 65, 8, 193, 8, 33, 8, 161, 8, 97, 8, 225, 8, + 17, 8, 145, 8, 81, 8, 209, 8, 49, 8, 177, 8, 113, 8, 241, 8, + 9, 8, 137, 8, 73, 8, 201, 8, 41, 8, 169, 8, 105, 8, 233, 8, + 25, 8, 153, 8, 89, 8, 217, 8, 57, 8, 185, 8, 121, 8, 249, 8, + 5, 8, 133, 8, 69, 8, 197, 8, 37, 8, 165, 8, 101, 8, 229, 8, + 21, 8, 149, 8, 85, 8, 213, 8, 53, 8, 181, 8, 117, 8, 245, 8, + 13, 8, 141, 8, 77, 8, 205, 8, 45, 8, 173, 8, 109, 8, 237, 8, + 29, 8, 157, 8, 93, 8, 221, 8, 61, 8, 189, 8, 125, 8, 253, 8, + 19, 9, 275, 9, 147, 9, 403, 9, 83, 9, 339, 9, 211, 9, 467, 9, + 51, 9, 307, 9, 179, 9, 435, 9, 115, 9, 371, 9, 243, 9, 499, 9, + 11, 9, 267, 9, 139, 9, 395, 9, 75, 9, 331, 9, 203, 9, 459, 9, + 43, 9, 299, 9, 171, 9, 427, 9, 107, 9, 363, 9, 235, 9, 491, 9, + 27, 9, 283, 9, 155, 9, 411, 9, 91, 9, 347, 9, 219, 9, 475, 9, + 59, 9, 315, 9, 187, 9, 443, 9, 123, 9, 379, 9, 251, 9, 507, 9, + 7, 9, 263, 9, 135, 9, 391, 9, 71, 9, 327, 9, 199, 9, 455, 9, + 39, 9, 295, 9, 167, 9, 423, 9, 103, 9, 359, 9, 231, 9, 487, 9, + 23, 9, 279, 9, 151, 9, 407, 9, 87, 9, 343, 9, 215, 9, 471, 9, + 55, 9, 311, 9, 183, 9, 439, 9, 119, 9, 375, 9, 247, 9, 503, 9, + 15, 9, 271, 9, 143, 9, 399, 9, 79, 9, 335, 9, 207, 9, 463, 9, + 47, 9, 303, 9, 175, 9, 431, 9, 111, 9, 367, 9, 239, 9, 495, 9, + 31, 9, 287, 9, 159, 9, 415, 9, 95, 9, 351, 9, 223, 9, 479, 9, + 63, 9, 319, 9, 191, 9, 447, 9, 127, 9, 383, 9, 255, 9, 511, 9, + 0, 7, 64, 7, 32, 7, 96, 7, 16, 7, 80, 7, 48, 7, 112, 7, + 8, 7, 72, 7, 40, 7, 104, 7, 24, 7, 88, 7, 56, 7, 120, 7, + 4, 7, 68, 7, 36, 7, 100, 7, 20, 7, 84, 7, 52, 7, 116, 7, + 3, 8, 131, 8, 67, 8, 195, 8, 35, 8, 163, 8, 99, 8, 227, 8 + }; + + internal static readonly short[] distTreeCodes = new short[] { + 0, 5, 16, 5, 8, 5, 24, 5, 4, 5, 20, 5, 12, 5, 28, 5, + 2, 5, 18, 5, 10, 5, 26, 5, 6, 5, 22, 5, 14, 5, 30, 5, + 1, 5, 17, 5, 9, 5, 25, 5, 5, 5, 21, 5, 13, 5, 29, 5, + 3, 5, 19, 5, 11, 5, 27, 5, 7, 5, 23, 5 }; + + internal static readonly StaticTree Literals; + internal static readonly StaticTree Distances; + internal static readonly StaticTree BitLengths; + + internal short[] treeCodes; // static tree or null + internal int[] extraBits; // extra bits for each code or null + internal int extraBase; // base index for extra_bits + internal int elems; // max number of elements in the tree + internal int maxLength; // max bit length for the codes + + private StaticTree(short[] treeCodes, int[] extraBits, int extraBase, int elems, int maxLength) + { + this.treeCodes = treeCodes; + this.extraBits = extraBits; + this.extraBase = extraBase; + this.elems = elems; + this.maxLength = maxLength; + } + static StaticTree() + { + Literals = new StaticTree(lengthAndLiteralsTreeCodes, Tree.ExtraLengthBits, InternalConstants.LITERALS + 1, InternalConstants.L_CODES, InternalConstants.MAX_BITS); + Distances = new StaticTree(distTreeCodes, Tree.ExtraDistanceBits, 0, InternalConstants.D_CODES, InternalConstants.MAX_BITS); + BitLengths = new StaticTree(null, Tree.extra_blbits, 0, InternalConstants.BL_CODES, InternalConstants.MAX_BL_BITS); + } + } + + + + /// + /// Computes an Adler-32 checksum. + /// + /// + /// The Adler checksum is similar to a CRC checksum, but faster to compute, though less + /// reliable. It is used in producing RFC1950 compressed streams. The Adler checksum + /// is a required part of the "ZLIB" standard. Applications will almost never need to + /// use this class directly. + /// + /// + /// + public sealed class Adler + { + // largest prime smaller than 65536 + private static readonly uint BASE = 65521; + // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 + private static readonly int NMAX = 5552; + + +#pragma warning disable 3001 +#pragma warning disable 3002 + + /// + /// Calculates the Adler32 checksum. + /// + /// + /// + /// This is used within ZLIB. You probably don't need to use this directly. + /// + /// + /// + /// To compute an Adler32 checksum on a byte array: + /// + /// var adler = Adler.Adler32(0, null, 0, 0); + /// adler = Adler.Adler32(adler, buffer, index, length); + /// + /// + public static uint Adler32(uint adler, byte[] buf, int index, int len) + { + if (buf == null) + return 1; + + uint s1 = (uint) (adler & 0xffff); + uint s2 = (uint) ((adler >> 16) & 0xffff); + + while (len > 0) + { + int k = len < NMAX ? len : NMAX; + len -= k; + while (k >= 16) + { + //s1 += (buf[index++] & 0xff); s2 += s1; + s1 += buf[index++]; s2 += s1; + s1 += buf[index++]; s2 += s1; + s1 += buf[index++]; s2 += s1; + s1 += buf[index++]; s2 += s1; + s1 += buf[index++]; s2 += s1; + s1 += buf[index++]; s2 += s1; + s1 += buf[index++]; s2 += s1; + s1 += buf[index++]; s2 += s1; + s1 += buf[index++]; s2 += s1; + s1 += buf[index++]; s2 += s1; + s1 += buf[index++]; s2 += s1; + s1 += buf[index++]; s2 += s1; + s1 += buf[index++]; s2 += s1; + s1 += buf[index++]; s2 += s1; + s1 += buf[index++]; s2 += s1; + s1 += buf[index++]; s2 += s1; + k -= 16; + } + if (k != 0) + { + do + { + s1 += buf[index++]; + s2 += s1; + } + while (--k != 0); + } + s1 %= BASE; + s2 %= BASE; + } + return (uint)((s2 << 16) | s1); + } +#pragma warning restore 3001 +#pragma warning restore 3002 + + } + +} \ No newline at end of file diff --git a/src/ZlibAndroidSdkStyle/ZlibAndroidSdkStyle.csproj b/src/ZlibAndroidSdkStyle/ZlibAndroidSdkStyle.csproj new file mode 100644 index 00000000..f7a94a3c --- /dev/null +++ b/src/ZlibAndroidSdkStyle/ZlibAndroidSdkStyle.csproj @@ -0,0 +1,8 @@ + + + net8.0-android + 21 + enable + enable + + \ No newline at end of file diff --git a/src/ZlibAndroidSdkStyle/ZlibBaseStream.cs b/src/ZlibAndroidSdkStyle/ZlibBaseStream.cs new file mode 100644 index 00000000..700ab7ba --- /dev/null +++ b/src/ZlibAndroidSdkStyle/ZlibBaseStream.cs @@ -0,0 +1,627 @@ +// ZlibBaseStream.cs +// ------------------------------------------------------------------ +// +// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. +// All rights reserved. +// +// This code module is part of DotNetZip, a zipfile class library. +// +// ------------------------------------------------------------------ +// +// This code is licensed under the Microsoft Public License. +// See the file License.txt for the license details. +// More info on: http://dotnetzip.codeplex.com +// +// ------------------------------------------------------------------ +// +// last saved (in emacs): +// Time-stamp: <2011-August-06 21:22:38> +// +// ------------------------------------------------------------------ +// +// This module defines the ZlibBaseStream class, which is an intnernal +// base class for DeflateStream, ZlibStream and GZipStream. +// +// ------------------------------------------------------------------ + +using System; +using System.IO; + +namespace Ionic.Zlib +{ + + internal enum ZlibStreamFlavor { ZLIB = 1950, DEFLATE = 1951, GZIP = 1952 } + + internal class ZlibBaseStream : System.IO.Stream + { + protected internal ZlibCodec _z = null; // deferred init... new ZlibCodec(); + + protected internal StreamMode _streamMode = StreamMode.Undefined; + protected internal FlushType _flushMode; + protected internal ZlibStreamFlavor _flavor; + protected internal CompressionMode _compressionMode; + protected internal CompressionLevel _level; + protected internal bool _leaveOpen; + protected internal byte[] _workingBuffer; + protected internal int _bufferSize = ZlibConstants.WorkingBufferSizeDefault; + protected internal byte[] _buf1 = new byte[1]; + + protected internal System.IO.Stream _stream; + protected internal CompressionStrategy Strategy = CompressionStrategy.Default; + + // workitem 7159 + Ionic.Crc.CRC32 crc; + protected internal string _GzipFileName; + protected internal string _GzipComment; + protected internal DateTime _GzipMtime; + protected internal int _gzipHeaderByteCount; + + internal int Crc32 { get { if (crc == null) return 0; return crc.Crc32Result; } } + + public ZlibBaseStream(System.IO.Stream stream, + CompressionMode compressionMode, + CompressionLevel level, + ZlibStreamFlavor flavor, + bool leaveOpen) + : base() + { + this._flushMode = FlushType.None; + //this._workingBuffer = new byte[WORKING_BUFFER_SIZE_DEFAULT]; + this._stream = stream; + this._leaveOpen = leaveOpen; + this._compressionMode = compressionMode; + this._flavor = flavor; + this._level = level; + // workitem 7159 + if (flavor == ZlibStreamFlavor.GZIP) + { + this.crc = new Ionic.Crc.CRC32(); + } + } + + + protected internal bool _wantCompress + { + get + { + return (this._compressionMode == CompressionMode.Compress); + } + } + + private ZlibCodec z + { + get + { + if (_z == null) + { + bool wantRfc1950Header = (this._flavor == ZlibStreamFlavor.ZLIB); + _z = new ZlibCodec(); + if (this._compressionMode == CompressionMode.Decompress) + { + _z.InitializeInflate(wantRfc1950Header); + } + else + { + _z.Strategy = Strategy; + _z.InitializeDeflate(this._level, wantRfc1950Header); + } + } + return _z; + } + } + + + + private byte[] workingBuffer + { + get + { + if (_workingBuffer == null) + _workingBuffer = new byte[_bufferSize]; + return _workingBuffer; + } + } + + + + public override void Write(System.Byte[] buffer, int offset, int count) + { + // workitem 7159 + // calculate the CRC on the unccompressed data (before writing) + if (crc != null) + crc.SlurpBlock(buffer, offset, count); + + if (_streamMode == StreamMode.Undefined) + _streamMode = StreamMode.Writer; + else if (_streamMode != StreamMode.Writer) + throw new ZlibException("Cannot Write after Reading."); + + if (count == 0) + return; + + // first reference of z property will initialize the private var _z + z.InputBuffer = buffer; + _z.NextIn = offset; + _z.AvailableBytesIn = count; + bool done = false; + do + { + _z.OutputBuffer = workingBuffer; + _z.NextOut = 0; + _z.AvailableBytesOut = _workingBuffer.Length; + int rc = (_wantCompress) + ? _z.Deflate(_flushMode) + : _z.Inflate(_flushMode); + if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) + throw new ZlibException((_wantCompress ? "de" : "in") + "flating: " + _z.Message); + + //if (_workingBuffer.Length - _z.AvailableBytesOut > 0) + _stream.Write(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut); + + done = _z.AvailableBytesIn == 0 && _z.AvailableBytesOut != 0; + + // If GZIP and de-compress, we're done when 8 bytes remain. + if (_flavor == ZlibStreamFlavor.GZIP && !_wantCompress) + done = (_z.AvailableBytesIn == 8 && _z.AvailableBytesOut != 0); + + } + while (!done); + } + + + + private void finish() + { + if (_z == null) return; + + if (_streamMode == StreamMode.Writer) + { + bool done = false; + do + { + _z.OutputBuffer = workingBuffer; + _z.NextOut = 0; + _z.AvailableBytesOut = _workingBuffer.Length; + int rc = (_wantCompress) + ? _z.Deflate(FlushType.Finish) + : _z.Inflate(FlushType.Finish); + + if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK) + { + string verb = (_wantCompress ? "de" : "in") + "flating"; + if (_z.Message == null) + throw new ZlibException(String.Format("{0}: (rc = {1})", verb, rc)); + else + throw new ZlibException(verb + ": " + _z.Message); + } + + if (_workingBuffer.Length - _z.AvailableBytesOut > 0) + { + _stream.Write(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut); + } + + done = _z.AvailableBytesIn == 0 && _z.AvailableBytesOut != 0; + // If GZIP and de-compress, we're done when 8 bytes remain. + if (_flavor == ZlibStreamFlavor.GZIP && !_wantCompress) + done = (_z.AvailableBytesIn == 8 && _z.AvailableBytesOut != 0); + + } + while (!done); + + Flush(); + + // workitem 7159 + if (_flavor == ZlibStreamFlavor.GZIP) + { + if (_wantCompress) + { + // Emit the GZIP trailer: CRC32 and size mod 2^32 + int c1 = crc.Crc32Result; + _stream.Write(BitConverter.GetBytes(c1), 0, 4); + int c2 = (Int32)(crc.TotalBytesRead & 0x00000000FFFFFFFF); + _stream.Write(BitConverter.GetBytes(c2), 0, 4); + } + else + { + throw new ZlibException("Writing with decompression is not supported."); + } + } + } + // workitem 7159 + else if (_streamMode == StreamMode.Reader) + { + if (_flavor == ZlibStreamFlavor.GZIP) + { + if (!_wantCompress) + { + // workitem 8501: handle edge case (decompress empty stream) + if (_z.TotalBytesOut == 0L) + return; + + // Read and potentially verify the GZIP trailer: + // CRC32 and size mod 2^32 + byte[] trailer = new byte[8]; + + // workitems 8679 & 12554 + if (_z.AvailableBytesIn < 8) + { + // Make sure we have read to the end of the stream + Array.Copy(_z.InputBuffer, _z.NextIn, trailer, 0, _z.AvailableBytesIn); + int bytesNeeded = 8 - _z.AvailableBytesIn; + int bytesRead = _stream.Read(trailer, + _z.AvailableBytesIn, + bytesNeeded); + if (bytesNeeded != bytesRead) + { + throw new ZlibException(String.Format("Missing or incomplete GZIP trailer. Expected 8 bytes, got {0}.", + _z.AvailableBytesIn + bytesRead)); + } + } + else + { + Array.Copy(_z.InputBuffer, _z.NextIn, trailer, 0, trailer.Length); + } + + Int32 crc32_expected = BitConverter.ToInt32(trailer, 0); + Int32 crc32_actual = crc.Crc32Result; + Int32 isize_expected = BitConverter.ToInt32(trailer, 4); + Int32 isize_actual = (Int32)(_z.TotalBytesOut & 0x00000000FFFFFFFF); + + if (crc32_actual != crc32_expected) + throw new ZlibException(String.Format("Bad CRC32 in GZIP trailer. (actual({0:X8})!=expected({1:X8}))", crc32_actual, crc32_expected)); + + if (isize_actual != isize_expected) + throw new ZlibException(String.Format("Bad size in GZIP trailer. (actual({0})!=expected({1}))", isize_actual, isize_expected)); + + } + else + { + throw new ZlibException("Reading with compression is not supported."); + } + } + } + } + + + private void end() + { + if (z == null) + return; + if (_wantCompress) + { + _z.EndDeflate(); + } + else + { + _z.EndInflate(); + } + _z = null; + } + + + public override void Close() + { + if (_stream == null) return; + try + { + finish(); + } + finally + { + end(); + if (!_leaveOpen) _stream.Close(); + _stream = null; + } + } + + public override void Flush() + { + _stream.Flush(); + } + + public override System.Int64 Seek(System.Int64 offset, System.IO.SeekOrigin origin) + { + throw new NotImplementedException(); + //_outStream.Seek(offset, origin); + } + public override void SetLength(System.Int64 value) + { + _stream.SetLength(value); + } + + +#if NOT + public int Read() + { + if (Read(_buf1, 0, 1) == 0) + return 0; + // calculate CRC after reading + if (crc!=null) + crc.SlurpBlock(_buf1,0,1); + return (_buf1[0] & 0xFF); + } +#endif + + private bool nomoreinput = false; + + + + private string ReadZeroTerminatedString() + { + var list = new System.Collections.Generic.List(); + bool done = false; + do + { + // workitem 7740 + int n = _stream.Read(_buf1, 0, 1); + if (n != 1) + throw new ZlibException("Unexpected EOF reading GZIP header."); + else + { + if (_buf1[0] == 0) + done = true; + else + list.Add(_buf1[0]); + } + } while (!done); + byte[] a = list.ToArray(); + return GZipStream.iso8859dash1.GetString(a, 0, a.Length); + } + + + private int _ReadAndValidateGzipHeader() + { + int totalBytesRead = 0; + // read the header on the first read + byte[] header = new byte[10]; + int n = _stream.Read(header, 0, header.Length); + + // workitem 8501: handle edge case (decompress empty stream) + if (n == 0) + return 0; + + if (n != 10) + throw new ZlibException("Not a valid GZIP stream."); + + if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) + throw new ZlibException("Bad GZIP header."); + + Int32 timet = BitConverter.ToInt32(header, 4); + _GzipMtime = GZipStream._unixEpoch.AddSeconds(timet); + totalBytesRead += n; + if ((header[3] & 0x04) == 0x04) + { + // read and discard extra field + n = _stream.Read(header, 0, 2); // 2-byte length field + totalBytesRead += n; + + Int16 extraLength = (Int16)(header[0] + header[1] * 256); + byte[] extra = new byte[extraLength]; + n = _stream.Read(extra, 0, extra.Length); + if (n != extraLength) + throw new ZlibException("Unexpected end-of-file reading GZIP header."); + totalBytesRead += n; + } + if ((header[3] & 0x08) == 0x08) + _GzipFileName = ReadZeroTerminatedString(); + if ((header[3] & 0x10) == 0x010) + _GzipComment = ReadZeroTerminatedString(); + if ((header[3] & 0x02) == 0x02) + Read(_buf1, 0, 1); // CRC16, ignore + + return totalBytesRead; + } + + + + public override System.Int32 Read(System.Byte[] buffer, System.Int32 offset, System.Int32 count) + { + // According to MS documentation, any implementation of the IO.Stream.Read function must: + // (a) throw an exception if offset & count reference an invalid part of the buffer, + // or if count < 0, or if buffer is null + // (b) return 0 only upon EOF, or if count = 0 + // (c) if not EOF, then return at least 1 byte, up to bytes + + if (_streamMode == StreamMode.Undefined) + { + if (!this._stream.CanRead) throw new ZlibException("The stream is not readable."); + // for the first read, set up some controls. + _streamMode = StreamMode.Reader; + // (The first reference to _z goes through the private accessor which + // may initialize it.) + z.AvailableBytesIn = 0; + if (_flavor == ZlibStreamFlavor.GZIP) + { + _gzipHeaderByteCount = _ReadAndValidateGzipHeader(); + // workitem 8501: handle edge case (decompress empty stream) + if (_gzipHeaderByteCount == 0) + return 0; + } + } + + if (_streamMode != StreamMode.Reader) + throw new ZlibException("Cannot Read after Writing."); + + if (count == 0) return 0; + if (nomoreinput && _wantCompress) return 0; // workitem 8557 + if (buffer == null) throw new ArgumentNullException("buffer"); + if (count < 0) throw new ArgumentOutOfRangeException("count"); + if (offset < buffer.GetLowerBound(0)) throw new ArgumentOutOfRangeException("offset"); + if ((offset + count) > buffer.GetLength(0)) throw new ArgumentOutOfRangeException("count"); + + int rc = 0; + + // set up the output of the deflate/inflate codec: + _z.OutputBuffer = buffer; + _z.NextOut = offset; + _z.AvailableBytesOut = count; + + // This is necessary in case _workingBuffer has been resized. (new byte[]) + // (The first reference to _workingBuffer goes through the private accessor which + // may initialize it.) + _z.InputBuffer = workingBuffer; + + do + { + // need data in _workingBuffer in order to deflate/inflate. Here, we check if we have any. + if ((_z.AvailableBytesIn == 0) && (!nomoreinput)) + { + // No data available, so try to Read data from the captive stream. + _z.NextIn = 0; + _z.AvailableBytesIn = _stream.Read(_workingBuffer, 0, _workingBuffer.Length); + if (_z.AvailableBytesIn == 0) + nomoreinput = true; + + } + // we have data in InputBuffer; now compress or decompress as appropriate + rc = (_wantCompress) + ? _z.Deflate(_flushMode) + : _z.Inflate(_flushMode); + + if (nomoreinput && (rc == ZlibConstants.Z_BUF_ERROR)) + return 0; + + if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) + throw new ZlibException(String.Format("{0}flating: rc={1} msg={2}", (_wantCompress ? "de" : "in"), rc, _z.Message)); + + if ((nomoreinput || rc == ZlibConstants.Z_STREAM_END) && (_z.AvailableBytesOut == count)) + break; // nothing more to read + } + //while (_z.AvailableBytesOut == count && rc == ZlibConstants.Z_OK); + while (_z.AvailableBytesOut > 0 && !nomoreinput && rc == ZlibConstants.Z_OK); + + + // workitem 8557 + // is there more room in output? + if (_z.AvailableBytesOut > 0) + { + if (rc == ZlibConstants.Z_OK && _z.AvailableBytesIn == 0) + { + // deferred + } + + // are we completely done reading? + if (nomoreinput) + { + // and in compression? + if (_wantCompress) + { + // no more input data available; therefore we flush to + // try to complete the read + rc = _z.Deflate(FlushType.Finish); + + if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) + throw new ZlibException(String.Format("Deflating: rc={0} msg={1}", rc, _z.Message)); + } + } + } + + + rc = (count - _z.AvailableBytesOut); + + // calculate CRC after reading + if (crc != null) + crc.SlurpBlock(buffer, offset, rc); + + return rc; + } + + + + public override System.Boolean CanRead + { + get { return this._stream.CanRead; } + } + + public override System.Boolean CanSeek + { + get { return this._stream.CanSeek; } + } + + public override System.Boolean CanWrite + { + get { return this._stream.CanWrite; } + } + + public override System.Int64 Length + { + get { return _stream.Length; } + } + + public override long Position + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + internal enum StreamMode + { + Writer, + Reader, + Undefined, + } + + + public static void CompressString(String s, Stream compressor) + { + byte[] uncompressed = System.Text.Encoding.UTF8.GetBytes(s); + using (compressor) + { + compressor.Write(uncompressed, 0, uncompressed.Length); + } + } + + public static void CompressBuffer(byte[] b, Stream compressor) + { + // workitem 8460 + using (compressor) + { + compressor.Write(b, 0, b.Length); + } + } + + public static String UncompressString(byte[] compressed, Stream decompressor) + { + // workitem 8460 + byte[] working = new byte[1024]; + var encoding = System.Text.Encoding.UTF8; + using (var output = new MemoryStream()) + { + using (decompressor) + { + int n; + while ((n = decompressor.Read(working, 0, working.Length)) != 0) + { + output.Write(working, 0, n); + } + } + + // reset to allow read from start + output.Seek(0, SeekOrigin.Begin); + var sr = new StreamReader(output, encoding); + return sr.ReadToEnd(); + } + } + + public static byte[] UncompressBuffer(byte[] compressed, Stream decompressor) + { + // workitem 8460 + byte[] working = new byte[1024]; + using (var output = new MemoryStream()) + { + using (decompressor) + { + int n; + while ((n = decompressor.Read(working, 0, working.Length)) != 0) + { + output.Write(working, 0, n); + } + } + return output.ToArray(); + } + } + + } + + +} diff --git a/src/ZlibAndroidSdkStyle/ZlibCodec.cs b/src/ZlibAndroidSdkStyle/ZlibCodec.cs new file mode 100644 index 00000000..ab0abcf3 --- /dev/null +++ b/src/ZlibAndroidSdkStyle/ZlibCodec.cs @@ -0,0 +1,717 @@ +// ZlibCodec.cs +// ------------------------------------------------------------------ +// +// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. +// All rights reserved. +// +// This code module is part of DotNetZip, a zipfile class library. +// +// ------------------------------------------------------------------ +// +// This code is licensed under the Microsoft Public License. +// See the file License.txt for the license details. +// More info on: http://dotnetzip.codeplex.com +// +// ------------------------------------------------------------------ +// +// last saved (in emacs): +// Time-stamp: <2009-November-03 15:40:51> +// +// ------------------------------------------------------------------ +// +// This module defines a Codec for ZLIB compression and +// decompression. This code extends code that was based the jzlib +// implementation of zlib, but this code is completely novel. The codec +// class is new, and encapsulates some behaviors that are new, and some +// that were present in other classes in the jzlib code base. In +// keeping with the license for jzlib, the copyright to the jzlib code +// is included below. +// +// ------------------------------------------------------------------ +// +// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the distribution. +// +// 3. The names of the authors may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------- +// +// This program is based on zlib-1.1.3; credit to authors +// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) +// and contributors of zlib. +// +// ----------------------------------------------------------------------- + + +using System; +using Interop=System.Runtime.InteropServices; + +namespace Ionic.Zlib +{ + /// + /// Encoder and Decoder for ZLIB and DEFLATE (IETF RFC1950 and RFC1951). + /// + /// + /// + /// This class compresses and decompresses data according to the Deflate algorithm + /// and optionally, the ZLIB format, as documented in RFC 1950 - ZLIB and RFC 1951 - DEFLATE. + /// + [Interop.GuidAttribute("ebc25cf6-9120-4283-b972-0e5520d0000D")] + [Interop.ComVisible(true)] +#if !NETCF + [Interop.ClassInterface(Interop.ClassInterfaceType.AutoDispatch)] +#endif + sealed public class ZlibCodec + { + /// + /// The buffer from which data is taken. + /// + public byte[] InputBuffer; + + /// + /// An index into the InputBuffer array, indicating where to start reading. + /// + public int NextIn; + + /// + /// The number of bytes available in the InputBuffer, starting at NextIn. + /// + /// + /// Generally you should set this to InputBuffer.Length before the first Inflate() or Deflate() call. + /// The class will update this number as calls to Inflate/Deflate are made. + /// + public int AvailableBytesIn; + + /// + /// Total number of bytes read so far, through all calls to Inflate()/Deflate(). + /// + public long TotalBytesIn; + + /// + /// Buffer to store output data. + /// + public byte[] OutputBuffer; + + /// + /// An index into the OutputBuffer array, indicating where to start writing. + /// + public int NextOut; + + /// + /// The number of bytes available in the OutputBuffer, starting at NextOut. + /// + /// + /// Generally you should set this to OutputBuffer.Length before the first Inflate() or Deflate() call. + /// The class will update this number as calls to Inflate/Deflate are made. + /// + public int AvailableBytesOut; + + /// + /// Total number of bytes written to the output so far, through all calls to Inflate()/Deflate(). + /// + public long TotalBytesOut; + + /// + /// used for diagnostics, when something goes wrong! + /// + public System.String Message; + + internal DeflateManager dstate; + internal InflateManager istate; + + internal uint _Adler32; + + /// + /// The compression level to use in this codec. Useful only in compression mode. + /// + public CompressionLevel CompressLevel = CompressionLevel.Default; + + /// + /// The number of Window Bits to use. + /// + /// + /// This gauges the size of the sliding window, and hence the + /// compression effectiveness as well as memory consumption. It's best to just leave this + /// setting alone if you don't know what it is. The maximum value is 15 bits, which implies + /// a 32k window. + /// + public int WindowBits = ZlibConstants.WindowBitsDefault; + + /// + /// The compression strategy to use. + /// + /// + /// This is only effective in compression. The theory offered by ZLIB is that different + /// strategies could potentially produce significant differences in compression behavior + /// for different data sets. Unfortunately I don't have any good recommendations for how + /// to set it differently. When I tested changing the strategy I got minimally different + /// compression performance. It's best to leave this property alone if you don't have a + /// good feel for it. Or, you may want to produce a test harness that runs through the + /// different strategy options and evaluates them on different file types. If you do that, + /// let me know your results. + /// + public CompressionStrategy Strategy = CompressionStrategy.Default; + + + /// + /// The Adler32 checksum on the data transferred through the codec so far. You probably don't need to look at this. + /// + public int Adler32 { get { return (int)_Adler32; } } + + + /// + /// Create a ZlibCodec. + /// + /// + /// If you use this default constructor, you will later have to explicitly call + /// InitializeInflate() or InitializeDeflate() before using the ZlibCodec to compress + /// or decompress. + /// + public ZlibCodec() { } + + /// + /// Create a ZlibCodec that either compresses or decompresses. + /// + /// + /// Indicates whether the codec should compress (deflate) or decompress (inflate). + /// + public ZlibCodec(CompressionMode mode) + { + if (mode == CompressionMode.Compress) + { + int rc = InitializeDeflate(); + if (rc != ZlibConstants.Z_OK) throw new ZlibException("Cannot initialize for deflate."); + } + else if (mode == CompressionMode.Decompress) + { + int rc = InitializeInflate(); + if (rc != ZlibConstants.Z_OK) throw new ZlibException("Cannot initialize for inflate."); + } + else throw new ZlibException("Invalid ZlibStreamFlavor."); + } + + /// + /// Initialize the inflation state. + /// + /// + /// It is not necessary to call this before using the ZlibCodec to inflate data; + /// It is implicitly called when you call the constructor. + /// + /// Z_OK if everything goes well. + public int InitializeInflate() + { + return InitializeInflate(this.WindowBits); + } + + /// + /// Initialize the inflation state with an explicit flag to + /// govern the handling of RFC1950 header bytes. + /// + /// + /// + /// By default, the ZLIB header defined in RFC 1950 is expected. If + /// you want to read a zlib stream you should specify true for + /// expectRfc1950Header. If you have a deflate stream, you will want to specify + /// false. It is only necessary to invoke this initializer explicitly if you + /// want to specify false. + /// + /// + /// whether to expect an RFC1950 header byte + /// pair when reading the stream of data to be inflated. + /// + /// Z_OK if everything goes well. + public int InitializeInflate(bool expectRfc1950Header) + { + return InitializeInflate(this.WindowBits, expectRfc1950Header); + } + + /// + /// Initialize the ZlibCodec for inflation, with the specified number of window bits. + /// + /// The number of window bits to use. If you need to ask what that is, + /// then you shouldn't be calling this initializer. + /// Z_OK if all goes well. + public int InitializeInflate(int windowBits) + { + this.WindowBits = windowBits; + return InitializeInflate(windowBits, true); + } + + /// + /// Initialize the inflation state with an explicit flag to govern the handling of + /// RFC1950 header bytes. + /// + /// + /// + /// If you want to read a zlib stream you should specify true for + /// expectRfc1950Header. In this case, the library will expect to find a ZLIB + /// header, as defined in RFC + /// 1950, in the compressed stream. If you will be reading a DEFLATE or + /// GZIP stream, which does not have such a header, you will want to specify + /// false. + /// + /// + /// whether to expect an RFC1950 header byte pair when reading + /// the stream of data to be inflated. + /// The number of window bits to use. If you need to ask what that is, + /// then you shouldn't be calling this initializer. + /// Z_OK if everything goes well. + public int InitializeInflate(int windowBits, bool expectRfc1950Header) + { + this.WindowBits = windowBits; + if (dstate != null) throw new ZlibException("You may not call InitializeInflate() after calling InitializeDeflate()."); + istate = new InflateManager(expectRfc1950Header); + return istate.Initialize(this, windowBits); + } + + /// + /// Inflate the data in the InputBuffer, placing the result in the OutputBuffer. + /// + /// + /// You must have set InputBuffer and OutputBuffer, NextIn and NextOut, and AvailableBytesIn and + /// AvailableBytesOut before calling this method. + /// + /// + /// + /// private void InflateBuffer() + /// { + /// int bufferSize = 1024; + /// byte[] buffer = new byte[bufferSize]; + /// ZlibCodec decompressor = new ZlibCodec(); + /// + /// Console.WriteLine("\n============================================"); + /// Console.WriteLine("Size of Buffer to Inflate: {0} bytes.", CompressedBytes.Length); + /// MemoryStream ms = new MemoryStream(DecompressedBytes); + /// + /// int rc = decompressor.InitializeInflate(); + /// + /// decompressor.InputBuffer = CompressedBytes; + /// decompressor.NextIn = 0; + /// decompressor.AvailableBytesIn = CompressedBytes.Length; + /// + /// decompressor.OutputBuffer = buffer; + /// + /// // pass 1: inflate + /// do + /// { + /// decompressor.NextOut = 0; + /// decompressor.AvailableBytesOut = buffer.Length; + /// rc = decompressor.Inflate(FlushType.None); + /// + /// if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) + /// throw new Exception("inflating: " + decompressor.Message); + /// + /// ms.Write(decompressor.OutputBuffer, 0, buffer.Length - decompressor.AvailableBytesOut); + /// } + /// while (decompressor.AvailableBytesIn > 0 || decompressor.AvailableBytesOut == 0); + /// + /// // pass 2: finish and flush + /// do + /// { + /// decompressor.NextOut = 0; + /// decompressor.AvailableBytesOut = buffer.Length; + /// rc = decompressor.Inflate(FlushType.Finish); + /// + /// if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK) + /// throw new Exception("inflating: " + decompressor.Message); + /// + /// if (buffer.Length - decompressor.AvailableBytesOut > 0) + /// ms.Write(buffer, 0, buffer.Length - decompressor.AvailableBytesOut); + /// } + /// while (decompressor.AvailableBytesIn > 0 || decompressor.AvailableBytesOut == 0); + /// + /// decompressor.EndInflate(); + /// } + /// + /// + /// + /// The flush to use when inflating. + /// Z_OK if everything goes well. + public int Inflate(FlushType flush) + { + if (istate == null) + throw new ZlibException("No Inflate State!"); + return istate.Inflate(flush); + } + + + /// + /// Ends an inflation session. + /// + /// + /// Call this after successively calling Inflate(). This will cause all buffers to be flushed. + /// After calling this you cannot call Inflate() without a intervening call to one of the + /// InitializeInflate() overloads. + /// + /// Z_OK if everything goes well. + public int EndInflate() + { + if (istate == null) + throw new ZlibException("No Inflate State!"); + int ret = istate.End(); + istate = null; + return ret; + } + + /// + /// I don't know what this does! + /// + /// Z_OK if everything goes well. + public int SyncInflate() + { + if (istate == null) + throw new ZlibException("No Inflate State!"); + return istate.Sync(); + } + + /// + /// Initialize the ZlibCodec for deflation operation. + /// + /// + /// The codec will use the MAX window bits and the default level of compression. + /// + /// + /// + /// int bufferSize = 40000; + /// byte[] CompressedBytes = new byte[bufferSize]; + /// byte[] DecompressedBytes = new byte[bufferSize]; + /// + /// ZlibCodec compressor = new ZlibCodec(); + /// + /// compressor.InitializeDeflate(CompressionLevel.Default); + /// + /// compressor.InputBuffer = System.Text.ASCIIEncoding.ASCII.GetBytes(TextToCompress); + /// compressor.NextIn = 0; + /// compressor.AvailableBytesIn = compressor.InputBuffer.Length; + /// + /// compressor.OutputBuffer = CompressedBytes; + /// compressor.NextOut = 0; + /// compressor.AvailableBytesOut = CompressedBytes.Length; + /// + /// while (compressor.TotalBytesIn != TextToCompress.Length && compressor.TotalBytesOut < bufferSize) + /// { + /// compressor.Deflate(FlushType.None); + /// } + /// + /// while (true) + /// { + /// int rc= compressor.Deflate(FlushType.Finish); + /// if (rc == ZlibConstants.Z_STREAM_END) break; + /// } + /// + /// compressor.EndDeflate(); + /// + /// + /// + /// Z_OK if all goes well. You generally don't need to check the return code. + public int InitializeDeflate() + { + return _InternalInitializeDeflate(true); + } + + /// + /// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel. + /// + /// + /// The codec will use the maximum window bits (15) and the specified + /// CompressionLevel. It will emit a ZLIB stream as it compresses. + /// + /// The compression level for the codec. + /// Z_OK if all goes well. + public int InitializeDeflate(CompressionLevel level) + { + this.CompressLevel = level; + return _InternalInitializeDeflate(true); + } + + + /// + /// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel, + /// and the explicit flag governing whether to emit an RFC1950 header byte pair. + /// + /// + /// The codec will use the maximum window bits (15) and the specified CompressionLevel. + /// If you want to generate a zlib stream, you should specify true for + /// wantRfc1950Header. In this case, the library will emit a ZLIB + /// header, as defined in RFC + /// 1950, in the compressed stream. + /// + /// The compression level for the codec. + /// whether to emit an initial RFC1950 byte pair in the compressed stream. + /// Z_OK if all goes well. + public int InitializeDeflate(CompressionLevel level, bool wantRfc1950Header) + { + this.CompressLevel = level; + return _InternalInitializeDeflate(wantRfc1950Header); + } + + + /// + /// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel, + /// and the specified number of window bits. + /// + /// + /// The codec will use the specified number of window bits and the specified CompressionLevel. + /// + /// The compression level for the codec. + /// the number of window bits to use. If you don't know what this means, don't use this method. + /// Z_OK if all goes well. + public int InitializeDeflate(CompressionLevel level, int bits) + { + this.CompressLevel = level; + this.WindowBits = bits; + return _InternalInitializeDeflate(true); + } + + /// + /// Initialize the ZlibCodec for deflation operation, using the specified + /// CompressionLevel, the specified number of window bits, and the explicit flag + /// governing whether to emit an RFC1950 header byte pair. + /// + /// + /// The compression level for the codec. + /// whether to emit an initial RFC1950 byte pair in the compressed stream. + /// the number of window bits to use. If you don't know what this means, don't use this method. + /// Z_OK if all goes well. + public int InitializeDeflate(CompressionLevel level, int bits, bool wantRfc1950Header) + { + this.CompressLevel = level; + this.WindowBits = bits; + return _InternalInitializeDeflate(wantRfc1950Header); + } + + private int _InternalInitializeDeflate(bool wantRfc1950Header) + { + if (istate != null) throw new ZlibException("You may not call InitializeDeflate() after calling InitializeInflate()."); + dstate = new DeflateManager(); + dstate.WantRfc1950HeaderBytes = wantRfc1950Header; + + return dstate.Initialize(this, this.CompressLevel, this.WindowBits, this.Strategy); + } + + /// + /// Deflate one batch of data. + /// + /// + /// You must have set InputBuffer and OutputBuffer before calling this method. + /// + /// + /// + /// private void DeflateBuffer(CompressionLevel level) + /// { + /// int bufferSize = 1024; + /// byte[] buffer = new byte[bufferSize]; + /// ZlibCodec compressor = new ZlibCodec(); + /// + /// Console.WriteLine("\n============================================"); + /// Console.WriteLine("Size of Buffer to Deflate: {0} bytes.", UncompressedBytes.Length); + /// MemoryStream ms = new MemoryStream(); + /// + /// int rc = compressor.InitializeDeflate(level); + /// + /// compressor.InputBuffer = UncompressedBytes; + /// compressor.NextIn = 0; + /// compressor.AvailableBytesIn = UncompressedBytes.Length; + /// + /// compressor.OutputBuffer = buffer; + /// + /// // pass 1: deflate + /// do + /// { + /// compressor.NextOut = 0; + /// compressor.AvailableBytesOut = buffer.Length; + /// rc = compressor.Deflate(FlushType.None); + /// + /// if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) + /// throw new Exception("deflating: " + compressor.Message); + /// + /// ms.Write(compressor.OutputBuffer, 0, buffer.Length - compressor.AvailableBytesOut); + /// } + /// while (compressor.AvailableBytesIn > 0 || compressor.AvailableBytesOut == 0); + /// + /// // pass 2: finish and flush + /// do + /// { + /// compressor.NextOut = 0; + /// compressor.AvailableBytesOut = buffer.Length; + /// rc = compressor.Deflate(FlushType.Finish); + /// + /// if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK) + /// throw new Exception("deflating: " + compressor.Message); + /// + /// if (buffer.Length - compressor.AvailableBytesOut > 0) + /// ms.Write(buffer, 0, buffer.Length - compressor.AvailableBytesOut); + /// } + /// while (compressor.AvailableBytesIn > 0 || compressor.AvailableBytesOut == 0); + /// + /// compressor.EndDeflate(); + /// + /// ms.Seek(0, SeekOrigin.Begin); + /// CompressedBytes = new byte[compressor.TotalBytesOut]; + /// ms.Read(CompressedBytes, 0, CompressedBytes.Length); + /// } + /// + /// + /// whether to flush all data as you deflate. Generally you will want to + /// use Z_NO_FLUSH here, in a series of calls to Deflate(), and then call EndDeflate() to + /// flush everything. + /// + /// Z_OK if all goes well. + public int Deflate(FlushType flush) + { + if (dstate == null) + throw new ZlibException("No Deflate State!"); + return dstate.Deflate(flush); + } + + /// + /// End a deflation session. + /// + /// + /// Call this after making a series of one or more calls to Deflate(). All buffers are flushed. + /// + /// Z_OK if all goes well. + public int EndDeflate() + { + if (dstate == null) + throw new ZlibException("No Deflate State!"); + // TODO: dinoch Tue, 03 Nov 2009 15:39 (test this) + //int ret = dstate.End(); + dstate = null; + return ZlibConstants.Z_OK; //ret; + } + + /// + /// Reset a codec for another deflation session. + /// + /// + /// Call this to reset the deflation state. For example if a thread is deflating + /// non-consecutive blocks, you can call Reset() after the Deflate(Sync) of the first + /// block and before the next Deflate(None) of the second block. + /// + /// Z_OK if all goes well. + public void ResetDeflate() + { + if (dstate == null) + throw new ZlibException("No Deflate State!"); + dstate.Reset(); + } + + + /// + /// Set the CompressionStrategy and CompressionLevel for a deflation session. + /// + /// the level of compression to use. + /// the strategy to use for compression. + /// Z_OK if all goes well. + public int SetDeflateParams(CompressionLevel level, CompressionStrategy strategy) + { + if (dstate == null) + throw new ZlibException("No Deflate State!"); + return dstate.SetParams(level, strategy); + } + + + /// + /// Set the dictionary to be used for either Inflation or Deflation. + /// + /// The dictionary bytes to use. + /// Z_OK if all goes well. + public int SetDictionary(byte[] dictionary) + { + if (istate != null) + return istate.SetDictionary(dictionary); + + if (dstate != null) + return dstate.SetDictionary(dictionary); + + throw new ZlibException("No Inflate or Deflate state!"); + } + + // Flush as much pending output as possible. All deflate() output goes + // through this function so some applications may wish to modify it + // to avoid allocating a large strm->next_out buffer and copying into it. + // (See also read_buf()). + internal void flush_pending() + { + int len = dstate.pendingCount; + + if (len > AvailableBytesOut) + len = AvailableBytesOut; + if (len == 0) + return; + + if (dstate.pending.Length <= dstate.nextPending || + OutputBuffer.Length <= NextOut || + dstate.pending.Length < (dstate.nextPending + len) || + OutputBuffer.Length < (NextOut + len)) + { + throw new ZlibException(String.Format("Invalid State. (pending.Length={0}, pendingCount={1})", + dstate.pending.Length, dstate.pendingCount)); + } + + Array.Copy(dstate.pending, dstate.nextPending, OutputBuffer, NextOut, len); + + NextOut += len; + dstate.nextPending += len; + TotalBytesOut += len; + AvailableBytesOut -= len; + dstate.pendingCount -= len; + if (dstate.pendingCount == 0) + { + dstate.nextPending = 0; + } + } + + // Read a new buffer from the current input stream, update the adler32 + // and total number of bytes read. All deflate() input goes through + // this function so some applications may wish to modify it to avoid + // allocating a large strm->next_in buffer and copying from it. + // (See also flush_pending()). + internal int read_buf(byte[] buf, int start, int size) + { + int len = AvailableBytesIn; + + if (len > size) + len = size; + if (len == 0) + return 0; + + AvailableBytesIn -= len; + + if (dstate.WantRfc1950HeaderBytes) + { + _Adler32 = Adler.Adler32(_Adler32, InputBuffer, NextIn, len); + } + Array.Copy(InputBuffer, NextIn, buf, start, len); + NextIn += len; + TotalBytesIn += len; + return len; + } + + } +} \ No newline at end of file diff --git a/src/ZlibAndroidSdkStyle/ZlibConstants.cs b/src/ZlibAndroidSdkStyle/ZlibConstants.cs new file mode 100644 index 00000000..59ae7300 --- /dev/null +++ b/src/ZlibAndroidSdkStyle/ZlibConstants.cs @@ -0,0 +1,128 @@ +// ZlibConstants.cs +// ------------------------------------------------------------------ +// +// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. +// All rights reserved. +// +// This code module is part of DotNetZip, a zipfile class library. +// +// ------------------------------------------------------------------ +// +// This code is licensed under the Microsoft Public License. +// See the file License.txt for the license details. +// More info on: http://dotnetzip.codeplex.com +// +// ------------------------------------------------------------------ +// +// last saved (in emacs): +// Time-stamp: <2009-November-03 18:50:19> +// +// ------------------------------------------------------------------ +// +// This module defines constants used by the zlib class library. This +// code is derived from the jzlib implementation of zlib, but +// significantly modified. In keeping with the license for jzlib, the +// copyright to that code is included here. +// +// ------------------------------------------------------------------ +// +// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the distribution. +// +// 3. The names of the authors may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------- +// +// This program is based on zlib-1.1.3; credit to authors +// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) +// and contributors of zlib. +// +// ----------------------------------------------------------------------- + + +using System; + +namespace Ionic.Zlib +{ + /// + /// A bunch of constants used in the Zlib interface. + /// + public static class ZlibConstants + { + /// + /// The maximum number of window bits for the Deflate algorithm. + /// + public const int WindowBitsMax = 15; // 32K LZ77 window + + /// + /// The default number of window bits for the Deflate algorithm. + /// + public const int WindowBitsDefault = WindowBitsMax; + + /// + /// indicates everything is A-OK + /// + public const int Z_OK = 0; + + /// + /// Indicates that the last operation reached the end of the stream. + /// + public const int Z_STREAM_END = 1; + + /// + /// The operation ended in need of a dictionary. + /// + public const int Z_NEED_DICT = 2; + + /// + /// There was an error with the stream - not enough data, not open and readable, etc. + /// + public const int Z_STREAM_ERROR = -2; + + /// + /// There was an error with the data - not enough data, bad data, etc. + /// + public const int Z_DATA_ERROR = -3; + + /// + /// There was an error with the working buffer. + /// + public const int Z_BUF_ERROR = -5; + + /// + /// The size of the working buffer used in the ZlibCodec class. Defaults to 8192 bytes. + /// +#if NETCF + public const int WorkingBufferSizeDefault = 8192; +#else + public const int WorkingBufferSizeDefault = 16384; +#endif + /// + /// The minimum size of the working buffer used in the ZlibCodec class. Currently it is 128 bytes. + /// + public const int WorkingBufferSizeMin = 1024; + } + +} + diff --git a/src/ZlibAndroidSdkStyle/ZlibStream.cs b/src/ZlibAndroidSdkStyle/ZlibStream.cs new file mode 100644 index 00000000..88ddca9d --- /dev/null +++ b/src/ZlibAndroidSdkStyle/ZlibStream.cs @@ -0,0 +1,725 @@ +// ZlibStream.cs +// ------------------------------------------------------------------ +// +// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. +// All rights reserved. +// +// This code module is part of DotNetZip, a zipfile class library. +// +// ------------------------------------------------------------------ +// +// This code is licensed under the Microsoft Public License. +// See the file License.txt for the license details. +// More info on: http://dotnetzip.codeplex.com +// +// ------------------------------------------------------------------ +// +// last saved (in emacs): +// Time-stamp: <2011-July-31 14:53:33> +// +// ------------------------------------------------------------------ +// +// This module defines the ZlibStream class, which is similar in idea to +// the System.IO.Compression.DeflateStream and +// System.IO.Compression.GZipStream classes in the .NET BCL. +// +// ------------------------------------------------------------------ + +using System; +using System.IO; + +namespace Ionic.Zlib +{ + + /// + /// Represents a Zlib stream for compression or decompression. + /// + /// + /// + /// + /// The ZlibStream is a Decorator on a . It adds ZLIB compression or decompression to any + /// stream. + /// + /// + /// Using this stream, applications can compress or decompress data via + /// stream Read() and Write() operations. Either compresssion or + /// decompression can occur through either reading or writing. The compression + /// format used is ZLIB, which is documented in IETF RFC 1950, "ZLIB Compressed + /// Data Format Specification version 3.3". This implementation of ZLIB always uses + /// DEFLATE as the compression method. (see IETF RFC 1951, "DEFLATE + /// Compressed Data Format Specification version 1.3.") + /// + /// + /// The ZLIB format allows for varying compression methods, window sizes, and dictionaries. + /// This implementation always uses the DEFLATE compression method, a preset dictionary, + /// and 15 window bits by default. + /// + /// + /// + /// This class is similar to , except that it adds the + /// RFC1950 header and trailer bytes to a compressed stream when compressing, or expects + /// the RFC1950 header and trailer bytes when decompressing. It is also similar to the + /// . + /// + /// + /// + /// + public class ZlibStream : System.IO.Stream + { + internal ZlibBaseStream _baseStream; + bool _disposed; + + /// + /// Create a ZlibStream using the specified CompressionMode. + /// + /// + /// + /// + /// When mode is CompressionMode.Compress, the ZlibStream + /// will use the default compression level. The "captive" stream will be + /// closed when the ZlibStream is closed. + /// + /// + /// + /// + /// + /// This example uses a ZlibStream to compress a file, and writes the + /// compressed data to another file. + /// + /// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress)) + /// { + /// using (var raw = System.IO.File.Create(fileToCompress + ".zlib")) + /// { + /// using (Stream compressor = new ZlibStream(raw, CompressionMode.Compress)) + /// { + /// byte[] buffer = new byte[WORKING_BUFFER_SIZE]; + /// int n; + /// while ((n= input.Read(buffer, 0, buffer.Length)) != 0) + /// { + /// compressor.Write(buffer, 0, n); + /// } + /// } + /// } + /// } + /// + /// + /// Using input As Stream = File.OpenRead(fileToCompress) + /// Using raw As FileStream = File.Create(fileToCompress & ".zlib") + /// Using compressor As Stream = New ZlibStream(raw, CompressionMode.Compress) + /// Dim buffer As Byte() = New Byte(4096) {} + /// Dim n As Integer = -1 + /// Do While (n <> 0) + /// If (n > 0) Then + /// compressor.Write(buffer, 0, n) + /// End If + /// n = input.Read(buffer, 0, buffer.Length) + /// Loop + /// End Using + /// End Using + /// End Using + /// + /// + /// + /// The stream which will be read or written. + /// Indicates whether the ZlibStream will compress or decompress. + public ZlibStream(System.IO.Stream stream, CompressionMode mode) + : this(stream, mode, CompressionLevel.Default, false) + { + } + + /// + /// Create a ZlibStream using the specified CompressionMode and + /// the specified CompressionLevel. + /// + /// + /// + /// + /// + /// When mode is CompressionMode.Decompress, the level parameter is ignored. + /// The "captive" stream will be closed when the ZlibStream is closed. + /// + /// + /// + /// + /// + /// This example uses a ZlibStream to compress data from a file, and writes the + /// compressed data to another file. + /// + /// + /// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress)) + /// { + /// using (var raw = System.IO.File.Create(fileToCompress + ".zlib")) + /// { + /// using (Stream compressor = new ZlibStream(raw, + /// CompressionMode.Compress, + /// CompressionLevel.BestCompression)) + /// { + /// byte[] buffer = new byte[WORKING_BUFFER_SIZE]; + /// int n; + /// while ((n= input.Read(buffer, 0, buffer.Length)) != 0) + /// { + /// compressor.Write(buffer, 0, n); + /// } + /// } + /// } + /// } + /// + /// + /// + /// Using input As Stream = File.OpenRead(fileToCompress) + /// Using raw As FileStream = File.Create(fileToCompress & ".zlib") + /// Using compressor As Stream = New ZlibStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression) + /// Dim buffer As Byte() = New Byte(4096) {} + /// Dim n As Integer = -1 + /// Do While (n <> 0) + /// If (n > 0) Then + /// compressor.Write(buffer, 0, n) + /// End If + /// n = input.Read(buffer, 0, buffer.Length) + /// Loop + /// End Using + /// End Using + /// End Using + /// + /// + /// + /// The stream to be read or written while deflating or inflating. + /// Indicates whether the ZlibStream will compress or decompress. + /// A tuning knob to trade speed for effectiveness. + public ZlibStream(System.IO.Stream stream, CompressionMode mode, CompressionLevel level) + : this(stream, mode, level, false) + { + } + + /// + /// Create a ZlibStream using the specified CompressionMode, and + /// explicitly specify whether the captive stream should be left open after + /// Deflation or Inflation. + /// + /// + /// + /// + /// + /// When mode is CompressionMode.Compress, the ZlibStream will use + /// the default compression level. + /// + /// + /// + /// This constructor allows the application to request that the captive stream + /// remain open after the deflation or inflation occurs. By default, after + /// Close() is called on the stream, the captive stream is also + /// closed. In some cases this is not desired, for example if the stream is a + /// that will be re-read after + /// compression. Specify true for the parameter to leave the stream + /// open. + /// + /// + /// + /// See the other overloads of this constructor for example code. + /// + /// + /// + /// + /// The stream which will be read or written. This is called the + /// "captive" stream in other places in this documentation. + /// Indicates whether the ZlibStream will compress or decompress. + /// true if the application would like the stream to remain + /// open after inflation/deflation. + public ZlibStream(System.IO.Stream stream, CompressionMode mode, bool leaveOpen) + : this(stream, mode, CompressionLevel.Default, leaveOpen) + { + } + + /// + /// Create a ZlibStream using the specified CompressionMode + /// and the specified CompressionLevel, and explicitly specify + /// whether the stream should be left open after Deflation or Inflation. + /// + /// + /// + /// + /// + /// This constructor allows the application to request that the captive + /// stream remain open after the deflation or inflation occurs. By + /// default, after Close() is called on the stream, the captive + /// stream is also closed. In some cases this is not desired, for example + /// if the stream is a that will be + /// re-read after compression. Specify true for the parameter to leave the stream open. + /// + /// + /// + /// When mode is CompressionMode.Decompress, the level parameter is + /// ignored. + /// + /// + /// + /// + /// + /// + /// This example shows how to use a ZlibStream to compress the data from a file, + /// and store the result into another file. The filestream remains open to allow + /// additional data to be written to it. + /// + /// + /// using (var output = System.IO.File.Create(fileToCompress + ".zlib")) + /// { + /// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress)) + /// { + /// using (Stream compressor = new ZlibStream(output, CompressionMode.Compress, CompressionLevel.BestCompression, true)) + /// { + /// byte[] buffer = new byte[WORKING_BUFFER_SIZE]; + /// int n; + /// while ((n= input.Read(buffer, 0, buffer.Length)) != 0) + /// { + /// compressor.Write(buffer, 0, n); + /// } + /// } + /// } + /// // can write additional data to the output stream here + /// } + /// + /// + /// Using output As FileStream = File.Create(fileToCompress & ".zlib") + /// Using input As Stream = File.OpenRead(fileToCompress) + /// Using compressor As Stream = New ZlibStream(output, CompressionMode.Compress, CompressionLevel.BestCompression, True) + /// Dim buffer As Byte() = New Byte(4096) {} + /// Dim n As Integer = -1 + /// Do While (n <> 0) + /// If (n > 0) Then + /// compressor.Write(buffer, 0, n) + /// End If + /// n = input.Read(buffer, 0, buffer.Length) + /// Loop + /// End Using + /// End Using + /// ' can write additional data to the output stream here. + /// End Using + /// + /// + /// + /// The stream which will be read or written. + /// + /// Indicates whether the ZlibStream will compress or decompress. + /// + /// + /// true if the application would like the stream to remain open after + /// inflation/deflation. + /// + /// + /// + /// A tuning knob to trade speed for effectiveness. This parameter is + /// effective only when mode is CompressionMode.Compress. + /// + public ZlibStream(System.IO.Stream stream, CompressionMode mode, CompressionLevel level, bool leaveOpen) + { + _baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.ZLIB, leaveOpen); + } + + #region Zlib properties + + /// + /// This property sets the flush behavior on the stream. + /// Sorry, though, not sure exactly how to describe all the various settings. + /// + virtual public FlushType FlushMode + { + get { return (this._baseStream._flushMode); } + set + { + if (_disposed) throw new ObjectDisposedException("ZlibStream"); + this._baseStream._flushMode = value; + } + } + + /// + /// The size of the working buffer for the compression codec. + /// + /// + /// + /// + /// The working buffer is used for all stream operations. The default size is + /// 1024 bytes. The minimum size is 128 bytes. You may get better performance + /// with a larger buffer. Then again, you might not. You would have to test + /// it. + /// + /// + /// + /// Set this before the first call to Read() or Write() on the + /// stream. If you try to set it afterwards, it will throw. + /// + /// + public int BufferSize + { + get + { + return this._baseStream._bufferSize; + } + set + { + if (_disposed) throw new ObjectDisposedException("ZlibStream"); + if (this._baseStream._workingBuffer != null) + throw new ZlibException("The working buffer is already set."); + if (value < ZlibConstants.WorkingBufferSizeMin) + throw new ZlibException(String.Format("Don't be silly. {0} bytes?? Use a bigger buffer, at least {1}.", value, ZlibConstants.WorkingBufferSizeMin)); + this._baseStream._bufferSize = value; + } + } + + /// Returns the total number of bytes input so far. + virtual public long TotalIn + { + get { return this._baseStream._z.TotalBytesIn; } + } + + /// Returns the total number of bytes output so far. + virtual public long TotalOut + { + get { return this._baseStream._z.TotalBytesOut; } + } + + #endregion + + #region System.IO.Stream methods + + /// + /// Dispose the stream. + /// + /// + /// + /// This may or may not result in a Close() call on the captive + /// stream. See the constructors that have a leaveOpen parameter + /// for more information. + /// + /// + /// This method may be invoked in two distinct scenarios. If disposing + /// == true, the method has been called directly or indirectly by a + /// user's code, for example via the public Dispose() method. In this + /// case, both managed and unmanaged resources can be referenced and + /// disposed. If disposing == false, the method has been called by the + /// runtime from inside the object finalizer and this method should not + /// reference other objects; in that case only unmanaged resources must + /// be referenced or disposed. + /// + /// + /// + /// indicates whether the Dispose method was invoked by user code. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!_disposed) + { + if (disposing && (this._baseStream != null)) + this._baseStream.Close(); + _disposed = true; + } + } + finally + { + base.Dispose(disposing); + } + } + + + /// + /// Indicates whether the stream can be read. + /// + /// + /// The return value depends on whether the captive stream supports reading. + /// + public override bool CanRead + { + get + { + if (_disposed) throw new ObjectDisposedException("ZlibStream"); + return _baseStream._stream.CanRead; + } + } + + /// + /// Indicates whether the stream supports Seek operations. + /// + /// + /// Always returns false. + /// + public override bool CanSeek + { + get { return false; } + } + + /// + /// Indicates whether the stream can be written. + /// + /// + /// The return value depends on whether the captive stream supports writing. + /// + public override bool CanWrite + { + get + { + if (_disposed) throw new ObjectDisposedException("ZlibStream"); + return _baseStream._stream.CanWrite; + } + } + + /// + /// Flush the stream. + /// + public override void Flush() + { + if (_disposed) throw new ObjectDisposedException("ZlibStream"); + _baseStream.Flush(); + } + + /// + /// Reading this property always throws a . + /// + public override long Length + { + get { throw new NotSupportedException(); } + } + + /// + /// The position of the stream pointer. + /// + /// + /// + /// Setting this property always throws a . Reading will return the total bytes + /// written out, if used in writing, or the total bytes read in, if used in + /// reading. The count may refer to compressed bytes or uncompressed bytes, + /// depending on how you've used the stream. + /// + public override long Position + { + get + { + if (this._baseStream._streamMode == Ionic.Zlib.ZlibBaseStream.StreamMode.Writer) + return this._baseStream._z.TotalBytesOut; + if (this._baseStream._streamMode == Ionic.Zlib.ZlibBaseStream.StreamMode.Reader) + return this._baseStream._z.TotalBytesIn; + return 0; + } + + set { throw new NotSupportedException(); } + } + + /// + /// Read data from the stream. + /// + /// + /// + /// + /// + /// If you wish to use the ZlibStream to compress data while reading, + /// you can create a ZlibStream with CompressionMode.Compress, + /// providing an uncompressed data stream. Then call Read() on that + /// ZlibStream, and the data read will be compressed. If you wish to + /// use the ZlibStream to decompress data while reading, you can create + /// a ZlibStream with CompressionMode.Decompress, providing a + /// readable compressed data stream. Then call Read() on that + /// ZlibStream, and the data will be decompressed as it is read. + /// + /// + /// + /// A ZlibStream can be used for Read() or Write(), but + /// not both. + /// + /// + /// + /// + /// + /// The buffer into which the read data should be placed. + /// + /// + /// the offset within that data array to put the first byte read. + /// + /// the number of bytes to read. + /// + /// the number of bytes read + public override int Read(byte[] buffer, int offset, int count) + { + if (_disposed) throw new ObjectDisposedException("ZlibStream"); + return _baseStream.Read(buffer, offset, count); + } + + /// + /// Calling this method always throws a . + /// + /// + /// The offset to seek to.... + /// IF THIS METHOD ACTUALLY DID ANYTHING. + /// + /// + /// The reference specifying how to apply the offset.... IF + /// THIS METHOD ACTUALLY DID ANYTHING. + /// + /// + /// nothing. This method always throws. + public override long Seek(long offset, System.IO.SeekOrigin origin) + { + throw new NotSupportedException(); + } + + /// + /// Calling this method always throws a . + /// + /// + /// The new value for the stream length.... IF + /// THIS METHOD ACTUALLY DID ANYTHING. + /// + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + /// Write data to the stream. + /// + /// + /// + /// + /// + /// If you wish to use the ZlibStream to compress data while writing, + /// you can create a ZlibStream with CompressionMode.Compress, + /// and a writable output stream. Then call Write() on that + /// ZlibStream, providing uncompressed data as input. The data sent to + /// the output stream will be the compressed form of the data written. If you + /// wish to use the ZlibStream to decompress data while writing, you + /// can create a ZlibStream with CompressionMode.Decompress, and a + /// writable output stream. Then call Write() on that stream, + /// providing previously compressed data. The data sent to the output stream + /// will be the decompressed form of the data written. + /// + /// + /// + /// A ZlibStream can be used for Read() or Write(), but not both. + /// + /// + /// The buffer holding data to write to the stream. + /// the offset within that data array to find the first byte to write. + /// the number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + if (_disposed) throw new ObjectDisposedException("ZlibStream"); + _baseStream.Write(buffer, offset, count); + } + #endregion + + + /// + /// Compress a string into a byte array using ZLIB. + /// + /// + /// + /// Uncompress it with . + /// + /// + /// + /// + /// + /// + /// + /// A string to compress. The string will first be encoded + /// using UTF8, then compressed. + /// + /// + /// The string in compressed form + public static byte[] CompressString(String s) + { + using (var ms = new MemoryStream()) + { + Stream compressor = + new ZlibStream(ms, CompressionMode.Compress, CompressionLevel.BestCompression); + ZlibBaseStream.CompressString(s, compressor); + return ms.ToArray(); + } + } + + + /// + /// Compress a byte array into a new byte array using ZLIB. + /// + /// + /// + /// Uncompress it with . + /// + /// + /// + /// + /// + /// + /// A buffer to compress. + /// + /// + /// The data in compressed form + public static byte[] CompressBuffer(byte[] b) + { + using (var ms = new MemoryStream()) + { + Stream compressor = + new ZlibStream( ms, CompressionMode.Compress, CompressionLevel.BestCompression ); + + ZlibBaseStream.CompressBuffer(b, compressor); + return ms.ToArray(); + } + } + + + /// + /// Uncompress a ZLIB-compressed byte array into a single string. + /// + /// + /// + /// + /// + /// + /// A buffer containing ZLIB-compressed data. + /// + /// + /// The uncompressed string + public static String UncompressString(byte[] compressed) + { + using (var input = new MemoryStream(compressed)) + { + Stream decompressor = + new ZlibStream(input, CompressionMode.Decompress); + + return ZlibBaseStream.UncompressString(compressed, decompressor); + } + } + + + /// + /// Uncompress a ZLIB-compressed byte array into a byte array. + /// + /// + /// + /// + /// + /// + /// A buffer containing ZLIB-compressed data. + /// + /// + /// The data in uncompressed form + public static byte[] UncompressBuffer(byte[] compressed) + { + using (var input = new MemoryStream(compressed)) + { + Stream decompressor = + new ZlibStream( input, CompressionMode.Decompress ); + + return ZlibBaseStream.UncompressBuffer(compressed, decompressor); + } + } + + } + + +} \ No newline at end of file diff --git a/src/keepass2android-appSdkStyle/AboutActivity.cs b/src/keepass2android-appSdkStyle/AboutActivity.cs new file mode 100644 index 00000000..11029c60 --- /dev/null +++ b/src/keepass2android-appSdkStyle/AboutActivity.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Android.App; +using Android.Content; +using Android.Content.PM; + +namespace keepass2android +{ + [Activity(Label = "@string/app_name", + ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.KeyboardHidden, + Theme = "@style/MyTheme_ActionBar", + Exported = true)] + [IntentFilter(new[] { "kp2a.action.AboutActivity" }, Categories = new[] { Intent.CategoryDefault })] + public class AboutActivity: Activity, IDialogInterfaceOnDismissListener + { + private AboutDialog _dialog; + + protected override void OnResume() + { + if ((_dialog == null) || (_dialog.IsShowing == false)) + { + if (new ActivityDesign(this).UseDarkTheme) + _dialog = new AboutDialog(this, Android.Resource.Style.ThemeHoloNoActionBarFullscreen); + else + _dialog = new AboutDialog(this, Android.Resource.Style.ThemeHoloLightNoActionBarFullscreen); + _dialog.SetOnDismissListener(this); + _dialog.Show(); + } + base.OnResume(); + } + + public void OnDismiss(IDialogInterface dialog) + { + Finish(); + } + } +} diff --git a/src/keepass2android-appSdkStyle/AboutDialog.cs b/src/keepass2android-appSdkStyle/AboutDialog.cs new file mode 100644 index 00000000..e1f70884 --- /dev/null +++ b/src/keepass2android-appSdkStyle/AboutDialog.cs @@ -0,0 +1,125 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using System; +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Text.Method; +using Android.Widget; +using Android.Content.PM; + +namespace keepass2android +{ + + public class AboutDialog : Dialog { + + public AboutDialog(Context context):base (context) { + } + public AboutDialog(Context context, int theme) + : base(context, theme) + { + } + + public AboutDialog(IntPtr javaRef, JniHandleOwnership transfer) : base(javaRef, transfer) + { + + } + + + protected override void OnCreate(Bundle savedInstanceState) { + base.OnCreate(savedInstanceState); + SetContentView(Resource.Layout.about); + SetTitle(Resource.String.app_name); + + SetVersion(); + SetContributors(); + + FindViewById(Resource.Id.suggest).Click += delegate + { + try + { + Util.GotoUrl(Context, Resource.String.SuggestionsURL); + } + catch (ActivityNotFoundException) + { + Toast.MakeText(Context, Resource.String.no_url_handler, ToastLength.Long).Show(); + } + + }; + FindViewById(Resource.Id.rate).Click += delegate + { + try + { + Util.GotoMarket(Context); + } + catch (ActivityNotFoundException) + { + Toast.MakeText(Context, Resource.String.no_url_handler, ToastLength.Long).Show(); + } + }; + FindViewById(Resource.Id.translate).Click += delegate + { + try + { + Util.GotoUrl(Context, Resource.String.TranslationURL); + } + catch (ActivityNotFoundException) + { + Toast.MakeText(Context, Resource.String.no_url_handler, ToastLength.Long).Show(); + } + }; FindViewById(Resource.Id.donate).Click += delegate + { + Util.GotoDonateUrl(Context); + }; + } + + private void SetContributors() + { + TextView tv = (TextView)FindViewById(Resource.Id.further_authors); + tv.Text = Context.GetString(Resource.String.further_authors, new Java.Lang.Object[] { Context.GetString(Resource.String.further_author_names) }); + + TextView tvdesigners = (TextView)FindViewById(Resource.Id.designers); + tvdesigners.Text = Context.GetString(Resource.String.designers, new Java.Lang.Object[] { Context.GetString(Resource.String.designer_names) }); + + TextView tvsupporters = (TextView)FindViewById(Resource.Id.supporters); + tvsupporters.Text = Context.GetString(Resource.String.supporters, new Java.Lang.Object[] { Context.GetString(Resource.String.supporter_names) }); + } + + private void SetVersion() { + Context ctx = Context; + + String version; + try { + PackageInfo packageInfo = ctx.PackageManager.GetPackageInfo(ctx.PackageName, 0); + version = packageInfo.VersionName; + + } catch (PackageManager.NameNotFoundException) { + version = ""; + } + + TextView tv = (TextView) FindViewById(Resource.Id.versionX); + tv.Text = version; + + FindViewById(Resource.Id.versionB).Click += (sender, args) => ChangeLog.ShowChangeLog(ctx, () => { }); + } + + } + +} + diff --git a/src/keepass2android-appSdkStyle/ActivityLaunchMode.cs b/src/keepass2android-appSdkStyle/ActivityLaunchMode.cs new file mode 100644 index 00000000..8cea7e65 --- /dev/null +++ b/src/keepass2android-appSdkStyle/ActivityLaunchMode.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; + +namespace keepass2android +{ + public abstract class ActivityLaunchMode + { + public abstract void Launch(Activity act, Intent i); + } + + public class ActivityLaunchModeForward : ActivityLaunchMode + { + public override void Launch(Activity act, Intent i) + { + i.AddFlags(ActivityFlags.ForwardResult); + act.StartActivity(i); + } + } + + public class ActivityLaunchModeRequestCode : ActivityLaunchMode + { + private readonly int _reqCode; + + public ActivityLaunchModeRequestCode(int reqCode) + { + _reqCode = reqCode; + } + public override void Launch(Activity act, Intent i) + { + act.StartActivityForResult(i, _reqCode); + } + } + + public class ActivityLaunchModeSimple : ActivityLaunchMode + { + public override void Launch(Activity act, Intent i) + { + act.StartActivity(i); + } + } +} \ No newline at end of file diff --git a/src/keepass2android-appSdkStyle/AndroidManifest.xml b/src/keepass2android-appSdkStyle/AndroidManifest.xml new file mode 100644 index 00000000..a08146fd --- /dev/null +++ b/src/keepass2android-appSdkStyle/AndroidManifest.xml @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:label="@string/language_selection_title" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/keepass2android-appSdkStyle/AppKilledInfo.cs b/src/keepass2android-appSdkStyle/AppKilledInfo.cs new file mode 100644 index 00000000..48a846dc --- /dev/null +++ b/src/keepass2android-appSdkStyle/AppKilledInfo.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; + +namespace keepass2android +{ + [Activity(Label = AppNames.AppName)] + public class AppKilledInfo : Activity, IDialogInterfaceOnDismissListener + { + protected override void OnCreate(Bundle bundle) + { + base.OnCreate(bundle); + //as we expect this to happen only rarely (having a foreground service running when unlocked), + //we don't try to handle this better + //But at least explain to the user what happened! + ((NotificationManager)GetSystemService(Context.NotificationService)).CancelAll(); + AlertDialog.Builder b = new AlertDialog.Builder(this); + b.SetMessage(Resource.String.killed_by_os); + b.SetPositiveButton(Android.Resource.String.Ok, delegate + { + Intent i = new Intent(this, typeof(SelectCurrentDbActivity)); + i.AddFlags(ActivityFlags.ClearTask | ActivityFlags.NewTask); + StartActivity(i); + + }); + b.SetNegativeButton(Resource.String.cancel, delegate { }); + b.SetTitle(GetString(AppNames.AppNameResource)); + + var dialog = b.Create(); + dialog.SetOnDismissListener(this); + dialog.Show(); + } + + public void OnDismiss(IDialogInterface dialog) + { + Finish(); + } + } +} \ No newline at end of file diff --git a/src/keepass2android-appSdkStyle/AssemblyInfo.cs b/src/keepass2android-appSdkStyle/AssemblyInfo.cs new file mode 100644 index 00000000..b3d53e6e --- /dev/null +++ b/src/keepass2android-appSdkStyle/AssemblyInfo.cs @@ -0,0 +1,21 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + + + + + diff --git a/src/keepass2android-appSdkStyle/Assets/LICENSE_SourceCodePro.txt b/src/keepass2android-appSdkStyle/Assets/LICENSE_SourceCodePro.txt new file mode 100644 index 00000000..11773304 --- /dev/null +++ b/src/keepass2android-appSdkStyle/Assets/LICENSE_SourceCodePro.txt @@ -0,0 +1,93 @@ +Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/keepass2android-appSdkStyle/Assets/SourceCodePro-Regular.ttf b/src/keepass2android-appSdkStyle/Assets/SourceCodePro-Regular.ttf new file mode 100644 index 00000000..b2cff928 Binary files /dev/null and b/src/keepass2android-appSdkStyle/Assets/SourceCodePro-Regular.ttf differ diff --git a/src/keepass2android-appSdkStyle/Assets/fontawesome-webfont.ttf b/src/keepass2android-appSdkStyle/Assets/fontawesome-webfont.ttf new file mode 100644 index 00000000..d3659246 Binary files /dev/null and b/src/keepass2android-appSdkStyle/Assets/fontawesome-webfont.ttf differ diff --git a/src/keepass2android-appSdkStyle/Assets/publicsuffix.txt b/src/keepass2android-appSdkStyle/Assets/publicsuffix.txt new file mode 100644 index 00000000..c891be6d --- /dev/null +++ b/src/keepass2android-appSdkStyle/Assets/publicsuffix.txt @@ -0,0 +1,12742 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Please pull this list from, and only from https://publicsuffix.org/list/public_suffix_list.dat, +// rather than any other VCS sites. Pulling from any other URL is not guaranteed to be supported. + +// Instructions on pulling and using this list can be found at https://publicsuffix.org/list/. + +// ===BEGIN ICANN DOMAINS=== + +// ac : https://en.wikipedia.org/wiki/.ac +ac +com.ac +edu.ac +gov.ac +net.ac +mil.ac +org.ac + +// ad : https://en.wikipedia.org/wiki/.ad +ad +nom.ad + +// ae : https://en.wikipedia.org/wiki/.ae +// see also: "Domain Name Eligibility Policy" at http://www.aeda.ae/eng/aepolicy.php +ae +co.ae +net.ae +org.ae +sch.ae +ac.ae +gov.ae +mil.ae + +// aero : see https://www.information.aero/index.php?id=66 +aero +accident-investigation.aero +accident-prevention.aero +aerobatic.aero +aeroclub.aero +aerodrome.aero +agents.aero +aircraft.aero +airline.aero +airport.aero +air-surveillance.aero +airtraffic.aero +air-traffic-control.aero +ambulance.aero +amusement.aero +association.aero +author.aero +ballooning.aero +broker.aero +caa.aero +cargo.aero +catering.aero +certification.aero +championship.aero +charter.aero +civilaviation.aero +club.aero +conference.aero +consultant.aero +consulting.aero +control.aero +council.aero +crew.aero +design.aero +dgca.aero +educator.aero +emergency.aero +engine.aero +engineer.aero +entertainment.aero +equipment.aero +exchange.aero +express.aero +federation.aero +flight.aero +freight.aero +fuel.aero +gliding.aero +government.aero +groundhandling.aero +group.aero +hanggliding.aero +homebuilt.aero +insurance.aero +journal.aero +journalist.aero +leasing.aero +logistics.aero +magazine.aero +maintenance.aero +media.aero +microlight.aero +modelling.aero +navigation.aero +parachuting.aero +paragliding.aero +passenger-association.aero +pilot.aero +press.aero +production.aero +recreation.aero +repbody.aero +res.aero +research.aero +rotorcraft.aero +safety.aero +scientist.aero +services.aero +show.aero +skydiving.aero +software.aero +student.aero +trader.aero +trading.aero +trainer.aero +union.aero +workinggroup.aero +works.aero + +// af : http://www.nic.af/help.jsp +af +gov.af +com.af +org.af +net.af +edu.af + +// ag : http://www.nic.ag/prices.htm +ag +com.ag +org.ag +net.ag +co.ag +nom.ag + +// ai : http://nic.com.ai/ +ai +off.ai +com.ai +net.ai +org.ai + +// al : http://www.ert.gov.al/ert_alb/faq_det.html?Id=31 +al +com.al +edu.al +gov.al +mil.al +net.al +org.al + +// am : https://en.wikipedia.org/wiki/.am +am + +// ao : https://en.wikipedia.org/wiki/.ao +// http://www.dns.ao/REGISTR.DOC +ao +ed.ao +gv.ao +og.ao +co.ao +pb.ao +it.ao + +// aq : https://en.wikipedia.org/wiki/.aq +aq + +// ar : https://nic.ar/nic-argentina/normativa-vigente +ar +com.ar +edu.ar +gob.ar +gov.ar +int.ar +mil.ar +musica.ar +net.ar +org.ar +tur.ar + +// arpa : https://en.wikipedia.org/wiki/.arpa +// Confirmed by registry 2008-06-18 +arpa +e164.arpa +in-addr.arpa +ip6.arpa +iris.arpa +uri.arpa +urn.arpa + +// as : https://en.wikipedia.org/wiki/.as +as +gov.as + +// asia : https://en.wikipedia.org/wiki/.asia +asia + +// at : https://en.wikipedia.org/wiki/.at +// Confirmed by registry 2008-06-17 +at +ac.at +co.at +gv.at +or.at + +// au : https://en.wikipedia.org/wiki/.au +// http://www.auda.org.au/ +au +// 2LDs +com.au +net.au +org.au +edu.au +gov.au +asn.au +id.au +// Historic 2LDs (closed to new registration, but sites still exist) +info.au +conf.au +oz.au +// CGDNs - http://www.cgdn.org.au/ +act.au +nsw.au +nt.au +qld.au +sa.au +tas.au +vic.au +wa.au +// 3LDs +act.edu.au +nsw.edu.au +nt.edu.au +qld.edu.au +sa.edu.au +tas.edu.au +vic.edu.au +wa.edu.au +// act.gov.au Bug 984824 - Removed at request of Greg Tankard +// nsw.gov.au Bug 547985 - Removed at request of +// nt.gov.au Bug 940478 - Removed at request of Greg Connors +qld.gov.au +sa.gov.au +tas.gov.au +vic.gov.au +wa.gov.au + +// aw : https://en.wikipedia.org/wiki/.aw +aw +com.aw + +// ax : https://en.wikipedia.org/wiki/.ax +ax + +// az : https://en.wikipedia.org/wiki/.az +az +com.az +net.az +int.az +gov.az +org.az +edu.az +info.az +pp.az +mil.az +name.az +pro.az +biz.az + +// ba : http://nic.ba/users_data/files/pravilnik_o_registraciji.pdf +ba +com.ba +edu.ba +gov.ba +mil.ba +net.ba +org.ba + +// bb : https://en.wikipedia.org/wiki/.bb +bb +biz.bb +co.bb +com.bb +edu.bb +gov.bb +info.bb +net.bb +org.bb +store.bb +tv.bb + +// bd : https://en.wikipedia.org/wiki/.bd +*.bd + +// be : https://en.wikipedia.org/wiki/.be +// Confirmed by registry 2008-06-08 +be +ac.be + +// bf : https://en.wikipedia.org/wiki/.bf +bf +gov.bf + +// bg : https://en.wikipedia.org/wiki/.bg +// https://www.register.bg/user/static/rules/en/index.html +bg +a.bg +b.bg +c.bg +d.bg +e.bg +f.bg +g.bg +h.bg +i.bg +j.bg +k.bg +l.bg +m.bg +n.bg +o.bg +p.bg +q.bg +r.bg +s.bg +t.bg +u.bg +v.bg +w.bg +x.bg +y.bg +z.bg +0.bg +1.bg +2.bg +3.bg +4.bg +5.bg +6.bg +7.bg +8.bg +9.bg + +// bh : https://en.wikipedia.org/wiki/.bh +bh +com.bh +edu.bh +net.bh +org.bh +gov.bh + +// bi : https://en.wikipedia.org/wiki/.bi +// http://whois.nic.bi/ +bi +co.bi +com.bi +edu.bi +or.bi +org.bi + +// biz : https://en.wikipedia.org/wiki/.biz +biz + +// bj : https://en.wikipedia.org/wiki/.bj +bj +asso.bj +barreau.bj +gouv.bj + +// bm : http://www.bermudanic.bm/dnr-text.txt +bm +com.bm +edu.bm +gov.bm +net.bm +org.bm + +// bn : http://www.bnnic.bn/faqs +bn +com.bn +edu.bn +gov.bn +net.bn +org.bn + +// bo : https://nic.bo/delegacion2015.php#h-1.10 +bo +com.bo +edu.bo +gob.bo +int.bo +org.bo +net.bo +mil.bo +tv.bo +web.bo +// Social Domains +academia.bo +agro.bo +arte.bo +blog.bo +bolivia.bo +ciencia.bo +cooperativa.bo +democracia.bo +deporte.bo +ecologia.bo +economia.bo +empresa.bo +indigena.bo +industria.bo +info.bo +medicina.bo +movimiento.bo +musica.bo +natural.bo +nombre.bo +noticias.bo +patria.bo +politica.bo +profesional.bo +plurinacional.bo +pueblo.bo +revista.bo +salud.bo +tecnologia.bo +tksat.bo +transporte.bo +wiki.bo + +// br : http://registro.br/dominio/categoria.html +// Submitted by registry +br +9guacu.br +abc.br +adm.br +adv.br +agr.br +aju.br +am.br +anani.br +aparecida.br +arq.br +art.br +ato.br +b.br +barueri.br +belem.br +bhz.br +bio.br +blog.br +bmd.br +boavista.br +bsb.br +campinagrande.br +campinas.br +caxias.br +cim.br +cng.br +cnt.br +com.br +contagem.br +coop.br +cri.br +cuiaba.br +curitiba.br +def.br +ecn.br +eco.br +edu.br +emp.br +eng.br +esp.br +etc.br +eti.br +far.br +feira.br +flog.br +floripa.br +fm.br +fnd.br +fortal.br +fot.br +foz.br +fst.br +g12.br +ggf.br +goiania.br +gov.br +// gov.br 26 states + df https://en.wikipedia.org/wiki/States_of_Brazil +ac.gov.br +al.gov.br +am.gov.br +ap.gov.br +ba.gov.br +ce.gov.br +df.gov.br +es.gov.br +go.gov.br +ma.gov.br +mg.gov.br +ms.gov.br +mt.gov.br +pa.gov.br +pb.gov.br +pe.gov.br +pi.gov.br +pr.gov.br +rj.gov.br +rn.gov.br +ro.gov.br +rr.gov.br +rs.gov.br +sc.gov.br +se.gov.br +sp.gov.br +to.gov.br +gru.br +imb.br +ind.br +inf.br +jab.br +jampa.br +jdf.br +joinville.br +jor.br +jus.br +leg.br +lel.br +londrina.br +macapa.br +maceio.br +manaus.br +maringa.br +mat.br +med.br +mil.br +morena.br +mp.br +mus.br +natal.br +net.br +niteroi.br +*.nom.br +not.br +ntr.br +odo.br +ong.br +org.br +osasco.br +palmas.br +poa.br +ppg.br +pro.br +psc.br +psi.br +pvh.br +qsl.br +radio.br +rec.br +recife.br +ribeirao.br +rio.br +riobranco.br +riopreto.br +salvador.br +sampa.br +santamaria.br +santoandre.br +saobernardo.br +saogonca.br +sjc.br +slg.br +slz.br +sorocaba.br +srv.br +taxi.br +teo.br +the.br +tmp.br +trd.br +tur.br +tv.br +udi.br +vet.br +vix.br +vlog.br +wiki.br +zlg.br + +// bs : http://www.nic.bs/rules.html +bs +com.bs +net.bs +org.bs +edu.bs +gov.bs + +// bt : https://en.wikipedia.org/wiki/.bt +bt +com.bt +edu.bt +gov.bt +net.bt +org.bt + +// bv : No registrations at this time. +// Submitted by registry +bv + +// bw : https://en.wikipedia.org/wiki/.bw +// http://www.gobin.info/domainname/bw.doc +// list of other 2nd level tlds ? +bw +co.bw +org.bw + +// by : https://en.wikipedia.org/wiki/.by +// http://tld.by/rules_2006_en.html +// list of other 2nd level tlds ? +by +gov.by +mil.by +// Official information does not indicate that com.by is a reserved +// second-level domain, but it's being used as one (see www.google.com.by and +// www.yahoo.com.by, for example), so we list it here for safety's sake. +com.by + +// http://hoster.by/ +of.by + +// bz : https://en.wikipedia.org/wiki/.bz +// http://www.belizenic.bz/ +bz +com.bz +net.bz +org.bz +edu.bz +gov.bz + +// ca : https://en.wikipedia.org/wiki/.ca +ca +// ca geographical names +ab.ca +bc.ca +mb.ca +nb.ca +nf.ca +nl.ca +ns.ca +nt.ca +nu.ca +on.ca +pe.ca +qc.ca +sk.ca +yk.ca +// gc.ca: https://en.wikipedia.org/wiki/.gc.ca +// see also: http://registry.gc.ca/en/SubdomainFAQ +gc.ca + +// cat : https://en.wikipedia.org/wiki/.cat +cat + +// cc : https://en.wikipedia.org/wiki/.cc +cc + +// cd : https://en.wikipedia.org/wiki/.cd +// see also: https://www.nic.cd/domain/insertDomain_2.jsp?act=1 +cd +gov.cd + +// cf : https://en.wikipedia.org/wiki/.cf +cf + +// cg : https://en.wikipedia.org/wiki/.cg +cg + +// ch : https://en.wikipedia.org/wiki/.ch +ch + +// ci : https://en.wikipedia.org/wiki/.ci +// http://www.nic.ci/index.php?page=charte +ci +org.ci +or.ci +com.ci +co.ci +edu.ci +ed.ci +ac.ci +net.ci +go.ci +asso.ci +aéroport.ci +int.ci +presse.ci +md.ci +gouv.ci + +// ck : https://en.wikipedia.org/wiki/.ck +*.ck +!www.ck + +// cl : https://en.wikipedia.org/wiki/.cl +cl +gov.cl +gob.cl +co.cl +mil.cl + +// cm : https://en.wikipedia.org/wiki/.cm plus bug 981927 +cm +co.cm +com.cm +gov.cm +net.cm + +// cn : https://en.wikipedia.org/wiki/.cn +// Submitted by registry +cn +ac.cn +com.cn +edu.cn +gov.cn +net.cn +org.cn +mil.cn +公司.cn +网络.cn +網絡.cn +// cn geographic names +ah.cn +bj.cn +cq.cn +fj.cn +gd.cn +gs.cn +gz.cn +gx.cn +ha.cn +hb.cn +he.cn +hi.cn +hl.cn +hn.cn +jl.cn +js.cn +jx.cn +ln.cn +nm.cn +nx.cn +qh.cn +sc.cn +sd.cn +sh.cn +sn.cn +sx.cn +tj.cn +xj.cn +xz.cn +yn.cn +zj.cn +hk.cn +mo.cn +tw.cn + +// co : https://en.wikipedia.org/wiki/.co +// Submitted by registry +co +arts.co +com.co +edu.co +firm.co +gov.co +info.co +int.co +mil.co +net.co +nom.co +org.co +rec.co +web.co + +// com : https://en.wikipedia.org/wiki/.com +com + +// coop : https://en.wikipedia.org/wiki/.coop +coop + +// cr : http://www.nic.cr/niccr_publico/showRegistroDominiosScreen.do +cr +ac.cr +co.cr +ed.cr +fi.cr +go.cr +or.cr +sa.cr + +// cu : https://en.wikipedia.org/wiki/.cu +cu +com.cu +edu.cu +org.cu +net.cu +gov.cu +inf.cu + +// cv : https://en.wikipedia.org/wiki/.cv +cv + +// cw : http://www.una.cw/cw_registry/ +// Confirmed by registry 2013-03-26 +cw +com.cw +edu.cw +net.cw +org.cw + +// cx : https://en.wikipedia.org/wiki/.cx +// list of other 2nd level tlds ? +cx +gov.cx + +// cy : http://www.nic.cy/ +// Submitted by registry Panayiotou Fotia +cy +ac.cy +biz.cy +com.cy +ekloges.cy +gov.cy +ltd.cy +name.cy +net.cy +org.cy +parliament.cy +press.cy +pro.cy +tm.cy + +// cz : https://en.wikipedia.org/wiki/.cz +cz + +// de : https://en.wikipedia.org/wiki/.de +// Confirmed by registry (with technical +// reservations) 2008-07-01 +de + +// dj : https://en.wikipedia.org/wiki/.dj +dj + +// dk : https://en.wikipedia.org/wiki/.dk +// Confirmed by registry 2008-06-17 +dk + +// dm : https://en.wikipedia.org/wiki/.dm +dm +com.dm +net.dm +org.dm +edu.dm +gov.dm + +// do : https://en.wikipedia.org/wiki/.do +do +art.do +com.do +edu.do +gob.do +gov.do +mil.do +net.do +org.do +sld.do +web.do + +// dz : https://en.wikipedia.org/wiki/.dz +dz +com.dz +org.dz +net.dz +gov.dz +edu.dz +asso.dz +pol.dz +art.dz + +// ec : http://www.nic.ec/reg/paso1.asp +// Submitted by registry +ec +com.ec +info.ec +net.ec +fin.ec +k12.ec +med.ec +pro.ec +org.ec +edu.ec +gov.ec +gob.ec +mil.ec + +// edu : https://en.wikipedia.org/wiki/.edu +edu + +// ee : http://www.eenet.ee/EENet/dom_reeglid.html#lisa_B +ee +edu.ee +gov.ee +riik.ee +lib.ee +med.ee +com.ee +pri.ee +aip.ee +org.ee +fie.ee + +// eg : https://en.wikipedia.org/wiki/.eg +eg +com.eg +edu.eg +eun.eg +gov.eg +mil.eg +name.eg +net.eg +org.eg +sci.eg + +// er : https://en.wikipedia.org/wiki/.er +*.er + +// es : https://www.nic.es/site_ingles/ingles/dominios/index.html +es +com.es +nom.es +org.es +gob.es +edu.es + +// et : https://en.wikipedia.org/wiki/.et +et +com.et +gov.et +org.et +edu.et +biz.et +name.et +info.et +net.et + +// eu : https://en.wikipedia.org/wiki/.eu +eu + +// fi : https://en.wikipedia.org/wiki/.fi +fi +// aland.fi : https://en.wikipedia.org/wiki/.ax +// This domain is being phased out in favor of .ax. As there are still many +// domains under aland.fi, we still keep it on the list until aland.fi is +// completely removed. +// TODO: Check for updates (expected to be phased out around Q1/2009) +aland.fi + +// fj : https://en.wikipedia.org/wiki/.fj +*.fj + +// fk : https://en.wikipedia.org/wiki/.fk +*.fk + +// fm : https://en.wikipedia.org/wiki/.fm +fm + +// fo : https://en.wikipedia.org/wiki/.fo +fo + +// fr : http://www.afnic.fr/ +// domaines descriptifs : http://www.afnic.fr/obtenir/chartes/nommage-fr/annexe-descriptifs +fr +com.fr +asso.fr +nom.fr +prd.fr +presse.fr +tm.fr +// domaines sectoriels : http://www.afnic.fr/obtenir/chartes/nommage-fr/annexe-sectoriels +aeroport.fr +assedic.fr +avocat.fr +avoues.fr +cci.fr +chambagri.fr +chirurgiens-dentistes.fr +experts-comptables.fr +geometre-expert.fr +gouv.fr +greta.fr +huissier-justice.fr +medecin.fr +notaires.fr +pharmacien.fr +port.fr +veterinaire.fr + +// ga : https://en.wikipedia.org/wiki/.ga +ga + +// gb : This registry is effectively dormant +// Submitted by registry +gb + +// gd : https://en.wikipedia.org/wiki/.gd +gd + +// ge : http://www.nic.net.ge/policy_en.pdf +ge +com.ge +edu.ge +gov.ge +org.ge +mil.ge +net.ge +pvt.ge + +// gf : https://en.wikipedia.org/wiki/.gf +gf + +// gg : http://www.channelisles.net/register-domains/ +// Confirmed by registry 2013-11-28 +gg +co.gg +net.gg +org.gg + +// gh : https://en.wikipedia.org/wiki/.gh +// see also: http://www.nic.gh/reg_now.php +// Although domains directly at second level are not possible at the moment, +// they have been possible for some time and may come back. +gh +com.gh +edu.gh +gov.gh +org.gh +mil.gh + +// gi : http://www.nic.gi/rules.html +gi +com.gi +ltd.gi +gov.gi +mod.gi +edu.gi +org.gi + +// gl : https://en.wikipedia.org/wiki/.gl +// http://nic.gl +gl +co.gl +com.gl +edu.gl +net.gl +org.gl + +// gm : http://www.nic.gm/htmlpages%5Cgm-policy.htm +gm + +// gn : http://psg.com/dns/gn/gn.txt +// Submitted by registry +gn +ac.gn +com.gn +edu.gn +gov.gn +org.gn +net.gn + +// gov : https://en.wikipedia.org/wiki/.gov +gov + +// gp : http://www.nic.gp/index.php?lang=en +gp +com.gp +net.gp +mobi.gp +edu.gp +org.gp +asso.gp + +// gq : https://en.wikipedia.org/wiki/.gq +gq + +// gr : https://grweb.ics.forth.gr/english/1617-B-2005.html +// Submitted by registry +gr +com.gr +edu.gr +net.gr +org.gr +gov.gr + +// gs : https://en.wikipedia.org/wiki/.gs +gs + +// gt : http://www.gt/politicas_de_registro.html +gt +com.gt +edu.gt +gob.gt +ind.gt +mil.gt +net.gt +org.gt + +// gu : http://gadao.gov.gu/register.html +// University of Guam : https://www.uog.edu +// Submitted by uognoc@triton.uog.edu +gu +com.gu +edu.gu +gov.gu +guam.gu +info.gu +net.gu +org.gu +web.gu + +// gw : https://en.wikipedia.org/wiki/.gw +gw + +// gy : https://en.wikipedia.org/wiki/.gy +// http://registry.gy/ +gy +co.gy +com.gy +edu.gy +gov.gy +net.gy +org.gy + +// hk : https://www.hkirc.hk +// Submitted by registry +hk +com.hk +edu.hk +gov.hk +idv.hk +net.hk +org.hk +公司.hk +教育.hk +敎育.hk +政府.hk +個人.hk +个人.hk +箇人.hk +網络.hk +网络.hk +组織.hk +網絡.hk +网絡.hk +组织.hk +組織.hk +組织.hk + +// hm : https://en.wikipedia.org/wiki/.hm +hm + +// hn : http://www.nic.hn/politicas/ps02,,05.html +hn +com.hn +edu.hn +org.hn +net.hn +mil.hn +gob.hn + +// hr : http://www.dns.hr/documents/pdf/HRTLD-regulations.pdf +hr +iz.hr +from.hr +name.hr +com.hr + +// ht : http://www.nic.ht/info/charte.cfm +ht +com.ht +shop.ht +firm.ht +info.ht +adult.ht +net.ht +pro.ht +org.ht +med.ht +art.ht +coop.ht +pol.ht +asso.ht +edu.ht +rel.ht +gouv.ht +perso.ht + +// hu : http://www.domain.hu/domain/English/sld.html +// Confirmed by registry 2008-06-12 +hu +co.hu +info.hu +org.hu +priv.hu +sport.hu +tm.hu +2000.hu +agrar.hu +bolt.hu +casino.hu +city.hu +erotica.hu +erotika.hu +film.hu +forum.hu +games.hu +hotel.hu +ingatlan.hu +jogasz.hu +konyvelo.hu +lakas.hu +media.hu +news.hu +reklam.hu +sex.hu +shop.hu +suli.hu +szex.hu +tozsde.hu +utazas.hu +video.hu + +// id : https://pandi.id/en/domain/registration-requirements/ +id +ac.id +biz.id +co.id +desa.id +go.id +mil.id +my.id +net.id +or.id +ponpes.id +sch.id +web.id + +// ie : https://en.wikipedia.org/wiki/.ie +ie +gov.ie + +// il : http://www.isoc.org.il/domains/ +il +ac.il +co.il +gov.il +idf.il +k12.il +muni.il +net.il +org.il + +// im : https://www.nic.im/ +// Submitted by registry +im +ac.im +co.im +com.im +ltd.co.im +net.im +org.im +plc.co.im +tt.im +tv.im + +// in : https://en.wikipedia.org/wiki/.in +// see also: https://registry.in/Policies +// Please note, that nic.in is not an official eTLD, but used by most +// government institutions. +in +co.in +firm.in +net.in +org.in +gen.in +ind.in +nic.in +ac.in +edu.in +res.in +gov.in +mil.in + +// info : https://en.wikipedia.org/wiki/.info +info + +// int : https://en.wikipedia.org/wiki/.int +// Confirmed by registry 2008-06-18 +int +eu.int + +// io : http://www.nic.io/rules.html +// list of other 2nd level tlds ? +io +com.io + +// iq : http://www.cmc.iq/english/iq/iqregister1.htm +iq +gov.iq +edu.iq +mil.iq +com.iq +org.iq +net.iq + +// ir : http://www.nic.ir/Terms_and_Conditions_ir,_Appendix_1_Domain_Rules +// Also see http://www.nic.ir/Internationalized_Domain_Names +// Two .ir entries added at request of , 2010-04-16 +ir +ac.ir +co.ir +gov.ir +id.ir +net.ir +org.ir +sch.ir +// xn--mgba3a4f16a.ir (.ir, Persian YEH) +ایران.ir +// xn--mgba3a4fra.ir (.ir, Arabic YEH) +ايران.ir + +// is : http://www.isnic.is/domain/rules.php +// Confirmed by registry 2008-12-06 +is +net.is +com.is +edu.is +gov.is +org.is +int.is + +// it : https://en.wikipedia.org/wiki/.it +it +gov.it +edu.it +// Reserved geo-names (regions and provinces): +// http://www.nic.it/sites/default/files/docs/Regulation_assignation_v7.1.pdf +// Regions +abr.it +abruzzo.it +aosta-valley.it +aostavalley.it +bas.it +basilicata.it +cal.it +calabria.it +cam.it +campania.it +emilia-romagna.it +emiliaromagna.it +emr.it +friuli-v-giulia.it +friuli-ve-giulia.it +friuli-vegiulia.it +friuli-venezia-giulia.it +friuli-veneziagiulia.it +friuli-vgiulia.it +friuliv-giulia.it +friulive-giulia.it +friulivegiulia.it +friulivenezia-giulia.it +friuliveneziagiulia.it +friulivgiulia.it +fvg.it +laz.it +lazio.it +lig.it +liguria.it +lom.it +lombardia.it +lombardy.it +lucania.it +mar.it +marche.it +mol.it +molise.it +piedmont.it +piemonte.it +pmn.it +pug.it +puglia.it +sar.it +sardegna.it +sardinia.it +sic.it +sicilia.it +sicily.it +taa.it +tos.it +toscana.it +trentin-sud-tirol.it +trentin-süd-tirol.it +trentin-sudtirol.it +trentin-südtirol.it +trentin-sued-tirol.it +trentin-suedtirol.it +trentino-a-adige.it +trentino-aadige.it +trentino-alto-adige.it +trentino-altoadige.it +trentino-s-tirol.it +trentino-stirol.it +trentino-sud-tirol.it +trentino-süd-tirol.it +trentino-sudtirol.it +trentino-südtirol.it +trentino-sued-tirol.it +trentino-suedtirol.it +trentino.it +trentinoa-adige.it +trentinoaadige.it +trentinoalto-adige.it +trentinoaltoadige.it +trentinos-tirol.it +trentinostirol.it +trentinosud-tirol.it +trentinosüd-tirol.it +trentinosudtirol.it +trentinosüdtirol.it +trentinosued-tirol.it +trentinosuedtirol.it +trentinsud-tirol.it +trentinsüd-tirol.it +trentinsudtirol.it +trentinsüdtirol.it +trentinsued-tirol.it +trentinsuedtirol.it +tuscany.it +umb.it +umbria.it +val-d-aosta.it +val-daosta.it +vald-aosta.it +valdaosta.it +valle-aosta.it +valle-d-aosta.it +valle-daosta.it +valleaosta.it +valled-aosta.it +valledaosta.it +vallee-aoste.it +vallée-aoste.it +vallee-d-aoste.it +vallée-d-aoste.it +valleeaoste.it +valléeaoste.it +valleedaoste.it +valléedaoste.it +vao.it +vda.it +ven.it +veneto.it +// Provinces +ag.it +agrigento.it +al.it +alessandria.it +alto-adige.it +altoadige.it +an.it +ancona.it +andria-barletta-trani.it +andria-trani-barletta.it +andriabarlettatrani.it +andriatranibarletta.it +ao.it +aosta.it +aoste.it +ap.it +aq.it +aquila.it +ar.it +arezzo.it +ascoli-piceno.it +ascolipiceno.it +asti.it +at.it +av.it +avellino.it +ba.it +balsan-sudtirol.it +balsan-südtirol.it +balsan-suedtirol.it +balsan.it +bari.it +barletta-trani-andria.it +barlettatraniandria.it +belluno.it +benevento.it +bergamo.it +bg.it +bi.it +biella.it +bl.it +bn.it +bo.it +bologna.it +bolzano-altoadige.it +bolzano.it +bozen-sudtirol.it +bozen-südtirol.it +bozen-suedtirol.it +bozen.it +br.it +brescia.it +brindisi.it +bs.it +bt.it +bulsan-sudtirol.it +bulsan-südtirol.it +bulsan-suedtirol.it +bulsan.it +bz.it +ca.it +cagliari.it +caltanissetta.it +campidano-medio.it +campidanomedio.it +campobasso.it +carbonia-iglesias.it +carboniaiglesias.it +carrara-massa.it +carraramassa.it +caserta.it +catania.it +catanzaro.it +cb.it +ce.it +cesena-forli.it +cesena-forlì.it +cesenaforli.it +cesenaforlì.it +ch.it +chieti.it +ci.it +cl.it +cn.it +co.it +como.it +cosenza.it +cr.it +cremona.it +crotone.it +cs.it +ct.it +cuneo.it +cz.it +dell-ogliastra.it +dellogliastra.it +en.it +enna.it +fc.it +fe.it +fermo.it +ferrara.it +fg.it +fi.it +firenze.it +florence.it +fm.it +foggia.it +forli-cesena.it +forlì-cesena.it +forlicesena.it +forlìcesena.it +fr.it +frosinone.it +ge.it +genoa.it +genova.it +go.it +gorizia.it +gr.it +grosseto.it +iglesias-carbonia.it +iglesiascarbonia.it +im.it +imperia.it +is.it +isernia.it +kr.it +la-spezia.it +laquila.it +laspezia.it +latina.it +lc.it +le.it +lecce.it +lecco.it +li.it +livorno.it +lo.it +lodi.it +lt.it +lu.it +lucca.it +macerata.it +mantova.it +massa-carrara.it +massacarrara.it +matera.it +mb.it +mc.it +me.it +medio-campidano.it +mediocampidano.it +messina.it +mi.it +milan.it +milano.it +mn.it +mo.it +modena.it +monza-brianza.it +monza-e-della-brianza.it +monza.it +monzabrianza.it +monzaebrianza.it +monzaedellabrianza.it +ms.it +mt.it +na.it +naples.it +napoli.it +no.it +novara.it +nu.it +nuoro.it +og.it +ogliastra.it +olbia-tempio.it +olbiatempio.it +or.it +oristano.it +ot.it +pa.it +padova.it +padua.it +palermo.it +parma.it +pavia.it +pc.it +pd.it +pe.it +perugia.it +pesaro-urbino.it +pesarourbino.it +pescara.it +pg.it +pi.it +piacenza.it +pisa.it +pistoia.it +pn.it +po.it +pordenone.it +potenza.it +pr.it +prato.it +pt.it +pu.it +pv.it +pz.it +ra.it +ragusa.it +ravenna.it +rc.it +re.it +reggio-calabria.it +reggio-emilia.it +reggiocalabria.it +reggioemilia.it +rg.it +ri.it +rieti.it +rimini.it +rm.it +rn.it +ro.it +roma.it +rome.it +rovigo.it +sa.it +salerno.it +sassari.it +savona.it +si.it +siena.it +siracusa.it +so.it +sondrio.it +sp.it +sr.it +ss.it +suedtirol.it +südtirol.it +sv.it +ta.it +taranto.it +te.it +tempio-olbia.it +tempioolbia.it +teramo.it +terni.it +tn.it +to.it +torino.it +tp.it +tr.it +trani-andria-barletta.it +trani-barletta-andria.it +traniandriabarletta.it +tranibarlettaandria.it +trapani.it +trento.it +treviso.it +trieste.it +ts.it +turin.it +tv.it +ud.it +udine.it +urbino-pesaro.it +urbinopesaro.it +va.it +varese.it +vb.it +vc.it +ve.it +venezia.it +venice.it +verbania.it +vercelli.it +verona.it +vi.it +vibo-valentia.it +vibovalentia.it +vicenza.it +viterbo.it +vr.it +vs.it +vt.it +vv.it + +// je : http://www.channelisles.net/register-domains/ +// Confirmed by registry 2013-11-28 +je +co.je +net.je +org.je + +// jm : http://www.com.jm/register.html +*.jm + +// jo : http://www.dns.jo/Registration_policy.aspx +jo +com.jo +org.jo +net.jo +edu.jo +sch.jo +gov.jo +mil.jo +name.jo + +// jobs : https://en.wikipedia.org/wiki/.jobs +jobs + +// jp : https://en.wikipedia.org/wiki/.jp +// http://jprs.co.jp/en/jpdomain.html +// Submitted by registry +jp +// jp organizational type names +ac.jp +ad.jp +co.jp +ed.jp +go.jp +gr.jp +lg.jp +ne.jp +or.jp +// jp prefecture type names +aichi.jp +akita.jp +aomori.jp +chiba.jp +ehime.jp +fukui.jp +fukuoka.jp +fukushima.jp +gifu.jp +gunma.jp +hiroshima.jp +hokkaido.jp +hyogo.jp +ibaraki.jp +ishikawa.jp +iwate.jp +kagawa.jp +kagoshima.jp +kanagawa.jp +kochi.jp +kumamoto.jp +kyoto.jp +mie.jp +miyagi.jp +miyazaki.jp +nagano.jp +nagasaki.jp +nara.jp +niigata.jp +oita.jp +okayama.jp +okinawa.jp +osaka.jp +saga.jp +saitama.jp +shiga.jp +shimane.jp +shizuoka.jp +tochigi.jp +tokushima.jp +tokyo.jp +tottori.jp +toyama.jp +wakayama.jp +yamagata.jp +yamaguchi.jp +yamanashi.jp +栃木.jp +愛知.jp +愛媛.jp +兵庫.jp +熊本.jp +茨城.jp +北海道.jp +千葉.jp +和歌山.jp +長崎.jp +長野.jp +新潟.jp +青森.jp +静岡.jp +東京.jp +石川.jp +埼玉.jp +三重.jp +京都.jp +佐賀.jp +大分.jp +大阪.jp +奈良.jp +宮城.jp +宮崎.jp +富山.jp +山口.jp +山形.jp +山梨.jp +岩手.jp +岐阜.jp +岡山.jp +島根.jp +広島.jp +徳島.jp +沖縄.jp +滋賀.jp +神奈川.jp +福井.jp +福岡.jp +福島.jp +秋田.jp +群馬.jp +香川.jp +高知.jp +鳥取.jp +鹿児島.jp +// jp geographic type names +// http://jprs.jp/doc/rule/saisoku-1.html +*.kawasaki.jp +*.kitakyushu.jp +*.kobe.jp +*.nagoya.jp +*.sapporo.jp +*.sendai.jp +*.yokohama.jp +!city.kawasaki.jp +!city.kitakyushu.jp +!city.kobe.jp +!city.nagoya.jp +!city.sapporo.jp +!city.sendai.jp +!city.yokohama.jp +// 4th level registration +aisai.aichi.jp +ama.aichi.jp +anjo.aichi.jp +asuke.aichi.jp +chiryu.aichi.jp +chita.aichi.jp +fuso.aichi.jp +gamagori.aichi.jp +handa.aichi.jp +hazu.aichi.jp +hekinan.aichi.jp +higashiura.aichi.jp +ichinomiya.aichi.jp +inazawa.aichi.jp +inuyama.aichi.jp +isshiki.aichi.jp +iwakura.aichi.jp +kanie.aichi.jp +kariya.aichi.jp +kasugai.aichi.jp +kira.aichi.jp +kiyosu.aichi.jp +komaki.aichi.jp +konan.aichi.jp +kota.aichi.jp +mihama.aichi.jp +miyoshi.aichi.jp +nishio.aichi.jp +nisshin.aichi.jp +obu.aichi.jp +oguchi.aichi.jp +oharu.aichi.jp +okazaki.aichi.jp +owariasahi.aichi.jp +seto.aichi.jp +shikatsu.aichi.jp +shinshiro.aichi.jp +shitara.aichi.jp +tahara.aichi.jp +takahama.aichi.jp +tobishima.aichi.jp +toei.aichi.jp +togo.aichi.jp +tokai.aichi.jp +tokoname.aichi.jp +toyoake.aichi.jp +toyohashi.aichi.jp +toyokawa.aichi.jp +toyone.aichi.jp +toyota.aichi.jp +tsushima.aichi.jp +yatomi.aichi.jp +akita.akita.jp +daisen.akita.jp +fujisato.akita.jp +gojome.akita.jp +hachirogata.akita.jp +happou.akita.jp +higashinaruse.akita.jp +honjo.akita.jp +honjyo.akita.jp +ikawa.akita.jp +kamikoani.akita.jp +kamioka.akita.jp +katagami.akita.jp +kazuno.akita.jp +kitaakita.akita.jp +kosaka.akita.jp +kyowa.akita.jp +misato.akita.jp +mitane.akita.jp +moriyoshi.akita.jp +nikaho.akita.jp +noshiro.akita.jp +odate.akita.jp +oga.akita.jp +ogata.akita.jp +semboku.akita.jp +yokote.akita.jp +yurihonjo.akita.jp +aomori.aomori.jp +gonohe.aomori.jp +hachinohe.aomori.jp +hashikami.aomori.jp +hiranai.aomori.jp +hirosaki.aomori.jp +itayanagi.aomori.jp +kuroishi.aomori.jp +misawa.aomori.jp +mutsu.aomori.jp +nakadomari.aomori.jp +noheji.aomori.jp +oirase.aomori.jp +owani.aomori.jp +rokunohe.aomori.jp +sannohe.aomori.jp +shichinohe.aomori.jp +shingo.aomori.jp +takko.aomori.jp +towada.aomori.jp +tsugaru.aomori.jp +tsuruta.aomori.jp +abiko.chiba.jp +asahi.chiba.jp +chonan.chiba.jp +chosei.chiba.jp +choshi.chiba.jp +chuo.chiba.jp +funabashi.chiba.jp +futtsu.chiba.jp +hanamigawa.chiba.jp +ichihara.chiba.jp +ichikawa.chiba.jp +ichinomiya.chiba.jp +inzai.chiba.jp +isumi.chiba.jp +kamagaya.chiba.jp +kamogawa.chiba.jp +kashiwa.chiba.jp +katori.chiba.jp +katsuura.chiba.jp +kimitsu.chiba.jp +kisarazu.chiba.jp +kozaki.chiba.jp +kujukuri.chiba.jp +kyonan.chiba.jp +matsudo.chiba.jp +midori.chiba.jp +mihama.chiba.jp +minamiboso.chiba.jp +mobara.chiba.jp +mutsuzawa.chiba.jp +nagara.chiba.jp +nagareyama.chiba.jp +narashino.chiba.jp +narita.chiba.jp +noda.chiba.jp +oamishirasato.chiba.jp +omigawa.chiba.jp +onjuku.chiba.jp +otaki.chiba.jp +sakae.chiba.jp +sakura.chiba.jp +shimofusa.chiba.jp +shirako.chiba.jp +shiroi.chiba.jp +shisui.chiba.jp +sodegaura.chiba.jp +sosa.chiba.jp +tako.chiba.jp +tateyama.chiba.jp +togane.chiba.jp +tohnosho.chiba.jp +tomisato.chiba.jp +urayasu.chiba.jp +yachimata.chiba.jp +yachiyo.chiba.jp +yokaichiba.chiba.jp +yokoshibahikari.chiba.jp +yotsukaido.chiba.jp +ainan.ehime.jp +honai.ehime.jp +ikata.ehime.jp +imabari.ehime.jp +iyo.ehime.jp +kamijima.ehime.jp +kihoku.ehime.jp +kumakogen.ehime.jp +masaki.ehime.jp +matsuno.ehime.jp +matsuyama.ehime.jp +namikata.ehime.jp +niihama.ehime.jp +ozu.ehime.jp +saijo.ehime.jp +seiyo.ehime.jp +shikokuchuo.ehime.jp +tobe.ehime.jp +toon.ehime.jp +uchiko.ehime.jp +uwajima.ehime.jp +yawatahama.ehime.jp +echizen.fukui.jp +eiheiji.fukui.jp +fukui.fukui.jp +ikeda.fukui.jp +katsuyama.fukui.jp +mihama.fukui.jp +minamiechizen.fukui.jp +obama.fukui.jp +ohi.fukui.jp +ono.fukui.jp +sabae.fukui.jp +sakai.fukui.jp +takahama.fukui.jp +tsuruga.fukui.jp +wakasa.fukui.jp +ashiya.fukuoka.jp +buzen.fukuoka.jp +chikugo.fukuoka.jp +chikuho.fukuoka.jp +chikujo.fukuoka.jp +chikushino.fukuoka.jp +chikuzen.fukuoka.jp +chuo.fukuoka.jp +dazaifu.fukuoka.jp +fukuchi.fukuoka.jp +hakata.fukuoka.jp +higashi.fukuoka.jp +hirokawa.fukuoka.jp +hisayama.fukuoka.jp +iizuka.fukuoka.jp +inatsuki.fukuoka.jp +kaho.fukuoka.jp +kasuga.fukuoka.jp +kasuya.fukuoka.jp +kawara.fukuoka.jp +keisen.fukuoka.jp +koga.fukuoka.jp +kurate.fukuoka.jp +kurogi.fukuoka.jp +kurume.fukuoka.jp +minami.fukuoka.jp +miyako.fukuoka.jp +miyama.fukuoka.jp +miyawaka.fukuoka.jp +mizumaki.fukuoka.jp +munakata.fukuoka.jp +nakagawa.fukuoka.jp +nakama.fukuoka.jp +nishi.fukuoka.jp +nogata.fukuoka.jp +ogori.fukuoka.jp +okagaki.fukuoka.jp +okawa.fukuoka.jp +oki.fukuoka.jp +omuta.fukuoka.jp +onga.fukuoka.jp +onojo.fukuoka.jp +oto.fukuoka.jp +saigawa.fukuoka.jp +sasaguri.fukuoka.jp +shingu.fukuoka.jp +shinyoshitomi.fukuoka.jp +shonai.fukuoka.jp +soeda.fukuoka.jp +sue.fukuoka.jp +tachiarai.fukuoka.jp +tagawa.fukuoka.jp +takata.fukuoka.jp +toho.fukuoka.jp +toyotsu.fukuoka.jp +tsuiki.fukuoka.jp +ukiha.fukuoka.jp +umi.fukuoka.jp +usui.fukuoka.jp +yamada.fukuoka.jp +yame.fukuoka.jp +yanagawa.fukuoka.jp +yukuhashi.fukuoka.jp +aizubange.fukushima.jp +aizumisato.fukushima.jp +aizuwakamatsu.fukushima.jp +asakawa.fukushima.jp +bandai.fukushima.jp +date.fukushima.jp +fukushima.fukushima.jp +furudono.fukushima.jp +futaba.fukushima.jp +hanawa.fukushima.jp +higashi.fukushima.jp +hirata.fukushima.jp +hirono.fukushima.jp +iitate.fukushima.jp +inawashiro.fukushima.jp +ishikawa.fukushima.jp +iwaki.fukushima.jp +izumizaki.fukushima.jp +kagamiishi.fukushima.jp +kaneyama.fukushima.jp +kawamata.fukushima.jp +kitakata.fukushima.jp +kitashiobara.fukushima.jp +koori.fukushima.jp +koriyama.fukushima.jp +kunimi.fukushima.jp +miharu.fukushima.jp +mishima.fukushima.jp +namie.fukushima.jp +nango.fukushima.jp +nishiaizu.fukushima.jp +nishigo.fukushima.jp +okuma.fukushima.jp +omotego.fukushima.jp +ono.fukushima.jp +otama.fukushima.jp +samegawa.fukushima.jp +shimogo.fukushima.jp +shirakawa.fukushima.jp +showa.fukushima.jp +soma.fukushima.jp +sukagawa.fukushima.jp +taishin.fukushima.jp +tamakawa.fukushima.jp +tanagura.fukushima.jp +tenei.fukushima.jp +yabuki.fukushima.jp +yamato.fukushima.jp +yamatsuri.fukushima.jp +yanaizu.fukushima.jp +yugawa.fukushima.jp +anpachi.gifu.jp +ena.gifu.jp +gifu.gifu.jp +ginan.gifu.jp +godo.gifu.jp +gujo.gifu.jp +hashima.gifu.jp +hichiso.gifu.jp +hida.gifu.jp +higashishirakawa.gifu.jp +ibigawa.gifu.jp +ikeda.gifu.jp +kakamigahara.gifu.jp +kani.gifu.jp +kasahara.gifu.jp +kasamatsu.gifu.jp +kawaue.gifu.jp +kitagata.gifu.jp +mino.gifu.jp +minokamo.gifu.jp +mitake.gifu.jp +mizunami.gifu.jp +motosu.gifu.jp +nakatsugawa.gifu.jp +ogaki.gifu.jp +sakahogi.gifu.jp +seki.gifu.jp +sekigahara.gifu.jp +shirakawa.gifu.jp +tajimi.gifu.jp +takayama.gifu.jp +tarui.gifu.jp +toki.gifu.jp +tomika.gifu.jp +wanouchi.gifu.jp +yamagata.gifu.jp +yaotsu.gifu.jp +yoro.gifu.jp +annaka.gunma.jp +chiyoda.gunma.jp +fujioka.gunma.jp +higashiagatsuma.gunma.jp +isesaki.gunma.jp +itakura.gunma.jp +kanna.gunma.jp +kanra.gunma.jp +katashina.gunma.jp +kawaba.gunma.jp +kiryu.gunma.jp +kusatsu.gunma.jp +maebashi.gunma.jp +meiwa.gunma.jp +midori.gunma.jp +minakami.gunma.jp +naganohara.gunma.jp +nakanojo.gunma.jp +nanmoku.gunma.jp +numata.gunma.jp +oizumi.gunma.jp +ora.gunma.jp +ota.gunma.jp +shibukawa.gunma.jp +shimonita.gunma.jp +shinto.gunma.jp +showa.gunma.jp +takasaki.gunma.jp +takayama.gunma.jp +tamamura.gunma.jp +tatebayashi.gunma.jp +tomioka.gunma.jp +tsukiyono.gunma.jp +tsumagoi.gunma.jp +ueno.gunma.jp +yoshioka.gunma.jp +asaminami.hiroshima.jp +daiwa.hiroshima.jp +etajima.hiroshima.jp +fuchu.hiroshima.jp +fukuyama.hiroshima.jp +hatsukaichi.hiroshima.jp +higashihiroshima.hiroshima.jp +hongo.hiroshima.jp +jinsekikogen.hiroshima.jp +kaita.hiroshima.jp +kui.hiroshima.jp +kumano.hiroshima.jp +kure.hiroshima.jp +mihara.hiroshima.jp +miyoshi.hiroshima.jp +naka.hiroshima.jp +onomichi.hiroshima.jp +osakikamijima.hiroshima.jp +otake.hiroshima.jp +saka.hiroshima.jp +sera.hiroshima.jp +seranishi.hiroshima.jp +shinichi.hiroshima.jp +shobara.hiroshima.jp +takehara.hiroshima.jp +abashiri.hokkaido.jp +abira.hokkaido.jp +aibetsu.hokkaido.jp +akabira.hokkaido.jp +akkeshi.hokkaido.jp +asahikawa.hokkaido.jp +ashibetsu.hokkaido.jp +ashoro.hokkaido.jp +assabu.hokkaido.jp +atsuma.hokkaido.jp +bibai.hokkaido.jp +biei.hokkaido.jp +bifuka.hokkaido.jp +bihoro.hokkaido.jp +biratori.hokkaido.jp +chippubetsu.hokkaido.jp +chitose.hokkaido.jp +date.hokkaido.jp +ebetsu.hokkaido.jp +embetsu.hokkaido.jp +eniwa.hokkaido.jp +erimo.hokkaido.jp +esan.hokkaido.jp +esashi.hokkaido.jp +fukagawa.hokkaido.jp +fukushima.hokkaido.jp +furano.hokkaido.jp +furubira.hokkaido.jp +haboro.hokkaido.jp +hakodate.hokkaido.jp +hamatonbetsu.hokkaido.jp +hidaka.hokkaido.jp +higashikagura.hokkaido.jp +higashikawa.hokkaido.jp +hiroo.hokkaido.jp +hokuryu.hokkaido.jp +hokuto.hokkaido.jp +honbetsu.hokkaido.jp +horokanai.hokkaido.jp +horonobe.hokkaido.jp +ikeda.hokkaido.jp +imakane.hokkaido.jp +ishikari.hokkaido.jp +iwamizawa.hokkaido.jp +iwanai.hokkaido.jp +kamifurano.hokkaido.jp +kamikawa.hokkaido.jp +kamishihoro.hokkaido.jp +kamisunagawa.hokkaido.jp +kamoenai.hokkaido.jp +kayabe.hokkaido.jp +kembuchi.hokkaido.jp +kikonai.hokkaido.jp +kimobetsu.hokkaido.jp +kitahiroshima.hokkaido.jp +kitami.hokkaido.jp +kiyosato.hokkaido.jp +koshimizu.hokkaido.jp +kunneppu.hokkaido.jp +kuriyama.hokkaido.jp +kuromatsunai.hokkaido.jp +kushiro.hokkaido.jp +kutchan.hokkaido.jp +kyowa.hokkaido.jp +mashike.hokkaido.jp +matsumae.hokkaido.jp +mikasa.hokkaido.jp +minamifurano.hokkaido.jp +mombetsu.hokkaido.jp +moseushi.hokkaido.jp +mukawa.hokkaido.jp +muroran.hokkaido.jp +naie.hokkaido.jp +nakagawa.hokkaido.jp +nakasatsunai.hokkaido.jp +nakatombetsu.hokkaido.jp +nanae.hokkaido.jp +nanporo.hokkaido.jp +nayoro.hokkaido.jp +nemuro.hokkaido.jp +niikappu.hokkaido.jp +niki.hokkaido.jp +nishiokoppe.hokkaido.jp +noboribetsu.hokkaido.jp +numata.hokkaido.jp +obihiro.hokkaido.jp +obira.hokkaido.jp +oketo.hokkaido.jp +okoppe.hokkaido.jp +otaru.hokkaido.jp +otobe.hokkaido.jp +otofuke.hokkaido.jp +otoineppu.hokkaido.jp +oumu.hokkaido.jp +ozora.hokkaido.jp +pippu.hokkaido.jp +rankoshi.hokkaido.jp +rebun.hokkaido.jp +rikubetsu.hokkaido.jp +rishiri.hokkaido.jp +rishirifuji.hokkaido.jp +saroma.hokkaido.jp +sarufutsu.hokkaido.jp +shakotan.hokkaido.jp +shari.hokkaido.jp +shibecha.hokkaido.jp +shibetsu.hokkaido.jp +shikabe.hokkaido.jp +shikaoi.hokkaido.jp +shimamaki.hokkaido.jp +shimizu.hokkaido.jp +shimokawa.hokkaido.jp +shinshinotsu.hokkaido.jp +shintoku.hokkaido.jp +shiranuka.hokkaido.jp +shiraoi.hokkaido.jp +shiriuchi.hokkaido.jp +sobetsu.hokkaido.jp +sunagawa.hokkaido.jp +taiki.hokkaido.jp +takasu.hokkaido.jp +takikawa.hokkaido.jp +takinoue.hokkaido.jp +teshikaga.hokkaido.jp +tobetsu.hokkaido.jp +tohma.hokkaido.jp +tomakomai.hokkaido.jp +tomari.hokkaido.jp +toya.hokkaido.jp +toyako.hokkaido.jp +toyotomi.hokkaido.jp +toyoura.hokkaido.jp +tsubetsu.hokkaido.jp +tsukigata.hokkaido.jp +urakawa.hokkaido.jp +urausu.hokkaido.jp +uryu.hokkaido.jp +utashinai.hokkaido.jp +wakkanai.hokkaido.jp +wassamu.hokkaido.jp +yakumo.hokkaido.jp +yoichi.hokkaido.jp +aioi.hyogo.jp +akashi.hyogo.jp +ako.hyogo.jp +amagasaki.hyogo.jp +aogaki.hyogo.jp +asago.hyogo.jp +ashiya.hyogo.jp +awaji.hyogo.jp +fukusaki.hyogo.jp +goshiki.hyogo.jp +harima.hyogo.jp +himeji.hyogo.jp +ichikawa.hyogo.jp +inagawa.hyogo.jp +itami.hyogo.jp +kakogawa.hyogo.jp +kamigori.hyogo.jp +kamikawa.hyogo.jp +kasai.hyogo.jp +kasuga.hyogo.jp +kawanishi.hyogo.jp +miki.hyogo.jp +minamiawaji.hyogo.jp +nishinomiya.hyogo.jp +nishiwaki.hyogo.jp +ono.hyogo.jp +sanda.hyogo.jp +sannan.hyogo.jp +sasayama.hyogo.jp +sayo.hyogo.jp +shingu.hyogo.jp +shinonsen.hyogo.jp +shiso.hyogo.jp +sumoto.hyogo.jp +taishi.hyogo.jp +taka.hyogo.jp +takarazuka.hyogo.jp +takasago.hyogo.jp +takino.hyogo.jp +tamba.hyogo.jp +tatsuno.hyogo.jp +toyooka.hyogo.jp +yabu.hyogo.jp +yashiro.hyogo.jp +yoka.hyogo.jp +yokawa.hyogo.jp +ami.ibaraki.jp +asahi.ibaraki.jp +bando.ibaraki.jp +chikusei.ibaraki.jp +daigo.ibaraki.jp +fujishiro.ibaraki.jp +hitachi.ibaraki.jp +hitachinaka.ibaraki.jp +hitachiomiya.ibaraki.jp +hitachiota.ibaraki.jp +ibaraki.ibaraki.jp +ina.ibaraki.jp +inashiki.ibaraki.jp +itako.ibaraki.jp +iwama.ibaraki.jp +joso.ibaraki.jp +kamisu.ibaraki.jp +kasama.ibaraki.jp +kashima.ibaraki.jp +kasumigaura.ibaraki.jp +koga.ibaraki.jp +miho.ibaraki.jp +mito.ibaraki.jp +moriya.ibaraki.jp +naka.ibaraki.jp +namegata.ibaraki.jp +oarai.ibaraki.jp +ogawa.ibaraki.jp +omitama.ibaraki.jp +ryugasaki.ibaraki.jp +sakai.ibaraki.jp +sakuragawa.ibaraki.jp +shimodate.ibaraki.jp +shimotsuma.ibaraki.jp +shirosato.ibaraki.jp +sowa.ibaraki.jp +suifu.ibaraki.jp +takahagi.ibaraki.jp +tamatsukuri.ibaraki.jp +tokai.ibaraki.jp +tomobe.ibaraki.jp +tone.ibaraki.jp +toride.ibaraki.jp +tsuchiura.ibaraki.jp +tsukuba.ibaraki.jp +uchihara.ibaraki.jp +ushiku.ibaraki.jp +yachiyo.ibaraki.jp +yamagata.ibaraki.jp +yawara.ibaraki.jp +yuki.ibaraki.jp +anamizu.ishikawa.jp +hakui.ishikawa.jp +hakusan.ishikawa.jp +kaga.ishikawa.jp +kahoku.ishikawa.jp +kanazawa.ishikawa.jp +kawakita.ishikawa.jp +komatsu.ishikawa.jp +nakanoto.ishikawa.jp +nanao.ishikawa.jp +nomi.ishikawa.jp +nonoichi.ishikawa.jp +noto.ishikawa.jp +shika.ishikawa.jp +suzu.ishikawa.jp +tsubata.ishikawa.jp +tsurugi.ishikawa.jp +uchinada.ishikawa.jp +wajima.ishikawa.jp +fudai.iwate.jp +fujisawa.iwate.jp +hanamaki.iwate.jp +hiraizumi.iwate.jp +hirono.iwate.jp +ichinohe.iwate.jp +ichinoseki.iwate.jp +iwaizumi.iwate.jp +iwate.iwate.jp +joboji.iwate.jp +kamaishi.iwate.jp +kanegasaki.iwate.jp +karumai.iwate.jp +kawai.iwate.jp +kitakami.iwate.jp +kuji.iwate.jp +kunohe.iwate.jp +kuzumaki.iwate.jp +miyako.iwate.jp +mizusawa.iwate.jp +morioka.iwate.jp +ninohe.iwate.jp +noda.iwate.jp +ofunato.iwate.jp +oshu.iwate.jp +otsuchi.iwate.jp +rikuzentakata.iwate.jp +shiwa.iwate.jp +shizukuishi.iwate.jp +sumita.iwate.jp +tanohata.iwate.jp +tono.iwate.jp +yahaba.iwate.jp +yamada.iwate.jp +ayagawa.kagawa.jp +higashikagawa.kagawa.jp +kanonji.kagawa.jp +kotohira.kagawa.jp +manno.kagawa.jp +marugame.kagawa.jp +mitoyo.kagawa.jp +naoshima.kagawa.jp +sanuki.kagawa.jp +tadotsu.kagawa.jp +takamatsu.kagawa.jp +tonosho.kagawa.jp +uchinomi.kagawa.jp +utazu.kagawa.jp +zentsuji.kagawa.jp +akune.kagoshima.jp +amami.kagoshima.jp +hioki.kagoshima.jp +isa.kagoshima.jp +isen.kagoshima.jp +izumi.kagoshima.jp +kagoshima.kagoshima.jp +kanoya.kagoshima.jp +kawanabe.kagoshima.jp +kinko.kagoshima.jp +kouyama.kagoshima.jp +makurazaki.kagoshima.jp +matsumoto.kagoshima.jp +minamitane.kagoshima.jp +nakatane.kagoshima.jp +nishinoomote.kagoshima.jp +satsumasendai.kagoshima.jp +soo.kagoshima.jp +tarumizu.kagoshima.jp +yusui.kagoshima.jp +aikawa.kanagawa.jp +atsugi.kanagawa.jp +ayase.kanagawa.jp +chigasaki.kanagawa.jp +ebina.kanagawa.jp +fujisawa.kanagawa.jp +hadano.kanagawa.jp +hakone.kanagawa.jp +hiratsuka.kanagawa.jp +isehara.kanagawa.jp +kaisei.kanagawa.jp +kamakura.kanagawa.jp +kiyokawa.kanagawa.jp +matsuda.kanagawa.jp +minamiashigara.kanagawa.jp +miura.kanagawa.jp +nakai.kanagawa.jp +ninomiya.kanagawa.jp +odawara.kanagawa.jp +oi.kanagawa.jp +oiso.kanagawa.jp +sagamihara.kanagawa.jp +samukawa.kanagawa.jp +tsukui.kanagawa.jp +yamakita.kanagawa.jp +yamato.kanagawa.jp +yokosuka.kanagawa.jp +yugawara.kanagawa.jp +zama.kanagawa.jp +zushi.kanagawa.jp +aki.kochi.jp +geisei.kochi.jp +hidaka.kochi.jp +higashitsuno.kochi.jp +ino.kochi.jp +kagami.kochi.jp +kami.kochi.jp +kitagawa.kochi.jp +kochi.kochi.jp +mihara.kochi.jp +motoyama.kochi.jp +muroto.kochi.jp +nahari.kochi.jp +nakamura.kochi.jp +nankoku.kochi.jp +nishitosa.kochi.jp +niyodogawa.kochi.jp +ochi.kochi.jp +okawa.kochi.jp +otoyo.kochi.jp +otsuki.kochi.jp +sakawa.kochi.jp +sukumo.kochi.jp +susaki.kochi.jp +tosa.kochi.jp +tosashimizu.kochi.jp +toyo.kochi.jp +tsuno.kochi.jp +umaji.kochi.jp +yasuda.kochi.jp +yusuhara.kochi.jp +amakusa.kumamoto.jp +arao.kumamoto.jp +aso.kumamoto.jp +choyo.kumamoto.jp +gyokuto.kumamoto.jp +kamiamakusa.kumamoto.jp +kikuchi.kumamoto.jp +kumamoto.kumamoto.jp +mashiki.kumamoto.jp +mifune.kumamoto.jp +minamata.kumamoto.jp +minamioguni.kumamoto.jp +nagasu.kumamoto.jp +nishihara.kumamoto.jp +oguni.kumamoto.jp +ozu.kumamoto.jp +sumoto.kumamoto.jp +takamori.kumamoto.jp +uki.kumamoto.jp +uto.kumamoto.jp +yamaga.kumamoto.jp +yamato.kumamoto.jp +yatsushiro.kumamoto.jp +ayabe.kyoto.jp +fukuchiyama.kyoto.jp +higashiyama.kyoto.jp +ide.kyoto.jp +ine.kyoto.jp +joyo.kyoto.jp +kameoka.kyoto.jp +kamo.kyoto.jp +kita.kyoto.jp +kizu.kyoto.jp +kumiyama.kyoto.jp +kyotamba.kyoto.jp +kyotanabe.kyoto.jp +kyotango.kyoto.jp +maizuru.kyoto.jp +minami.kyoto.jp +minamiyamashiro.kyoto.jp +miyazu.kyoto.jp +muko.kyoto.jp +nagaokakyo.kyoto.jp +nakagyo.kyoto.jp +nantan.kyoto.jp +oyamazaki.kyoto.jp +sakyo.kyoto.jp +seika.kyoto.jp +tanabe.kyoto.jp +uji.kyoto.jp +ujitawara.kyoto.jp +wazuka.kyoto.jp +yamashina.kyoto.jp +yawata.kyoto.jp +asahi.mie.jp +inabe.mie.jp +ise.mie.jp +kameyama.mie.jp +kawagoe.mie.jp +kiho.mie.jp +kisosaki.mie.jp +kiwa.mie.jp +komono.mie.jp +kumano.mie.jp +kuwana.mie.jp +matsusaka.mie.jp +meiwa.mie.jp +mihama.mie.jp +minamiise.mie.jp +misugi.mie.jp +miyama.mie.jp +nabari.mie.jp +shima.mie.jp +suzuka.mie.jp +tado.mie.jp +taiki.mie.jp +taki.mie.jp +tamaki.mie.jp +toba.mie.jp +tsu.mie.jp +udono.mie.jp +ureshino.mie.jp +watarai.mie.jp +yokkaichi.mie.jp +furukawa.miyagi.jp +higashimatsushima.miyagi.jp +ishinomaki.miyagi.jp +iwanuma.miyagi.jp +kakuda.miyagi.jp +kami.miyagi.jp +kawasaki.miyagi.jp +marumori.miyagi.jp +matsushima.miyagi.jp +minamisanriku.miyagi.jp +misato.miyagi.jp +murata.miyagi.jp +natori.miyagi.jp +ogawara.miyagi.jp +ohira.miyagi.jp +onagawa.miyagi.jp +osaki.miyagi.jp +rifu.miyagi.jp +semine.miyagi.jp +shibata.miyagi.jp +shichikashuku.miyagi.jp +shikama.miyagi.jp +shiogama.miyagi.jp +shiroishi.miyagi.jp +tagajo.miyagi.jp +taiwa.miyagi.jp +tome.miyagi.jp +tomiya.miyagi.jp +wakuya.miyagi.jp +watari.miyagi.jp +yamamoto.miyagi.jp +zao.miyagi.jp +aya.miyazaki.jp +ebino.miyazaki.jp +gokase.miyazaki.jp +hyuga.miyazaki.jp +kadogawa.miyazaki.jp +kawaminami.miyazaki.jp +kijo.miyazaki.jp +kitagawa.miyazaki.jp +kitakata.miyazaki.jp +kitaura.miyazaki.jp +kobayashi.miyazaki.jp +kunitomi.miyazaki.jp +kushima.miyazaki.jp +mimata.miyazaki.jp +miyakonojo.miyazaki.jp +miyazaki.miyazaki.jp +morotsuka.miyazaki.jp +nichinan.miyazaki.jp +nishimera.miyazaki.jp +nobeoka.miyazaki.jp +saito.miyazaki.jp +shiiba.miyazaki.jp +shintomi.miyazaki.jp +takaharu.miyazaki.jp +takanabe.miyazaki.jp +takazaki.miyazaki.jp +tsuno.miyazaki.jp +achi.nagano.jp +agematsu.nagano.jp +anan.nagano.jp +aoki.nagano.jp +asahi.nagano.jp +azumino.nagano.jp +chikuhoku.nagano.jp +chikuma.nagano.jp +chino.nagano.jp +fujimi.nagano.jp +hakuba.nagano.jp +hara.nagano.jp +hiraya.nagano.jp +iida.nagano.jp +iijima.nagano.jp +iiyama.nagano.jp +iizuna.nagano.jp +ikeda.nagano.jp +ikusaka.nagano.jp +ina.nagano.jp +karuizawa.nagano.jp +kawakami.nagano.jp +kiso.nagano.jp +kisofukushima.nagano.jp +kitaaiki.nagano.jp +komagane.nagano.jp +komoro.nagano.jp +matsukawa.nagano.jp +matsumoto.nagano.jp +miasa.nagano.jp +minamiaiki.nagano.jp +minamimaki.nagano.jp +minamiminowa.nagano.jp +minowa.nagano.jp +miyada.nagano.jp +miyota.nagano.jp +mochizuki.nagano.jp +nagano.nagano.jp +nagawa.nagano.jp +nagiso.nagano.jp +nakagawa.nagano.jp +nakano.nagano.jp +nozawaonsen.nagano.jp +obuse.nagano.jp +ogawa.nagano.jp +okaya.nagano.jp +omachi.nagano.jp +omi.nagano.jp +ookuwa.nagano.jp +ooshika.nagano.jp +otaki.nagano.jp +otari.nagano.jp +sakae.nagano.jp +sakaki.nagano.jp +saku.nagano.jp +sakuho.nagano.jp +shimosuwa.nagano.jp +shinanomachi.nagano.jp +shiojiri.nagano.jp +suwa.nagano.jp +suzaka.nagano.jp +takagi.nagano.jp +takamori.nagano.jp +takayama.nagano.jp +tateshina.nagano.jp +tatsuno.nagano.jp +togakushi.nagano.jp +togura.nagano.jp +tomi.nagano.jp +ueda.nagano.jp +wada.nagano.jp +yamagata.nagano.jp +yamanouchi.nagano.jp +yasaka.nagano.jp +yasuoka.nagano.jp +chijiwa.nagasaki.jp +futsu.nagasaki.jp +goto.nagasaki.jp +hasami.nagasaki.jp +hirado.nagasaki.jp +iki.nagasaki.jp +isahaya.nagasaki.jp +kawatana.nagasaki.jp +kuchinotsu.nagasaki.jp +matsuura.nagasaki.jp +nagasaki.nagasaki.jp +obama.nagasaki.jp +omura.nagasaki.jp +oseto.nagasaki.jp +saikai.nagasaki.jp +sasebo.nagasaki.jp +seihi.nagasaki.jp +shimabara.nagasaki.jp +shinkamigoto.nagasaki.jp +togitsu.nagasaki.jp +tsushima.nagasaki.jp +unzen.nagasaki.jp +ando.nara.jp +gose.nara.jp +heguri.nara.jp +higashiyoshino.nara.jp +ikaruga.nara.jp +ikoma.nara.jp +kamikitayama.nara.jp +kanmaki.nara.jp +kashiba.nara.jp +kashihara.nara.jp +katsuragi.nara.jp +kawai.nara.jp +kawakami.nara.jp +kawanishi.nara.jp +koryo.nara.jp +kurotaki.nara.jp +mitsue.nara.jp +miyake.nara.jp +nara.nara.jp +nosegawa.nara.jp +oji.nara.jp +ouda.nara.jp +oyodo.nara.jp +sakurai.nara.jp +sango.nara.jp +shimoichi.nara.jp +shimokitayama.nara.jp +shinjo.nara.jp +soni.nara.jp +takatori.nara.jp +tawaramoto.nara.jp +tenkawa.nara.jp +tenri.nara.jp +uda.nara.jp +yamatokoriyama.nara.jp +yamatotakada.nara.jp +yamazoe.nara.jp +yoshino.nara.jp +aga.niigata.jp +agano.niigata.jp +gosen.niigata.jp +itoigawa.niigata.jp +izumozaki.niigata.jp +joetsu.niigata.jp +kamo.niigata.jp +kariwa.niigata.jp +kashiwazaki.niigata.jp +minamiuonuma.niigata.jp +mitsuke.niigata.jp +muika.niigata.jp +murakami.niigata.jp +myoko.niigata.jp +nagaoka.niigata.jp +niigata.niigata.jp +ojiya.niigata.jp +omi.niigata.jp +sado.niigata.jp +sanjo.niigata.jp +seiro.niigata.jp +seirou.niigata.jp +sekikawa.niigata.jp +shibata.niigata.jp +tagami.niigata.jp +tainai.niigata.jp +tochio.niigata.jp +tokamachi.niigata.jp +tsubame.niigata.jp +tsunan.niigata.jp +uonuma.niigata.jp +yahiko.niigata.jp +yoita.niigata.jp +yuzawa.niigata.jp +beppu.oita.jp +bungoono.oita.jp +bungotakada.oita.jp +hasama.oita.jp +hiji.oita.jp +himeshima.oita.jp +hita.oita.jp +kamitsue.oita.jp +kokonoe.oita.jp +kuju.oita.jp +kunisaki.oita.jp +kusu.oita.jp +oita.oita.jp +saiki.oita.jp +taketa.oita.jp +tsukumi.oita.jp +usa.oita.jp +usuki.oita.jp +yufu.oita.jp +akaiwa.okayama.jp +asakuchi.okayama.jp +bizen.okayama.jp +hayashima.okayama.jp +ibara.okayama.jp +kagamino.okayama.jp +kasaoka.okayama.jp +kibichuo.okayama.jp +kumenan.okayama.jp +kurashiki.okayama.jp +maniwa.okayama.jp +misaki.okayama.jp +nagi.okayama.jp +niimi.okayama.jp +nishiawakura.okayama.jp +okayama.okayama.jp +satosho.okayama.jp +setouchi.okayama.jp +shinjo.okayama.jp +shoo.okayama.jp +soja.okayama.jp +takahashi.okayama.jp +tamano.okayama.jp +tsuyama.okayama.jp +wake.okayama.jp +yakage.okayama.jp +aguni.okinawa.jp +ginowan.okinawa.jp +ginoza.okinawa.jp +gushikami.okinawa.jp +haebaru.okinawa.jp +higashi.okinawa.jp +hirara.okinawa.jp +iheya.okinawa.jp +ishigaki.okinawa.jp +ishikawa.okinawa.jp +itoman.okinawa.jp +izena.okinawa.jp +kadena.okinawa.jp +kin.okinawa.jp +kitadaito.okinawa.jp +kitanakagusuku.okinawa.jp +kumejima.okinawa.jp +kunigami.okinawa.jp +minamidaito.okinawa.jp +motobu.okinawa.jp +nago.okinawa.jp +naha.okinawa.jp +nakagusuku.okinawa.jp +nakijin.okinawa.jp +nanjo.okinawa.jp +nishihara.okinawa.jp +ogimi.okinawa.jp +okinawa.okinawa.jp +onna.okinawa.jp +shimoji.okinawa.jp +taketomi.okinawa.jp +tarama.okinawa.jp +tokashiki.okinawa.jp +tomigusuku.okinawa.jp +tonaki.okinawa.jp +urasoe.okinawa.jp +uruma.okinawa.jp +yaese.okinawa.jp +yomitan.okinawa.jp +yonabaru.okinawa.jp +yonaguni.okinawa.jp +zamami.okinawa.jp +abeno.osaka.jp +chihayaakasaka.osaka.jp +chuo.osaka.jp +daito.osaka.jp +fujiidera.osaka.jp +habikino.osaka.jp +hannan.osaka.jp +higashiosaka.osaka.jp +higashisumiyoshi.osaka.jp +higashiyodogawa.osaka.jp +hirakata.osaka.jp +ibaraki.osaka.jp +ikeda.osaka.jp +izumi.osaka.jp +izumiotsu.osaka.jp +izumisano.osaka.jp +kadoma.osaka.jp +kaizuka.osaka.jp +kanan.osaka.jp +kashiwara.osaka.jp +katano.osaka.jp +kawachinagano.osaka.jp +kishiwada.osaka.jp +kita.osaka.jp +kumatori.osaka.jp +matsubara.osaka.jp +minato.osaka.jp +minoh.osaka.jp +misaki.osaka.jp +moriguchi.osaka.jp +neyagawa.osaka.jp +nishi.osaka.jp +nose.osaka.jp +osakasayama.osaka.jp +sakai.osaka.jp +sayama.osaka.jp +sennan.osaka.jp +settsu.osaka.jp +shijonawate.osaka.jp +shimamoto.osaka.jp +suita.osaka.jp +tadaoka.osaka.jp +taishi.osaka.jp +tajiri.osaka.jp +takaishi.osaka.jp +takatsuki.osaka.jp +tondabayashi.osaka.jp +toyonaka.osaka.jp +toyono.osaka.jp +yao.osaka.jp +ariake.saga.jp +arita.saga.jp +fukudomi.saga.jp +genkai.saga.jp +hamatama.saga.jp +hizen.saga.jp +imari.saga.jp +kamimine.saga.jp +kanzaki.saga.jp +karatsu.saga.jp +kashima.saga.jp +kitagata.saga.jp +kitahata.saga.jp +kiyama.saga.jp +kouhoku.saga.jp +kyuragi.saga.jp +nishiarita.saga.jp +ogi.saga.jp +omachi.saga.jp +ouchi.saga.jp +saga.saga.jp +shiroishi.saga.jp +taku.saga.jp +tara.saga.jp +tosu.saga.jp +yoshinogari.saga.jp +arakawa.saitama.jp +asaka.saitama.jp +chichibu.saitama.jp +fujimi.saitama.jp +fujimino.saitama.jp +fukaya.saitama.jp +hanno.saitama.jp +hanyu.saitama.jp +hasuda.saitama.jp +hatogaya.saitama.jp +hatoyama.saitama.jp +hidaka.saitama.jp +higashichichibu.saitama.jp +higashimatsuyama.saitama.jp +honjo.saitama.jp +ina.saitama.jp +iruma.saitama.jp +iwatsuki.saitama.jp +kamiizumi.saitama.jp +kamikawa.saitama.jp +kamisato.saitama.jp +kasukabe.saitama.jp +kawagoe.saitama.jp +kawaguchi.saitama.jp +kawajima.saitama.jp +kazo.saitama.jp +kitamoto.saitama.jp +koshigaya.saitama.jp +kounosu.saitama.jp +kuki.saitama.jp +kumagaya.saitama.jp +matsubushi.saitama.jp +minano.saitama.jp +misato.saitama.jp +miyashiro.saitama.jp +miyoshi.saitama.jp +moroyama.saitama.jp +nagatoro.saitama.jp +namegawa.saitama.jp +niiza.saitama.jp +ogano.saitama.jp +ogawa.saitama.jp +ogose.saitama.jp +okegawa.saitama.jp +omiya.saitama.jp +otaki.saitama.jp +ranzan.saitama.jp +ryokami.saitama.jp +saitama.saitama.jp +sakado.saitama.jp +satte.saitama.jp +sayama.saitama.jp +shiki.saitama.jp +shiraoka.saitama.jp +soka.saitama.jp +sugito.saitama.jp +toda.saitama.jp +tokigawa.saitama.jp +tokorozawa.saitama.jp +tsurugashima.saitama.jp +urawa.saitama.jp +warabi.saitama.jp +yashio.saitama.jp +yokoze.saitama.jp +yono.saitama.jp +yorii.saitama.jp +yoshida.saitama.jp +yoshikawa.saitama.jp +yoshimi.saitama.jp +aisho.shiga.jp +gamo.shiga.jp +higashiomi.shiga.jp +hikone.shiga.jp +koka.shiga.jp +konan.shiga.jp +kosei.shiga.jp +koto.shiga.jp +kusatsu.shiga.jp +maibara.shiga.jp +moriyama.shiga.jp +nagahama.shiga.jp +nishiazai.shiga.jp +notogawa.shiga.jp +omihachiman.shiga.jp +otsu.shiga.jp +ritto.shiga.jp +ryuoh.shiga.jp +takashima.shiga.jp +takatsuki.shiga.jp +torahime.shiga.jp +toyosato.shiga.jp +yasu.shiga.jp +akagi.shimane.jp +ama.shimane.jp +gotsu.shimane.jp +hamada.shimane.jp +higashiizumo.shimane.jp +hikawa.shimane.jp +hikimi.shimane.jp +izumo.shimane.jp +kakinoki.shimane.jp +masuda.shimane.jp +matsue.shimane.jp +misato.shimane.jp +nishinoshima.shimane.jp +ohda.shimane.jp +okinoshima.shimane.jp +okuizumo.shimane.jp +shimane.shimane.jp +tamayu.shimane.jp +tsuwano.shimane.jp +unnan.shimane.jp +yakumo.shimane.jp +yasugi.shimane.jp +yatsuka.shimane.jp +arai.shizuoka.jp +atami.shizuoka.jp +fuji.shizuoka.jp +fujieda.shizuoka.jp +fujikawa.shizuoka.jp +fujinomiya.shizuoka.jp +fukuroi.shizuoka.jp +gotemba.shizuoka.jp +haibara.shizuoka.jp +hamamatsu.shizuoka.jp +higashiizu.shizuoka.jp +ito.shizuoka.jp +iwata.shizuoka.jp +izu.shizuoka.jp +izunokuni.shizuoka.jp +kakegawa.shizuoka.jp +kannami.shizuoka.jp +kawanehon.shizuoka.jp +kawazu.shizuoka.jp +kikugawa.shizuoka.jp +kosai.shizuoka.jp +makinohara.shizuoka.jp +matsuzaki.shizuoka.jp +minamiizu.shizuoka.jp +mishima.shizuoka.jp +morimachi.shizuoka.jp +nishiizu.shizuoka.jp +numazu.shizuoka.jp +omaezaki.shizuoka.jp +shimada.shizuoka.jp +shimizu.shizuoka.jp +shimoda.shizuoka.jp +shizuoka.shizuoka.jp +susono.shizuoka.jp +yaizu.shizuoka.jp +yoshida.shizuoka.jp +ashikaga.tochigi.jp +bato.tochigi.jp +haga.tochigi.jp +ichikai.tochigi.jp +iwafune.tochigi.jp +kaminokawa.tochigi.jp +kanuma.tochigi.jp +karasuyama.tochigi.jp +kuroiso.tochigi.jp +mashiko.tochigi.jp +mibu.tochigi.jp +moka.tochigi.jp +motegi.tochigi.jp +nasu.tochigi.jp +nasushiobara.tochigi.jp +nikko.tochigi.jp +nishikata.tochigi.jp +nogi.tochigi.jp +ohira.tochigi.jp +ohtawara.tochigi.jp +oyama.tochigi.jp +sakura.tochigi.jp +sano.tochigi.jp +shimotsuke.tochigi.jp +shioya.tochigi.jp +takanezawa.tochigi.jp +tochigi.tochigi.jp +tsuga.tochigi.jp +ujiie.tochigi.jp +utsunomiya.tochigi.jp +yaita.tochigi.jp +aizumi.tokushima.jp +anan.tokushima.jp +ichiba.tokushima.jp +itano.tokushima.jp +kainan.tokushima.jp +komatsushima.tokushima.jp +matsushige.tokushima.jp +mima.tokushima.jp +minami.tokushima.jp +miyoshi.tokushima.jp +mugi.tokushima.jp +nakagawa.tokushima.jp +naruto.tokushima.jp +sanagochi.tokushima.jp +shishikui.tokushima.jp +tokushima.tokushima.jp +wajiki.tokushima.jp +adachi.tokyo.jp +akiruno.tokyo.jp +akishima.tokyo.jp +aogashima.tokyo.jp +arakawa.tokyo.jp +bunkyo.tokyo.jp +chiyoda.tokyo.jp +chofu.tokyo.jp +chuo.tokyo.jp +edogawa.tokyo.jp +fuchu.tokyo.jp +fussa.tokyo.jp +hachijo.tokyo.jp +hachioji.tokyo.jp +hamura.tokyo.jp +higashikurume.tokyo.jp +higashimurayama.tokyo.jp +higashiyamato.tokyo.jp +hino.tokyo.jp +hinode.tokyo.jp +hinohara.tokyo.jp +inagi.tokyo.jp +itabashi.tokyo.jp +katsushika.tokyo.jp +kita.tokyo.jp +kiyose.tokyo.jp +kodaira.tokyo.jp +koganei.tokyo.jp +kokubunji.tokyo.jp +komae.tokyo.jp +koto.tokyo.jp +kouzushima.tokyo.jp +kunitachi.tokyo.jp +machida.tokyo.jp +meguro.tokyo.jp +minato.tokyo.jp +mitaka.tokyo.jp +mizuho.tokyo.jp +musashimurayama.tokyo.jp +musashino.tokyo.jp +nakano.tokyo.jp +nerima.tokyo.jp +ogasawara.tokyo.jp +okutama.tokyo.jp +ome.tokyo.jp +oshima.tokyo.jp +ota.tokyo.jp +setagaya.tokyo.jp +shibuya.tokyo.jp +shinagawa.tokyo.jp +shinjuku.tokyo.jp +suginami.tokyo.jp +sumida.tokyo.jp +tachikawa.tokyo.jp +taito.tokyo.jp +tama.tokyo.jp +toshima.tokyo.jp +chizu.tottori.jp +hino.tottori.jp +kawahara.tottori.jp +koge.tottori.jp +kotoura.tottori.jp +misasa.tottori.jp +nanbu.tottori.jp +nichinan.tottori.jp +sakaiminato.tottori.jp +tottori.tottori.jp +wakasa.tottori.jp +yazu.tottori.jp +yonago.tottori.jp +asahi.toyama.jp +fuchu.toyama.jp +fukumitsu.toyama.jp +funahashi.toyama.jp +himi.toyama.jp +imizu.toyama.jp +inami.toyama.jp +johana.toyama.jp +kamiichi.toyama.jp +kurobe.toyama.jp +nakaniikawa.toyama.jp +namerikawa.toyama.jp +nanto.toyama.jp +nyuzen.toyama.jp +oyabe.toyama.jp +taira.toyama.jp +takaoka.toyama.jp +tateyama.toyama.jp +toga.toyama.jp +tonami.toyama.jp +toyama.toyama.jp +unazuki.toyama.jp +uozu.toyama.jp +yamada.toyama.jp +arida.wakayama.jp +aridagawa.wakayama.jp +gobo.wakayama.jp +hashimoto.wakayama.jp +hidaka.wakayama.jp +hirogawa.wakayama.jp +inami.wakayama.jp +iwade.wakayama.jp +kainan.wakayama.jp +kamitonda.wakayama.jp +katsuragi.wakayama.jp +kimino.wakayama.jp +kinokawa.wakayama.jp +kitayama.wakayama.jp +koya.wakayama.jp +koza.wakayama.jp +kozagawa.wakayama.jp +kudoyama.wakayama.jp +kushimoto.wakayama.jp +mihama.wakayama.jp +misato.wakayama.jp +nachikatsuura.wakayama.jp +shingu.wakayama.jp +shirahama.wakayama.jp +taiji.wakayama.jp +tanabe.wakayama.jp +wakayama.wakayama.jp +yuasa.wakayama.jp +yura.wakayama.jp +asahi.yamagata.jp +funagata.yamagata.jp +higashine.yamagata.jp +iide.yamagata.jp +kahoku.yamagata.jp +kaminoyama.yamagata.jp +kaneyama.yamagata.jp +kawanishi.yamagata.jp +mamurogawa.yamagata.jp +mikawa.yamagata.jp +murayama.yamagata.jp +nagai.yamagata.jp +nakayama.yamagata.jp +nanyo.yamagata.jp +nishikawa.yamagata.jp +obanazawa.yamagata.jp +oe.yamagata.jp +oguni.yamagata.jp +ohkura.yamagata.jp +oishida.yamagata.jp +sagae.yamagata.jp +sakata.yamagata.jp +sakegawa.yamagata.jp +shinjo.yamagata.jp +shirataka.yamagata.jp +shonai.yamagata.jp +takahata.yamagata.jp +tendo.yamagata.jp +tozawa.yamagata.jp +tsuruoka.yamagata.jp +yamagata.yamagata.jp +yamanobe.yamagata.jp +yonezawa.yamagata.jp +yuza.yamagata.jp +abu.yamaguchi.jp +hagi.yamaguchi.jp +hikari.yamaguchi.jp +hofu.yamaguchi.jp +iwakuni.yamaguchi.jp +kudamatsu.yamaguchi.jp +mitou.yamaguchi.jp +nagato.yamaguchi.jp +oshima.yamaguchi.jp +shimonoseki.yamaguchi.jp +shunan.yamaguchi.jp +tabuse.yamaguchi.jp +tokuyama.yamaguchi.jp +toyota.yamaguchi.jp +ube.yamaguchi.jp +yuu.yamaguchi.jp +chuo.yamanashi.jp +doshi.yamanashi.jp +fuefuki.yamanashi.jp +fujikawa.yamanashi.jp +fujikawaguchiko.yamanashi.jp +fujiyoshida.yamanashi.jp +hayakawa.yamanashi.jp +hokuto.yamanashi.jp +ichikawamisato.yamanashi.jp +kai.yamanashi.jp +kofu.yamanashi.jp +koshu.yamanashi.jp +kosuge.yamanashi.jp +minami-alps.yamanashi.jp +minobu.yamanashi.jp +nakamichi.yamanashi.jp +nanbu.yamanashi.jp +narusawa.yamanashi.jp +nirasaki.yamanashi.jp +nishikatsura.yamanashi.jp +oshino.yamanashi.jp +otsuki.yamanashi.jp +showa.yamanashi.jp +tabayama.yamanashi.jp +tsuru.yamanashi.jp +uenohara.yamanashi.jp +yamanakako.yamanashi.jp +yamanashi.yamanashi.jp + +// ke : http://www.kenic.or.ke/index.php/en/ke-domains/ke-domains +ke +ac.ke +co.ke +go.ke +info.ke +me.ke +mobi.ke +ne.ke +or.ke +sc.ke + +// kg : http://www.domain.kg/dmn_n.html +kg +org.kg +net.kg +com.kg +edu.kg +gov.kg +mil.kg + +// kh : http://www.mptc.gov.kh/dns_registration.htm +*.kh + +// ki : http://www.ki/dns/index.html +ki +edu.ki +biz.ki +net.ki +org.ki +gov.ki +info.ki +com.ki + +// km : https://en.wikipedia.org/wiki/.km +// http://www.domaine.km/documents/charte.doc +km +org.km +nom.km +gov.km +prd.km +tm.km +edu.km +mil.km +ass.km +com.km +// These are only mentioned as proposed suggestions at domaine.km, but +// https://en.wikipedia.org/wiki/.km says they're available for registration: +coop.km +asso.km +presse.km +medecin.km +notaires.km +pharmaciens.km +veterinaire.km +gouv.km + +// kn : https://en.wikipedia.org/wiki/.kn +// http://www.dot.kn/domainRules.html +kn +net.kn +org.kn +edu.kn +gov.kn + +// kp : http://www.kcce.kp/en_index.php +kp +com.kp +edu.kp +gov.kp +org.kp +rep.kp +tra.kp + +// kr : https://en.wikipedia.org/wiki/.kr +// see also: http://domain.nida.or.kr/eng/registration.jsp +kr +ac.kr +co.kr +es.kr +go.kr +hs.kr +kg.kr +mil.kr +ms.kr +ne.kr +or.kr +pe.kr +re.kr +sc.kr +// kr geographical names +busan.kr +chungbuk.kr +chungnam.kr +daegu.kr +daejeon.kr +gangwon.kr +gwangju.kr +gyeongbuk.kr +gyeonggi.kr +gyeongnam.kr +incheon.kr +jeju.kr +jeonbuk.kr +jeonnam.kr +seoul.kr +ulsan.kr + +// kw : https://www.nic.kw/policies/ +// Confirmed by registry +kw +com.kw +edu.kw +emb.kw +gov.kw +ind.kw +net.kw +org.kw + +// ky : http://www.icta.ky/da_ky_reg_dom.php +// Confirmed by registry 2008-06-17 +ky +edu.ky +gov.ky +com.ky +org.ky +net.ky + +// kz : https://en.wikipedia.org/wiki/.kz +// see also: http://www.nic.kz/rules/index.jsp +kz +org.kz +edu.kz +net.kz +gov.kz +mil.kz +com.kz + +// la : https://en.wikipedia.org/wiki/.la +// Submitted by registry +la +int.la +net.la +info.la +edu.la +gov.la +per.la +com.la +org.la + +// lb : https://en.wikipedia.org/wiki/.lb +// Submitted by registry +lb +com.lb +edu.lb +gov.lb +net.lb +org.lb + +// lc : https://en.wikipedia.org/wiki/.lc +// see also: http://www.nic.lc/rules.htm +lc +com.lc +net.lc +co.lc +org.lc +edu.lc +gov.lc + +// li : https://en.wikipedia.org/wiki/.li +li + +// lk : http://www.nic.lk/seclevpr.html +lk +gov.lk +sch.lk +net.lk +int.lk +com.lk +org.lk +edu.lk +ngo.lk +soc.lk +web.lk +ltd.lk +assn.lk +grp.lk +hotel.lk +ac.lk + +// lr : http://psg.com/dns/lr/lr.txt +// Submitted by registry +lr +com.lr +edu.lr +gov.lr +org.lr +net.lr + +// ls : http://www.nic.ls/ +// Confirmed by registry +ls +ac.ls +biz.ls +co.ls +edu.ls +gov.ls +info.ls +net.ls +org.ls +sc.ls + +// lt : https://en.wikipedia.org/wiki/.lt +lt +// gov.lt : http://www.gov.lt/index_en.php +gov.lt + +// lu : http://www.dns.lu/en/ +lu + +// lv : http://www.nic.lv/DNS/En/generic.php +lv +com.lv +edu.lv +gov.lv +org.lv +mil.lv +id.lv +net.lv +asn.lv +conf.lv + +// ly : http://www.nic.ly/regulations.php +ly +com.ly +net.ly +gov.ly +plc.ly +edu.ly +sch.ly +med.ly +org.ly +id.ly + +// ma : https://en.wikipedia.org/wiki/.ma +// http://www.anrt.ma/fr/admin/download/upload/file_fr782.pdf +ma +co.ma +net.ma +gov.ma +org.ma +ac.ma +press.ma + +// mc : http://www.nic.mc/ +mc +tm.mc +asso.mc + +// md : https://en.wikipedia.org/wiki/.md +md + +// me : https://en.wikipedia.org/wiki/.me +me +co.me +net.me +org.me +edu.me +ac.me +gov.me +its.me +priv.me + +// mg : http://nic.mg/nicmg/?page_id=39 +mg +org.mg +nom.mg +gov.mg +prd.mg +tm.mg +edu.mg +mil.mg +com.mg +co.mg + +// mh : https://en.wikipedia.org/wiki/.mh +mh + +// mil : https://en.wikipedia.org/wiki/.mil +mil + +// mk : https://en.wikipedia.org/wiki/.mk +// see also: http://dns.marnet.net.mk/postapka.php +mk +com.mk +org.mk +net.mk +edu.mk +gov.mk +inf.mk +name.mk + +// ml : http://www.gobin.info/domainname/ml-template.doc +// see also: https://en.wikipedia.org/wiki/.ml +ml +com.ml +edu.ml +gouv.ml +gov.ml +net.ml +org.ml +presse.ml + +// mm : https://en.wikipedia.org/wiki/.mm +*.mm + +// mn : https://en.wikipedia.org/wiki/.mn +mn +gov.mn +edu.mn +org.mn + +// mo : http://www.monic.net.mo/ +mo +com.mo +net.mo +org.mo +edu.mo +gov.mo + +// mobi : https://en.wikipedia.org/wiki/.mobi +mobi + +// mp : http://www.dot.mp/ +// Confirmed by registry 2008-06-17 +mp + +// mq : https://en.wikipedia.org/wiki/.mq +mq + +// mr : https://en.wikipedia.org/wiki/.mr +mr +gov.mr + +// ms : http://www.nic.ms/pdf/MS_Domain_Name_Rules.pdf +ms +com.ms +edu.ms +gov.ms +net.ms +org.ms + +// mt : https://www.nic.org.mt/go/policy +// Submitted by registry +mt +com.mt +edu.mt +net.mt +org.mt + +// mu : https://en.wikipedia.org/wiki/.mu +mu +com.mu +net.mu +org.mu +gov.mu +ac.mu +co.mu +or.mu + +// museum : http://about.museum/naming/ +// http://index.museum/ +museum +academy.museum +agriculture.museum +air.museum +airguard.museum +alabama.museum +alaska.museum +amber.museum +ambulance.museum +american.museum +americana.museum +americanantiques.museum +americanart.museum +amsterdam.museum +and.museum +annefrank.museum +anthro.museum +anthropology.museum +antiques.museum +aquarium.museum +arboretum.museum +archaeological.museum +archaeology.museum +architecture.museum +art.museum +artanddesign.museum +artcenter.museum +artdeco.museum +arteducation.museum +artgallery.museum +arts.museum +artsandcrafts.museum +asmatart.museum +assassination.museum +assisi.museum +association.museum +astronomy.museum +atlanta.museum +austin.museum +australia.museum +automotive.museum +aviation.museum +axis.museum +badajoz.museum +baghdad.museum +bahn.museum +bale.museum +baltimore.museum +barcelona.museum +baseball.museum +basel.museum +baths.museum +bauern.museum +beauxarts.museum +beeldengeluid.museum +bellevue.museum +bergbau.museum +berkeley.museum +berlin.museum +bern.museum +bible.museum +bilbao.museum +bill.museum +birdart.museum +birthplace.museum +bonn.museum +boston.museum +botanical.museum +botanicalgarden.museum +botanicgarden.museum +botany.museum +brandywinevalley.museum +brasil.museum +bristol.museum +british.museum +britishcolumbia.museum +broadcast.museum +brunel.museum +brussel.museum +brussels.museum +bruxelles.museum +building.museum +burghof.museum +bus.museum +bushey.museum +cadaques.museum +california.museum +cambridge.museum +can.museum +canada.museum +capebreton.museum +carrier.museum +cartoonart.museum +casadelamoneda.museum +castle.museum +castres.museum +celtic.museum +center.museum +chattanooga.museum +cheltenham.museum +chesapeakebay.museum +chicago.museum +children.museum +childrens.museum +childrensgarden.museum +chiropractic.museum +chocolate.museum +christiansburg.museum +cincinnati.museum +cinema.museum +circus.museum +civilisation.museum +civilization.museum +civilwar.museum +clinton.museum +clock.museum +coal.museum +coastaldefence.museum +cody.museum +coldwar.museum +collection.museum +colonialwilliamsburg.museum +coloradoplateau.museum +columbia.museum +columbus.museum +communication.museum +communications.museum +community.museum +computer.museum +computerhistory.museum +comunicações.museum +contemporary.museum +contemporaryart.museum +convent.museum +copenhagen.museum +corporation.museum +correios-e-telecomunicações.museum +corvette.museum +costume.museum +countryestate.museum +county.museum +crafts.museum +cranbrook.museum +creation.museum +cultural.museum +culturalcenter.museum +culture.museum +cyber.museum +cymru.museum +dali.museum +dallas.museum +database.museum +ddr.museum +decorativearts.museum +delaware.museum +delmenhorst.museum +denmark.museum +depot.museum +design.museum +detroit.museum +dinosaur.museum +discovery.museum +dolls.museum +donostia.museum +durham.museum +eastafrica.museum +eastcoast.museum +education.museum +educational.museum +egyptian.museum +eisenbahn.museum +elburg.museum +elvendrell.museum +embroidery.museum +encyclopedic.museum +england.museum +entomology.museum +environment.museum +environmentalconservation.museum +epilepsy.museum +essex.museum +estate.museum +ethnology.museum +exeter.museum +exhibition.museum +family.museum +farm.museum +farmequipment.museum +farmers.museum +farmstead.museum +field.museum +figueres.museum +filatelia.museum +film.museum +fineart.museum +finearts.museum +finland.museum +flanders.museum +florida.museum +force.museum +fortmissoula.museum +fortworth.museum +foundation.museum +francaise.museum +frankfurt.museum +franziskaner.museum +freemasonry.museum +freiburg.museum +fribourg.museum +frog.museum +fundacio.museum +furniture.museum +gallery.museum +garden.museum +gateway.museum +geelvinck.museum +gemological.museum +geology.museum +georgia.museum +giessen.museum +glas.museum +glass.museum +gorge.museum +grandrapids.museum +graz.museum +guernsey.museum +halloffame.museum +hamburg.museum +handson.museum +harvestcelebration.museum +hawaii.museum +health.museum +heimatunduhren.museum +hellas.museum +helsinki.museum +hembygdsforbund.museum +heritage.museum +histoire.museum +historical.museum +historicalsociety.museum +historichouses.museum +historisch.museum +historisches.museum +history.museum +historyofscience.museum +horology.museum +house.museum +humanities.museum +illustration.museum +imageandsound.museum +indian.museum +indiana.museum +indianapolis.museum +indianmarket.museum +intelligence.museum +interactive.museum +iraq.museum +iron.museum +isleofman.museum +jamison.museum +jefferson.museum +jerusalem.museum +jewelry.museum +jewish.museum +jewishart.museum +jfk.museum +journalism.museum +judaica.museum +judygarland.museum +juedisches.museum +juif.museum +karate.museum +karikatur.museum +kids.museum +koebenhavn.museum +koeln.museum +kunst.museum +kunstsammlung.museum +kunstunddesign.museum +labor.museum +labour.museum +lajolla.museum +lancashire.museum +landes.museum +lans.museum +läns.museum +larsson.museum +lewismiller.museum +lincoln.museum +linz.museum +living.museum +livinghistory.museum +localhistory.museum +london.museum +losangeles.museum +louvre.museum +loyalist.museum +lucerne.museum +luxembourg.museum +luzern.museum +mad.museum +madrid.museum +mallorca.museum +manchester.museum +mansion.museum +mansions.museum +manx.museum +marburg.museum +maritime.museum +maritimo.museum +maryland.museum +marylhurst.museum +media.museum +medical.museum +medizinhistorisches.museum +meeres.museum +memorial.museum +mesaverde.museum +michigan.museum +midatlantic.museum +military.museum +mill.museum +miners.museum +mining.museum +minnesota.museum +missile.museum +missoula.museum +modern.museum +moma.museum +money.museum +monmouth.museum +monticello.museum +montreal.museum +moscow.museum +motorcycle.museum +muenchen.museum +muenster.museum +mulhouse.museum +muncie.museum +museet.museum +museumcenter.museum +museumvereniging.museum +music.museum +national.museum +nationalfirearms.museum +nationalheritage.museum +nativeamerican.museum +naturalhistory.museum +naturalhistorymuseum.museum +naturalsciences.museum +nature.museum +naturhistorisches.museum +natuurwetenschappen.museum +naumburg.museum +naval.museum +nebraska.museum +neues.museum +newhampshire.museum +newjersey.museum +newmexico.museum +newport.museum +newspaper.museum +newyork.museum +niepce.museum +norfolk.museum +north.museum +nrw.museum +nuernberg.museum +nuremberg.museum +nyc.museum +nyny.museum +oceanographic.museum +oceanographique.museum +omaha.museum +online.museum +ontario.museum +openair.museum +oregon.museum +oregontrail.museum +otago.museum +oxford.museum +pacific.museum +paderborn.museum +palace.museum +paleo.museum +palmsprings.museum +panama.museum +paris.museum +pasadena.museum +pharmacy.museum +philadelphia.museum +philadelphiaarea.museum +philately.museum +phoenix.museum +photography.museum +pilots.museum +pittsburgh.museum +planetarium.museum +plantation.museum +plants.museum +plaza.museum +portal.museum +portland.museum +portlligat.museum +posts-and-telecommunications.museum +preservation.museum +presidio.museum +press.museum +project.museum +public.museum +pubol.museum +quebec.museum +railroad.museum +railway.museum +research.museum +resistance.museum +riodejaneiro.museum +rochester.museum +rockart.museum +roma.museum +russia.museum +saintlouis.museum +salem.museum +salvadordali.museum +salzburg.museum +sandiego.museum +sanfrancisco.museum +santabarbara.museum +santacruz.museum +santafe.museum +saskatchewan.museum +satx.museum +savannahga.museum +schlesisches.museum +schoenbrunn.museum +schokoladen.museum +school.museum +schweiz.museum +science.museum +scienceandhistory.museum +scienceandindustry.museum +sciencecenter.museum +sciencecenters.museum +science-fiction.museum +sciencehistory.museum +sciences.museum +sciencesnaturelles.museum +scotland.museum +seaport.museum +settlement.museum +settlers.museum +shell.museum +sherbrooke.museum +sibenik.museum +silk.museum +ski.museum +skole.museum +society.museum +sologne.museum +soundandvision.museum +southcarolina.museum +southwest.museum +space.museum +spy.museum +square.museum +stadt.museum +stalbans.museum +starnberg.museum +state.museum +stateofdelaware.museum +station.museum +steam.museum +steiermark.museum +stjohn.museum +stockholm.museum +stpetersburg.museum +stuttgart.museum +suisse.museum +surgeonshall.museum +surrey.museum +svizzera.museum +sweden.museum +sydney.museum +tank.museum +tcm.museum +technology.museum +telekommunikation.museum +television.museum +texas.museum +textile.museum +theater.museum +time.museum +timekeeping.museum +topology.museum +torino.museum +touch.museum +town.museum +transport.museum +tree.museum +trolley.museum +trust.museum +trustee.museum +uhren.museum +ulm.museum +undersea.museum +university.museum +usa.museum +usantiques.museum +usarts.museum +uscountryestate.museum +usculture.museum +usdecorativearts.museum +usgarden.museum +ushistory.museum +ushuaia.museum +uslivinghistory.museum +utah.museum +uvic.museum +valley.museum +vantaa.museum +versailles.museum +viking.museum +village.museum +virginia.museum +virtual.museum +virtuel.museum +vlaanderen.museum +volkenkunde.museum +wales.museum +wallonie.museum +war.museum +washingtondc.museum +watchandclock.museum +watch-and-clock.museum +western.museum +westfalen.museum +whaling.museum +wildlife.museum +williamsburg.museum +windmill.museum +workshop.museum +york.museum +yorkshire.museum +yosemite.museum +youth.museum +zoological.museum +zoology.museum +ירושלים.museum +иком.museum + +// mv : https://en.wikipedia.org/wiki/.mv +// "mv" included because, contra Wikipedia, google.mv exists. +mv +aero.mv +biz.mv +com.mv +coop.mv +edu.mv +gov.mv +info.mv +int.mv +mil.mv +museum.mv +name.mv +net.mv +org.mv +pro.mv + +// mw : http://www.registrar.mw/ +mw +ac.mw +biz.mw +co.mw +com.mw +coop.mw +edu.mw +gov.mw +int.mw +museum.mw +net.mw +org.mw + +// mx : http://www.nic.mx/ +// Submitted by registry +mx +com.mx +org.mx +gob.mx +edu.mx +net.mx + +// my : http://www.mynic.net.my/ +my +com.my +net.my +org.my +gov.my +edu.my +mil.my +name.my + +// mz : http://www.uem.mz/ +// Submitted by registry +mz +ac.mz +adv.mz +co.mz +edu.mz +gov.mz +mil.mz +net.mz +org.mz + +// na : http://www.na-nic.com.na/ +// http://www.info.na/domain/ +na +info.na +pro.na +name.na +school.na +or.na +dr.na +us.na +mx.na +ca.na +in.na +cc.na +tv.na +ws.na +mobi.na +co.na +com.na +org.na + +// name : has 2nd-level tlds, but there's no list of them +name + +// nc : http://www.cctld.nc/ +nc +asso.nc +nom.nc + +// ne : https://en.wikipedia.org/wiki/.ne +ne + +// net : https://en.wikipedia.org/wiki/.net +net + +// nf : https://en.wikipedia.org/wiki/.nf +nf +com.nf +net.nf +per.nf +rec.nf +web.nf +arts.nf +firm.nf +info.nf +other.nf +store.nf + +// ng : http://www.nira.org.ng/index.php/join-us/register-ng-domain/189-nira-slds +ng +com.ng +edu.ng +gov.ng +i.ng +mil.ng +mobi.ng +name.ng +net.ng +org.ng +sch.ng + +// ni : http://www.nic.ni/ +ni +ac.ni +biz.ni +co.ni +com.ni +edu.ni +gob.ni +in.ni +info.ni +int.ni +mil.ni +net.ni +nom.ni +org.ni +web.ni + +// nl : https://en.wikipedia.org/wiki/.nl +// https://www.sidn.nl/ +// ccTLD for the Netherlands +nl + +// no : http://www.norid.no/regelverk/index.en.html +// The Norwegian registry has declined to notify us of updates. The web pages +// referenced below are the official source of the data. There is also an +// announce mailing list: +// https://postlister.uninett.no/sympa/info/norid-diskusjon +no +// Norid generic domains : http://www.norid.no/regelverk/vedlegg-c.en.html +fhs.no +vgs.no +fylkesbibl.no +folkebibl.no +museum.no +idrett.no +priv.no +// Non-Norid generic domains : http://www.norid.no/regelverk/vedlegg-d.en.html +mil.no +stat.no +dep.no +kommune.no +herad.no +// no geographical names : http://www.norid.no/regelverk/vedlegg-b.en.html +// counties +aa.no +ah.no +bu.no +fm.no +hl.no +hm.no +jan-mayen.no +mr.no +nl.no +nt.no +of.no +ol.no +oslo.no +rl.no +sf.no +st.no +svalbard.no +tm.no +tr.no +va.no +vf.no +// primary and lower secondary schools per county +gs.aa.no +gs.ah.no +gs.bu.no +gs.fm.no +gs.hl.no +gs.hm.no +gs.jan-mayen.no +gs.mr.no +gs.nl.no +gs.nt.no +gs.of.no +gs.ol.no +gs.oslo.no +gs.rl.no +gs.sf.no +gs.st.no +gs.svalbard.no +gs.tm.no +gs.tr.no +gs.va.no +gs.vf.no +// cities +akrehamn.no +åkrehamn.no +algard.no +ålgård.no +arna.no +brumunddal.no +bryne.no +bronnoysund.no +brønnøysund.no +drobak.no +drøbak.no +egersund.no +fetsund.no +floro.no +florø.no +fredrikstad.no +hokksund.no +honefoss.no +hønefoss.no +jessheim.no +jorpeland.no +jørpeland.no +kirkenes.no +kopervik.no +krokstadelva.no +langevag.no +langevåg.no +leirvik.no +mjondalen.no +mjøndalen.no +mo-i-rana.no +mosjoen.no +mosjøen.no +nesoddtangen.no +orkanger.no +osoyro.no +osøyro.no +raholt.no +råholt.no +sandnessjoen.no +sandnessjøen.no +skedsmokorset.no +slattum.no +spjelkavik.no +stathelle.no +stavern.no +stjordalshalsen.no +stjørdalshalsen.no +tananger.no +tranby.no +vossevangen.no +// communities +afjord.no +åfjord.no +agdenes.no +al.no +ål.no +alesund.no +ålesund.no +alstahaug.no +alta.no +áltá.no +alaheadju.no +álaheadju.no +alvdal.no +amli.no +åmli.no +amot.no +åmot.no +andebu.no +andoy.no +andøy.no +andasuolo.no +ardal.no +årdal.no +aremark.no +arendal.no +ås.no +aseral.no +åseral.no +asker.no +askim.no +askvoll.no +askoy.no +askøy.no +asnes.no +åsnes.no +audnedaln.no +aukra.no +aure.no +aurland.no +aurskog-holand.no +aurskog-høland.no +austevoll.no +austrheim.no +averoy.no +averøy.no +balestrand.no +ballangen.no +balat.no +bálát.no +balsfjord.no +bahccavuotna.no +báhccavuotna.no +bamble.no +bardu.no +beardu.no +beiarn.no +bajddar.no +bájddar.no +baidar.no +báidár.no +berg.no +bergen.no +berlevag.no +berlevåg.no +bearalvahki.no +bearalváhki.no +bindal.no +birkenes.no +bjarkoy.no +bjarkøy.no +bjerkreim.no +bjugn.no +bodo.no +bodø.no +badaddja.no +bådåddjå.no +budejju.no +bokn.no +bremanger.no +bronnoy.no +brønnøy.no +bygland.no +bykle.no +barum.no +bærum.no +bo.telemark.no +bø.telemark.no +bo.nordland.no +bø.nordland.no +bievat.no +bievát.no +bomlo.no +bømlo.no +batsfjord.no +båtsfjord.no +bahcavuotna.no +báhcavuotna.no +dovre.no +drammen.no +drangedal.no +dyroy.no +dyrøy.no +donna.no +dønna.no +eid.no +eidfjord.no +eidsberg.no +eidskog.no +eidsvoll.no +eigersund.no +elverum.no +enebakk.no +engerdal.no +etne.no +etnedal.no +evenes.no +evenassi.no +evenášši.no +evje-og-hornnes.no +farsund.no +fauske.no +fuossko.no +fuoisku.no +fedje.no +fet.no +finnoy.no +finnøy.no +fitjar.no +fjaler.no +fjell.no +flakstad.no +flatanger.no +flekkefjord.no +flesberg.no +flora.no +fla.no +flå.no +folldal.no +forsand.no +fosnes.no +frei.no +frogn.no +froland.no +frosta.no +frana.no +fræna.no +froya.no +frøya.no +fusa.no +fyresdal.no +forde.no +førde.no +gamvik.no +gangaviika.no +gáŋgaviika.no +gaular.no +gausdal.no +gildeskal.no +gildeskål.no +giske.no +gjemnes.no +gjerdrum.no +gjerstad.no +gjesdal.no +gjovik.no +gjøvik.no +gloppen.no +gol.no +gran.no +grane.no +granvin.no +gratangen.no +grimstad.no +grong.no +kraanghke.no +kråanghke.no +grue.no +gulen.no +hadsel.no +halden.no +halsa.no +hamar.no +hamaroy.no +habmer.no +hábmer.no +hapmir.no +hápmir.no +hammerfest.no +hammarfeasta.no +hámmárfeasta.no +haram.no +hareid.no +harstad.no +hasvik.no +aknoluokta.no +ákŋoluokta.no +hattfjelldal.no +aarborte.no +haugesund.no +hemne.no +hemnes.no +hemsedal.no +heroy.more-og-romsdal.no +herøy.møre-og-romsdal.no +heroy.nordland.no +herøy.nordland.no +hitra.no +hjartdal.no +hjelmeland.no +hobol.no +hobøl.no +hof.no +hol.no +hole.no +holmestrand.no +holtalen.no +holtålen.no +hornindal.no +horten.no +hurdal.no +hurum.no +hvaler.no +hyllestad.no +hagebostad.no +hægebostad.no +hoyanger.no +høyanger.no +hoylandet.no +høylandet.no +ha.no +hå.no +ibestad.no +inderoy.no +inderøy.no +iveland.no +jevnaker.no +jondal.no +jolster.no +jølster.no +karasjok.no +karasjohka.no +kárášjohka.no +karlsoy.no +galsa.no +gálsá.no +karmoy.no +karmøy.no +kautokeino.no +guovdageaidnu.no +klepp.no +klabu.no +klæbu.no +kongsberg.no +kongsvinger.no +kragero.no +kragerø.no +kristiansand.no +kristiansund.no +krodsherad.no +krødsherad.no +kvalsund.no +rahkkeravju.no +ráhkkerávju.no +kvam.no +kvinesdal.no +kvinnherad.no +kviteseid.no +kvitsoy.no +kvitsøy.no +kvafjord.no +kvæfjord.no +giehtavuoatna.no +kvanangen.no +kvænangen.no +navuotna.no +návuotna.no +kafjord.no +kåfjord.no +gaivuotna.no +gáivuotna.no +larvik.no +lavangen.no +lavagis.no +loabat.no +loabát.no +lebesby.no +davvesiida.no +leikanger.no +leirfjord.no +leka.no +leksvik.no +lenvik.no +leangaviika.no +leaŋgaviika.no +lesja.no +levanger.no +lier.no +lierne.no +lillehammer.no +lillesand.no +lindesnes.no +lindas.no +lindås.no +lom.no +loppa.no +lahppi.no +láhppi.no +lund.no +lunner.no +luroy.no +lurøy.no +luster.no +lyngdal.no +lyngen.no +ivgu.no +lardal.no +lerdal.no +lærdal.no +lodingen.no +lødingen.no +lorenskog.no +lørenskog.no +loten.no +løten.no +malvik.no +masoy.no +måsøy.no +muosat.no +muosát.no +mandal.no +marker.no +marnardal.no +masfjorden.no +meland.no +meldal.no +melhus.no +meloy.no +meløy.no +meraker.no +meråker.no +moareke.no +moåreke.no +midsund.no +midtre-gauldal.no +modalen.no +modum.no +molde.no +moskenes.no +moss.no +mosvik.no +malselv.no +målselv.no +malatvuopmi.no +málatvuopmi.no +namdalseid.no +aejrie.no +namsos.no +namsskogan.no +naamesjevuemie.no +nååmesjevuemie.no +laakesvuemie.no +nannestad.no +narvik.no +narviika.no +naustdal.no +nedre-eiker.no +nes.akershus.no +nes.buskerud.no +nesna.no +nesodden.no +nesseby.no +unjarga.no +unjárga.no +nesset.no +nissedal.no +nittedal.no +nord-aurdal.no +nord-fron.no +nord-odal.no +norddal.no +nordkapp.no +davvenjarga.no +davvenjárga.no +nordre-land.no +nordreisa.no +raisa.no +ráisa.no +nore-og-uvdal.no +notodden.no +naroy.no +nærøy.no +notteroy.no +nøtterøy.no +odda.no +oksnes.no +øksnes.no +oppdal.no +oppegard.no +oppegård.no +orkdal.no +orland.no +ørland.no +orskog.no +ørskog.no +orsta.no +ørsta.no +os.hedmark.no +os.hordaland.no +osen.no +osteroy.no +osterøy.no +ostre-toten.no +østre-toten.no +overhalla.no +ovre-eiker.no +øvre-eiker.no +oyer.no +øyer.no +oygarden.no +øygarden.no +oystre-slidre.no +øystre-slidre.no +porsanger.no +porsangu.no +porsáŋgu.no +porsgrunn.no +radoy.no +radøy.no +rakkestad.no +rana.no +ruovat.no +randaberg.no +rauma.no +rendalen.no +rennebu.no +rennesoy.no +rennesøy.no +rindal.no +ringebu.no +ringerike.no +ringsaker.no +rissa.no +risor.no +risør.no +roan.no +rollag.no +rygge.no +ralingen.no +rælingen.no +rodoy.no +rødøy.no +romskog.no +rømskog.no +roros.no +røros.no +rost.no +røst.no +royken.no +røyken.no +royrvik.no +røyrvik.no +rade.no +råde.no +salangen.no +siellak.no +saltdal.no +salat.no +sálát.no +sálat.no +samnanger.no +sande.more-og-romsdal.no +sande.møre-og-romsdal.no +sande.vestfold.no +sandefjord.no +sandnes.no +sandoy.no +sandøy.no +sarpsborg.no +sauda.no +sauherad.no +sel.no +selbu.no +selje.no +seljord.no +sigdal.no +siljan.no +sirdal.no +skaun.no +skedsmo.no +ski.no +skien.no +skiptvet.no +skjervoy.no +skjervøy.no +skierva.no +skiervá.no +skjak.no +skjåk.no +skodje.no +skanland.no +skånland.no +skanit.no +skánit.no +smola.no +smøla.no +snillfjord.no +snasa.no +snåsa.no +snoasa.no +snaase.no +snåase.no +sogndal.no +sokndal.no +sola.no +solund.no +songdalen.no +sortland.no +spydeberg.no +stange.no +stavanger.no +steigen.no +steinkjer.no +stjordal.no +stjørdal.no +stokke.no +stor-elvdal.no +stord.no +stordal.no +storfjord.no +omasvuotna.no +strand.no +stranda.no +stryn.no +sula.no +suldal.no +sund.no +sunndal.no +surnadal.no +sveio.no +svelvik.no +sykkylven.no +sogne.no +søgne.no +somna.no +sømna.no +sondre-land.no +søndre-land.no +sor-aurdal.no +sør-aurdal.no +sor-fron.no +sør-fron.no +sor-odal.no +sør-odal.no +sor-varanger.no +sør-varanger.no +matta-varjjat.no +mátta-várjjat.no +sorfold.no +sørfold.no +sorreisa.no +sørreisa.no +sorum.no +sørum.no +tana.no +deatnu.no +time.no +tingvoll.no +tinn.no +tjeldsund.no +dielddanuorri.no +tjome.no +tjøme.no +tokke.no +tolga.no +torsken.no +tranoy.no +tranøy.no +tromso.no +tromsø.no +tromsa.no +romsa.no +trondheim.no +troandin.no +trysil.no +trana.no +træna.no +trogstad.no +trøgstad.no +tvedestrand.no +tydal.no +tynset.no +tysfjord.no +divtasvuodna.no +divttasvuotna.no +tysnes.no +tysvar.no +tysvær.no +tonsberg.no +tønsberg.no +ullensaker.no +ullensvang.no +ulvik.no +utsira.no +vadso.no +vadsø.no +cahcesuolo.no +čáhcesuolo.no +vaksdal.no +valle.no +vang.no +vanylven.no +vardo.no +vardø.no +varggat.no +várggát.no +vefsn.no +vaapste.no +vega.no +vegarshei.no +vegårshei.no +vennesla.no +verdal.no +verran.no +vestby.no +vestnes.no +vestre-slidre.no +vestre-toten.no +vestvagoy.no +vestvågøy.no +vevelstad.no +vik.no +vikna.no +vindafjord.no +volda.no +voss.no +varoy.no +værøy.no +vagan.no +vågan.no +voagat.no +vagsoy.no +vågsøy.no +vaga.no +vågå.no +valer.ostfold.no +våler.østfold.no +valer.hedmark.no +våler.hedmark.no + +// np : http://www.mos.com.np/register.html +*.np + +// nr : http://cenpac.net.nr/dns/index.html +// Submitted by registry +nr +biz.nr +info.nr +gov.nr +edu.nr +org.nr +net.nr +com.nr + +// nu : https://en.wikipedia.org/wiki/.nu +nu + +// nz : https://en.wikipedia.org/wiki/.nz +// Submitted by registry +nz +ac.nz +co.nz +cri.nz +geek.nz +gen.nz +govt.nz +health.nz +iwi.nz +kiwi.nz +maori.nz +mil.nz +māori.nz +net.nz +org.nz +parliament.nz +school.nz + +// om : https://en.wikipedia.org/wiki/.om +om +co.om +com.om +edu.om +gov.om +med.om +museum.om +net.om +org.om +pro.om + +// onion : https://tools.ietf.org/html/rfc7686 +onion + +// org : https://en.wikipedia.org/wiki/.org +org + +// pa : http://www.nic.pa/ +// Some additional second level "domains" resolve directly as hostnames, such as +// pannet.pa, so we add a rule for "pa". +pa +ac.pa +gob.pa +com.pa +org.pa +sld.pa +edu.pa +net.pa +ing.pa +abo.pa +med.pa +nom.pa + +// pe : https://www.nic.pe/InformeFinalComision.pdf +pe +edu.pe +gob.pe +nom.pe +mil.pe +org.pe +com.pe +net.pe + +// pf : http://www.gobin.info/domainname/formulaire-pf.pdf +pf +com.pf +org.pf +edu.pf + +// pg : https://en.wikipedia.org/wiki/.pg +*.pg + +// ph : http://www.domains.ph/FAQ2.asp +// Submitted by registry +ph +com.ph +net.ph +org.ph +gov.ph +edu.ph +ngo.ph +mil.ph +i.ph + +// pk : http://pk5.pknic.net.pk/pk5/msgNamepk.PK +pk +com.pk +net.pk +edu.pk +org.pk +fam.pk +biz.pk +web.pk +gov.pk +gob.pk +gok.pk +gon.pk +gop.pk +gos.pk +info.pk + +// pl http://www.dns.pl/english/index.html +// Submitted by registry +pl +com.pl +net.pl +org.pl +// pl functional domains (http://www.dns.pl/english/index.html) +aid.pl +agro.pl +atm.pl +auto.pl +biz.pl +edu.pl +gmina.pl +gsm.pl +info.pl +mail.pl +miasta.pl +media.pl +mil.pl +nieruchomosci.pl +nom.pl +pc.pl +powiat.pl +priv.pl +realestate.pl +rel.pl +sex.pl +shop.pl +sklep.pl +sos.pl +szkola.pl +targi.pl +tm.pl +tourism.pl +travel.pl +turystyka.pl +// Government domains +gov.pl +ap.gov.pl +ic.gov.pl +is.gov.pl +us.gov.pl +kmpsp.gov.pl +kppsp.gov.pl +kwpsp.gov.pl +psp.gov.pl +wskr.gov.pl +kwp.gov.pl +mw.gov.pl +ug.gov.pl +um.gov.pl +umig.gov.pl +ugim.gov.pl +upow.gov.pl +uw.gov.pl +starostwo.gov.pl +pa.gov.pl +po.gov.pl +psse.gov.pl +pup.gov.pl +rzgw.gov.pl +sa.gov.pl +so.gov.pl +sr.gov.pl +wsa.gov.pl +sko.gov.pl +uzs.gov.pl +wiih.gov.pl +winb.gov.pl +pinb.gov.pl +wios.gov.pl +witd.gov.pl +wzmiuw.gov.pl +piw.gov.pl +wiw.gov.pl +griw.gov.pl +wif.gov.pl +oum.gov.pl +sdn.gov.pl +zp.gov.pl +uppo.gov.pl +mup.gov.pl +wuoz.gov.pl +konsulat.gov.pl +oirm.gov.pl +// pl regional domains (http://www.dns.pl/english/index.html) +augustow.pl +babia-gora.pl +bedzin.pl +beskidy.pl +bialowieza.pl +bialystok.pl +bielawa.pl +bieszczady.pl +boleslawiec.pl +bydgoszcz.pl +bytom.pl +cieszyn.pl +czeladz.pl +czest.pl +dlugoleka.pl +elblag.pl +elk.pl +glogow.pl +gniezno.pl +gorlice.pl +grajewo.pl +ilawa.pl +jaworzno.pl +jelenia-gora.pl +jgora.pl +kalisz.pl +kazimierz-dolny.pl +karpacz.pl +kartuzy.pl +kaszuby.pl +katowice.pl +kepno.pl +ketrzyn.pl +klodzko.pl +kobierzyce.pl +kolobrzeg.pl +konin.pl +konskowola.pl +kutno.pl +lapy.pl +lebork.pl +legnica.pl +lezajsk.pl +limanowa.pl +lomza.pl +lowicz.pl +lubin.pl +lukow.pl +malbork.pl +malopolska.pl +mazowsze.pl +mazury.pl +mielec.pl +mielno.pl +mragowo.pl +naklo.pl +nowaruda.pl +nysa.pl +olawa.pl +olecko.pl +olkusz.pl +olsztyn.pl +opoczno.pl +opole.pl +ostroda.pl +ostroleka.pl +ostrowiec.pl +ostrowwlkp.pl +pila.pl +pisz.pl +podhale.pl +podlasie.pl +polkowice.pl +pomorze.pl +pomorskie.pl +prochowice.pl +pruszkow.pl +przeworsk.pl +pulawy.pl +radom.pl +rawa-maz.pl +rybnik.pl +rzeszow.pl +sanok.pl +sejny.pl +slask.pl +slupsk.pl +sosnowiec.pl +stalowa-wola.pl +skoczow.pl +starachowice.pl +stargard.pl +suwalki.pl +swidnica.pl +swiebodzin.pl +swinoujscie.pl +szczecin.pl +szczytno.pl +tarnobrzeg.pl +tgory.pl +turek.pl +tychy.pl +ustka.pl +walbrzych.pl +warmia.pl +warszawa.pl +waw.pl +wegrow.pl +wielun.pl +wlocl.pl +wloclawek.pl +wodzislaw.pl +wolomin.pl +wroclaw.pl +zachpomor.pl +zagan.pl +zarow.pl +zgora.pl +zgorzelec.pl + +// pm : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.pdf +pm + +// pn : http://www.government.pn/PnRegistry/policies.htm +pn +gov.pn +co.pn +org.pn +edu.pn +net.pn + +// post : https://en.wikipedia.org/wiki/.post +post + +// pr : http://www.nic.pr/index.asp?f=1 +pr +com.pr +net.pr +org.pr +gov.pr +edu.pr +isla.pr +pro.pr +biz.pr +info.pr +name.pr +// these aren't mentioned on nic.pr, but on https://en.wikipedia.org/wiki/.pr +est.pr +prof.pr +ac.pr + +// pro : http://registry.pro/get-pro +pro +aaa.pro +aca.pro +acct.pro +avocat.pro +bar.pro +cpa.pro +eng.pro +jur.pro +law.pro +med.pro +recht.pro + +// ps : https://en.wikipedia.org/wiki/.ps +// http://www.nic.ps/registration/policy.html#reg +ps +edu.ps +gov.ps +sec.ps +plo.ps +com.ps +org.ps +net.ps + +// pt : http://online.dns.pt/dns/start_dns +pt +net.pt +gov.pt +org.pt +edu.pt +int.pt +publ.pt +com.pt +nome.pt + +// pw : https://en.wikipedia.org/wiki/.pw +pw +co.pw +ne.pw +or.pw +ed.pw +go.pw +belau.pw + +// py : http://www.nic.py/pautas.html#seccion_9 +// Submitted by registry +py +com.py +coop.py +edu.py +gov.py +mil.py +net.py +org.py + +// qa : http://domains.qa/en/ +qa +com.qa +edu.qa +gov.qa +mil.qa +name.qa +net.qa +org.qa +sch.qa + +// re : http://www.afnic.re/obtenir/chartes/nommage-re/annexe-descriptifs +re +asso.re +com.re +nom.re + +// ro : http://www.rotld.ro/ +ro +arts.ro +com.ro +firm.ro +info.ro +nom.ro +nt.ro +org.ro +rec.ro +store.ro +tm.ro +www.ro + +// rs : https://www.rnids.rs/en/domains/national-domains +rs +ac.rs +co.rs +edu.rs +gov.rs +in.rs +org.rs + +// ru : https://cctld.ru/en/domains/domens_ru/reserved/ +ru +ac.ru +edu.ru +gov.ru +int.ru +mil.ru +test.ru + +// rw : http://www.nic.rw/cgi-bin/policy.pl +rw +gov.rw +net.rw +edu.rw +ac.rw +com.rw +co.rw +int.rw +mil.rw +gouv.rw + +// sa : http://www.nic.net.sa/ +sa +com.sa +net.sa +org.sa +gov.sa +med.sa +pub.sa +edu.sa +sch.sa + +// sb : http://www.sbnic.net.sb/ +// Submitted by registry +sb +com.sb +edu.sb +gov.sb +net.sb +org.sb + +// sc : http://www.nic.sc/ +sc +com.sc +gov.sc +net.sc +org.sc +edu.sc + +// sd : http://www.isoc.sd/sudanic.isoc.sd/billing_pricing.htm +// Submitted by registry +sd +com.sd +net.sd +org.sd +edu.sd +med.sd +tv.sd +gov.sd +info.sd + +// se : https://en.wikipedia.org/wiki/.se +// Submitted by registry +se +a.se +ac.se +b.se +bd.se +brand.se +c.se +d.se +e.se +f.se +fh.se +fhsk.se +fhv.se +g.se +h.se +i.se +k.se +komforb.se +kommunalforbund.se +komvux.se +l.se +lanbib.se +m.se +n.se +naturbruksgymn.se +o.se +org.se +p.se +parti.se +pp.se +press.se +r.se +s.se +t.se +tm.se +u.se +w.se +x.se +y.se +z.se + +// sg : http://www.nic.net.sg/page/registration-policies-procedures-and-guidelines +sg +com.sg +net.sg +org.sg +gov.sg +edu.sg +per.sg + +// sh : http://www.nic.sh/registrar.html +sh +com.sh +net.sh +gov.sh +org.sh +mil.sh + +// si : https://en.wikipedia.org/wiki/.si +si + +// sj : No registrations at this time. +// Submitted by registry +sj + +// sk : https://en.wikipedia.org/wiki/.sk +// list of 2nd level domains ? +sk + +// sl : http://www.nic.sl +// Submitted by registry +sl +com.sl +net.sl +edu.sl +gov.sl +org.sl + +// sm : https://en.wikipedia.org/wiki/.sm +sm + +// sn : https://en.wikipedia.org/wiki/.sn +sn +art.sn +com.sn +edu.sn +gouv.sn +org.sn +perso.sn +univ.sn + +// so : http://www.soregistry.com/ +so +com.so +net.so +org.so + +// sr : https://en.wikipedia.org/wiki/.sr +sr + +// st : http://www.nic.st/html/policyrules/ +st +co.st +com.st +consulado.st +edu.st +embaixada.st +gov.st +mil.st +net.st +org.st +principe.st +saotome.st +store.st + +// su : https://en.wikipedia.org/wiki/.su +su + +// sv : http://www.svnet.org.sv/niveldos.pdf +sv +com.sv +edu.sv +gob.sv +org.sv +red.sv + +// sx : https://en.wikipedia.org/wiki/.sx +// Submitted by registry +sx +gov.sx + +// sy : https://en.wikipedia.org/wiki/.sy +// see also: http://www.gobin.info/domainname/sy.doc +sy +edu.sy +gov.sy +net.sy +mil.sy +com.sy +org.sy + +// sz : https://en.wikipedia.org/wiki/.sz +// http://www.sispa.org.sz/ +sz +co.sz +ac.sz +org.sz + +// tc : https://en.wikipedia.org/wiki/.tc +tc + +// td : https://en.wikipedia.org/wiki/.td +td + +// tel: https://en.wikipedia.org/wiki/.tel +// http://www.telnic.org/ +tel + +// tf : https://en.wikipedia.org/wiki/.tf +tf + +// tg : https://en.wikipedia.org/wiki/.tg +// http://www.nic.tg/ +tg + +// th : https://en.wikipedia.org/wiki/.th +// Submitted by registry +th +ac.th +co.th +go.th +in.th +mi.th +net.th +or.th + +// tj : http://www.nic.tj/policy.html +tj +ac.tj +biz.tj +co.tj +com.tj +edu.tj +go.tj +gov.tj +int.tj +mil.tj +name.tj +net.tj +nic.tj +org.tj +test.tj +web.tj + +// tk : https://en.wikipedia.org/wiki/.tk +tk + +// tl : https://en.wikipedia.org/wiki/.tl +tl +gov.tl + +// tm : http://www.nic.tm/local.html +tm +com.tm +co.tm +org.tm +net.tm +nom.tm +gov.tm +mil.tm +edu.tm + +// tn : https://en.wikipedia.org/wiki/.tn +// http://whois.ati.tn/ +tn +com.tn +ens.tn +fin.tn +gov.tn +ind.tn +intl.tn +nat.tn +net.tn +org.tn +info.tn +perso.tn +tourism.tn +edunet.tn +rnrt.tn +rns.tn +rnu.tn +mincom.tn +agrinet.tn +defense.tn +turen.tn + +// to : https://en.wikipedia.org/wiki/.to +// Submitted by registry +to +com.to +gov.to +net.to +org.to +edu.to +mil.to + +// subTLDs: https://www.nic.tr/forms/eng/policies.pdf +// and: https://www.nic.tr/forms/politikalar.pdf +// Submitted by +tr +com.tr +info.tr +biz.tr +net.tr +org.tr +web.tr +gen.tr +tv.tr +av.tr +dr.tr +bbs.tr +name.tr +tel.tr +gov.tr +bel.tr +pol.tr +mil.tr +k12.tr +edu.tr +kep.tr + +// Used by Northern Cyprus +nc.tr + +// Used by government agencies of Northern Cyprus +gov.nc.tr + +// tt : http://www.nic.tt/ +tt +co.tt +com.tt +org.tt +net.tt +biz.tt +info.tt +pro.tt +int.tt +coop.tt +jobs.tt +mobi.tt +travel.tt +museum.tt +aero.tt +name.tt +gov.tt +edu.tt + +// tv : https://en.wikipedia.org/wiki/.tv +// Not listing any 2LDs as reserved since none seem to exist in practice, +// Wikipedia notwithstanding. +tv + +// tw : https://en.wikipedia.org/wiki/.tw +tw +edu.tw +gov.tw +mil.tw +com.tw +net.tw +org.tw +idv.tw +game.tw +ebiz.tw +club.tw +網路.tw +組織.tw +商業.tw + +// tz : http://www.tznic.or.tz/index.php/domains +// Submitted by registry +tz +ac.tz +co.tz +go.tz +hotel.tz +info.tz +me.tz +mil.tz +mobi.tz +ne.tz +or.tz +sc.tz +tv.tz + +// ua : https://hostmaster.ua/policy/?ua +// Submitted by registry +ua +// ua 2LD +com.ua +edu.ua +gov.ua +in.ua +net.ua +org.ua +// ua geographic names +// https://hostmaster.ua/2ld/ +cherkassy.ua +cherkasy.ua +chernigov.ua +chernihiv.ua +chernivtsi.ua +chernovtsy.ua +ck.ua +cn.ua +cr.ua +crimea.ua +cv.ua +dn.ua +dnepropetrovsk.ua +dnipropetrovsk.ua +dominic.ua +donetsk.ua +dp.ua +if.ua +ivano-frankivsk.ua +kh.ua +kharkiv.ua +kharkov.ua +kherson.ua +khmelnitskiy.ua +khmelnytskyi.ua +kiev.ua +kirovograd.ua +km.ua +kr.ua +krym.ua +ks.ua +kv.ua +kyiv.ua +lg.ua +lt.ua +lugansk.ua +lutsk.ua +lv.ua +lviv.ua +mk.ua +mykolaiv.ua +nikolaev.ua +od.ua +odesa.ua +odessa.ua +pl.ua +poltava.ua +rivne.ua +rovno.ua +rv.ua +sb.ua +sebastopol.ua +sevastopol.ua +sm.ua +sumy.ua +te.ua +ternopil.ua +uz.ua +uzhgorod.ua +vinnica.ua +vinnytsia.ua +vn.ua +volyn.ua +yalta.ua +zaporizhzhe.ua +zaporizhzhia.ua +zhitomir.ua +zhytomyr.ua +zp.ua +zt.ua + +// ug : https://www.registry.co.ug/ +ug +co.ug +or.ug +ac.ug +sc.ug +go.ug +ne.ug +com.ug +org.ug + +// uk : https://en.wikipedia.org/wiki/.uk +// Submitted by registry +uk +ac.uk +co.uk +gov.uk +ltd.uk +me.uk +net.uk +nhs.uk +org.uk +plc.uk +police.uk +*.sch.uk + +// us : https://en.wikipedia.org/wiki/.us +us +dni.us +fed.us +isa.us +kids.us +nsn.us +// us geographic names +ak.us +al.us +ar.us +as.us +az.us +ca.us +co.us +ct.us +dc.us +de.us +fl.us +ga.us +gu.us +hi.us +ia.us +id.us +il.us +in.us +ks.us +ky.us +la.us +ma.us +md.us +me.us +mi.us +mn.us +mo.us +ms.us +mt.us +nc.us +nd.us +ne.us +nh.us +nj.us +nm.us +nv.us +ny.us +oh.us +ok.us +or.us +pa.us +pr.us +ri.us +sc.us +sd.us +tn.us +tx.us +ut.us +vi.us +vt.us +va.us +wa.us +wi.us +wv.us +wy.us +// The registrar notes several more specific domains available in each state, +// such as state.*.us, dst.*.us, etc., but resolution of these is somewhat +// haphazard; in some states these domains resolve as addresses, while in others +// only subdomains are available, or even nothing at all. We include the +// most common ones where it's clear that different sites are different +// entities. +k12.ak.us +k12.al.us +k12.ar.us +k12.as.us +k12.az.us +k12.ca.us +k12.co.us +k12.ct.us +k12.dc.us +k12.de.us +k12.fl.us +k12.ga.us +k12.gu.us +// k12.hi.us Bug 614565 - Hawaii has a state-wide DOE login +k12.ia.us +k12.id.us +k12.il.us +k12.in.us +k12.ks.us +k12.ky.us +k12.la.us +k12.ma.us +k12.md.us +k12.me.us +k12.mi.us +k12.mn.us +k12.mo.us +k12.ms.us +k12.mt.us +k12.nc.us +// k12.nd.us Bug 1028347 - Removed at request of Travis Rosso +k12.ne.us +k12.nh.us +k12.nj.us +k12.nm.us +k12.nv.us +k12.ny.us +k12.oh.us +k12.ok.us +k12.or.us +k12.pa.us +k12.pr.us +k12.ri.us +k12.sc.us +// k12.sd.us Bug 934131 - Removed at request of James Booze +k12.tn.us +k12.tx.us +k12.ut.us +k12.vi.us +k12.vt.us +k12.va.us +k12.wa.us +k12.wi.us +// k12.wv.us Bug 947705 - Removed at request of Verne Britton +k12.wy.us +cc.ak.us +cc.al.us +cc.ar.us +cc.as.us +cc.az.us +cc.ca.us +cc.co.us +cc.ct.us +cc.dc.us +cc.de.us +cc.fl.us +cc.ga.us +cc.gu.us +cc.hi.us +cc.ia.us +cc.id.us +cc.il.us +cc.in.us +cc.ks.us +cc.ky.us +cc.la.us +cc.ma.us +cc.md.us +cc.me.us +cc.mi.us +cc.mn.us +cc.mo.us +cc.ms.us +cc.mt.us +cc.nc.us +cc.nd.us +cc.ne.us +cc.nh.us +cc.nj.us +cc.nm.us +cc.nv.us +cc.ny.us +cc.oh.us +cc.ok.us +cc.or.us +cc.pa.us +cc.pr.us +cc.ri.us +cc.sc.us +cc.sd.us +cc.tn.us +cc.tx.us +cc.ut.us +cc.vi.us +cc.vt.us +cc.va.us +cc.wa.us +cc.wi.us +cc.wv.us +cc.wy.us +lib.ak.us +lib.al.us +lib.ar.us +lib.as.us +lib.az.us +lib.ca.us +lib.co.us +lib.ct.us +lib.dc.us +// lib.de.us Issue #243 - Moved to Private section at request of Ed Moore +lib.fl.us +lib.ga.us +lib.gu.us +lib.hi.us +lib.ia.us +lib.id.us +lib.il.us +lib.in.us +lib.ks.us +lib.ky.us +lib.la.us +lib.ma.us +lib.md.us +lib.me.us +lib.mi.us +lib.mn.us +lib.mo.us +lib.ms.us +lib.mt.us +lib.nc.us +lib.nd.us +lib.ne.us +lib.nh.us +lib.nj.us +lib.nm.us +lib.nv.us +lib.ny.us +lib.oh.us +lib.ok.us +lib.or.us +lib.pa.us +lib.pr.us +lib.ri.us +lib.sc.us +lib.sd.us +lib.tn.us +lib.tx.us +lib.ut.us +lib.vi.us +lib.vt.us +lib.va.us +lib.wa.us +lib.wi.us +// lib.wv.us Bug 941670 - Removed at request of Larry W Arnold +lib.wy.us +// k12.ma.us contains school districts in Massachusetts. The 4LDs are +// managed independently except for private (PVT), charter (CHTR) and +// parochial (PAROCH) schools. Those are delegated directly to the +// 5LD operators. +pvt.k12.ma.us +chtr.k12.ma.us +paroch.k12.ma.us +// Merit Network, Inc. maintains the registry for =~ /(k12|cc|lib).mi.us/ and the following +// see also: http://domreg.merit.edu +// see also: whois -h whois.domreg.merit.edu help +ann-arbor.mi.us +cog.mi.us +dst.mi.us +eaton.mi.us +gen.mi.us +mus.mi.us +tec.mi.us +washtenaw.mi.us + +// uy : http://www.nic.org.uy/ +uy +com.uy +edu.uy +gub.uy +mil.uy +net.uy +org.uy + +// uz : http://www.reg.uz/ +uz +co.uz +com.uz +net.uz +org.uz + +// va : https://en.wikipedia.org/wiki/.va +va + +// vc : https://en.wikipedia.org/wiki/.vc +// Submitted by registry +vc +com.vc +net.vc +org.vc +gov.vc +mil.vc +edu.vc + +// ve : https://registro.nic.ve/ +// Submitted by registry +ve +arts.ve +co.ve +com.ve +e12.ve +edu.ve +firm.ve +gob.ve +gov.ve +info.ve +int.ve +mil.ve +net.ve +org.ve +rec.ve +store.ve +tec.ve +web.ve + +// vg : https://en.wikipedia.org/wiki/.vg +vg + +// vi : http://www.nic.vi/newdomainform.htm +// http://www.nic.vi/Domain_Rules/body_domain_rules.html indicates some other +// TLDs are "reserved", such as edu.vi and gov.vi, but doesn't actually say they +// are available for registration (which they do not seem to be). +vi +co.vi +com.vi +k12.vi +net.vi +org.vi + +// vn : https://www.dot.vn/vnnic/vnnic/domainregistration.jsp +vn +com.vn +net.vn +org.vn +edu.vn +gov.vn +int.vn +ac.vn +biz.vn +info.vn +name.vn +pro.vn +health.vn + +// vu : https://en.wikipedia.org/wiki/.vu +// http://www.vunic.vu/ +vu +com.vu +edu.vu +net.vu +org.vu + +// wf : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.pdf +wf + +// ws : https://en.wikipedia.org/wiki/.ws +// http://samoanic.ws/index.dhtml +ws +com.ws +net.ws +org.ws +gov.ws +edu.ws + +// yt : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.pdf +yt + +// IDN ccTLDs +// When submitting patches, please maintain a sort by ISO 3166 ccTLD, then +// U-label, and follow this format: +// // A-Label ("", [, variant info]) : +// // [sponsoring org] +// U-Label + +// xn--mgbaam7a8h ("Emerat", Arabic) : AE +// http://nic.ae/english/arabicdomain/rules.jsp +امارات + +// xn--y9a3aq ("hye", Armenian) : AM +// ISOC AM (operated by .am Registry) +հայ + +// xn--54b7fta0cc ("Bangla", Bangla) : BD +বাংলা + +// xn--90ae ("bg", Bulgarian) : BG +бг + +// xn--90ais ("bel", Belarusian/Russian Cyrillic) : BY +// Operated by .by registry +бел + +// xn--fiqs8s ("Zhongguo/China", Chinese, Simplified) : CN +// CNNIC +// http://cnnic.cn/html/Dir/2005/10/11/3218.htm +中国 + +// xn--fiqz9s ("Zhongguo/China", Chinese, Traditional) : CN +// CNNIC +// http://cnnic.cn/html/Dir/2005/10/11/3218.htm +中國 + +// xn--lgbbat1ad8j ("Algeria/Al Jazair", Arabic) : DZ +الجزائر + +// xn--wgbh1c ("Egypt/Masr", Arabic) : EG +// http://www.dotmasr.eg/ +مصر + +// xn--e1a4c ("eu", Cyrillic) : EU +ею + +// xn--node ("ge", Georgian Mkhedruli) : GE +გე + +// xn--qxam ("el", Greek) : GR +// Hellenic Ministry of Infrastructure, Transport, and Networks +ελ + +// xn--j6w193g ("Hong Kong", Chinese) : HK +// https://www.hkirc.hk +// Submitted by registry +// https://www.hkirc.hk/content.jsp?id=30#!/34 +香港 +公司.香港 +教育.香港 +政府.香港 +個人.香港 +網絡.香港 +組織.香港 + +// xn--2scrj9c ("Bharat", Kannada) : IN +// India +ಭಾರತ + +// xn--3hcrj9c ("Bharat", Oriya) : IN +// India +ଭାରତ + +// xn--45br5cyl ("Bharatam", Assamese) : IN +// India +ভাৰত + +// xn--h2breg3eve ("Bharatam", Sanskrit) : IN +// India +भारतम् + +// xn--h2brj9c8c ("Bharot", Santali) : IN +// India +भारोत + +// xn--mgbgu82a ("Bharat", Sindhi) : IN +// India +ڀارت + +// xn--rvc1e0am3e ("Bharatam", Malayalam) : IN +// India +ഭാരതം + +// xn--h2brj9c ("Bharat", Devanagari) : IN +// India +भारत + +// xn--mgbbh1a ("Bharat", Kashmiri) : IN +// India +بارت + +// xn--mgbbh1a71e ("Bharat", Arabic) : IN +// India +بھارت + +// xn--fpcrj9c3d ("Bharat", Telugu) : IN +// India +భారత్ + +// xn--gecrj9c ("Bharat", Gujarati) : IN +// India +ભારત + +// xn--s9brj9c ("Bharat", Gurmukhi) : IN +// India +ਭਾਰਤ + +// xn--45brj9c ("Bharat", Bengali) : IN +// India +ভারত + +// xn--xkc2dl3a5ee0h ("India", Tamil) : IN +// India +இந்தியா + +// xn--mgba3a4f16a ("Iran", Persian) : IR +ایران + +// xn--mgba3a4fra ("Iran", Arabic) : IR +ايران + +// xn--mgbtx2b ("Iraq", Arabic) : IQ +// Communications and Media Commission +عراق + +// xn--mgbayh7gpa ("al-Ordon", Arabic) : JO +// National Information Technology Center (NITC) +// Royal Scientific Society, Al-Jubeiha +الاردن + +// xn--3e0b707e ("Republic of Korea", Hangul) : KR +한국 + +// xn--80ao21a ("Kaz", Kazakh) : KZ +қаз + +// xn--fzc2c9e2c ("Lanka", Sinhalese-Sinhala) : LK +// http://nic.lk +ලංකා + +// xn--xkc2al3hye2a ("Ilangai", Tamil) : LK +// http://nic.lk +இலங்கை + +// xn--mgbc0a9azcg ("Morocco/al-Maghrib", Arabic) : MA +المغرب + +// xn--d1alf ("mkd", Macedonian) : MK +// MARnet +мкд + +// xn--l1acc ("mon", Mongolian) : MN +мон + +// xn--mix891f ("Macao", Chinese, Traditional) : MO +// MONIC / HNET Asia (Registry Operator for .mo) +澳門 + +// xn--mix082f ("Macao", Chinese, Simplified) : MO +澳门 + +// xn--mgbx4cd0ab ("Malaysia", Malay) : MY +مليسيا + +// xn--mgb9awbf ("Oman", Arabic) : OM +عمان + +// xn--mgbai9azgqp6j ("Pakistan", Urdu/Arabic) : PK +پاکستان + +// xn--mgbai9a5eva00b ("Pakistan", Urdu/Arabic, variant) : PK +پاكستان + +// xn--ygbi2ammx ("Falasteen", Arabic) : PS +// The Palestinian National Internet Naming Authority (PNINA) +// http://www.pnina.ps +فلسطين + +// xn--90a3ac ("srb", Cyrillic) : RS +// https://www.rnids.rs/en/domains/national-domains +срб +пр.срб +орг.срб +обр.срб +од.срб +упр.срб +ак.срб + +// xn--p1ai ("rf", Russian-Cyrillic) : RU +// http://www.cctld.ru/en/docs/rulesrf.php +рф + +// xn--wgbl6a ("Qatar", Arabic) : QA +// http://www.ict.gov.qa/ +قطر + +// xn--mgberp4a5d4ar ("AlSaudiah", Arabic) : SA +// http://www.nic.net.sa/ +السعودية + +// xn--mgberp4a5d4a87g ("AlSaudiah", Arabic, variant) : SA +السعودیة + +// xn--mgbqly7c0a67fbc ("AlSaudiah", Arabic, variant) : SA +السعودیۃ + +// xn--mgbqly7cvafr ("AlSaudiah", Arabic, variant) : SA +السعوديه + +// xn--mgbpl2fh ("sudan", Arabic) : SD +// Operated by .sd registry +سودان + +// xn--yfro4i67o Singapore ("Singapore", Chinese) : SG +新加坡 + +// xn--clchc0ea0b2g2a9gcd ("Singapore", Tamil) : SG +சிங்கப்பூர் + +// xn--ogbpf8fl ("Syria", Arabic) : SY +سورية + +// xn--mgbtf8fl ("Syria", Arabic, variant) : SY +سوريا + +// xn--o3cw4h ("Thai", Thai) : TH +// http://www.thnic.co.th +ไทย +ศึกษา.ไทย +ธุรกิจ.ไทย +รัฐบาล.ไทย +ทหาร.ไทย +เน็ต.ไทย +องค์กร.ไทย + +// xn--pgbs0dh ("Tunisia", Arabic) : TN +// http://nic.tn +تونس + +// xn--kpry57d ("Taiwan", Chinese, Traditional) : TW +// http://www.twnic.net/english/dn/dn_07a.htm +台灣 + +// xn--kprw13d ("Taiwan", Chinese, Simplified) : TW +// http://www.twnic.net/english/dn/dn_07a.htm +台湾 + +// xn--nnx388a ("Taiwan", Chinese, variant) : TW +臺灣 + +// xn--j1amh ("ukr", Cyrillic) : UA +укр + +// xn--mgb2ddes ("AlYemen", Arabic) : YE +اليمن + +// xxx : http://icmregistry.com +xxx + +// ye : http://www.y.net.ye/services/domain_name.htm +*.ye + +// za : http://www.zadna.org.za/content/page/domain-information +ac.za +agric.za +alt.za +co.za +edu.za +gov.za +grondar.za +law.za +mil.za +net.za +ngo.za +nis.za +nom.za +org.za +school.za +tm.za +web.za + +// zm : https://zicta.zm/ +// Submitted by registry +zm +ac.zm +biz.zm +co.zm +com.zm +edu.zm +gov.zm +info.zm +mil.zm +net.zm +org.zm +sch.zm + +// zw : https://www.potraz.gov.zw/ +// Confirmed by registry 2017-01-25 +zw +ac.zw +co.zw +gov.zw +mil.zw +org.zw + + +// newGTLDs +// List of new gTLDs imported from https://newgtlds.icann.org/newgtlds.csv on 2018-05-08T19:40:37Z +// This list is auto-generated, don't edit it manually. + +// aaa : 2015-02-26 American Automobile Association, Inc. +aaa + +// aarp : 2015-05-21 AARP +aarp + +// abarth : 2015-07-30 Fiat Chrysler Automobiles N.V. +abarth + +// abb : 2014-10-24 ABB Ltd +abb + +// abbott : 2014-07-24 Abbott Laboratories, Inc. +abbott + +// abbvie : 2015-07-30 AbbVie Inc. +abbvie + +// abc : 2015-07-30 Disney Enterprises, Inc. +abc + +// able : 2015-06-25 Able Inc. +able + +// abogado : 2014-04-24 Minds + Machines Group Limited +abogado + +// abudhabi : 2015-07-30 Abu Dhabi Systems and Information Centre +abudhabi + +// academy : 2013-11-07 Binky Moon, LLC +academy + +// accenture : 2014-08-15 Accenture plc +accenture + +// accountant : 2014-11-20 dot Accountant Limited +accountant + +// accountants : 2014-03-20 Binky Moon, LLC +accountants + +// aco : 2015-01-08 ACO Severin Ahlmann GmbH & Co. KG +aco + +// active : 2014-05-01 Active Network, LLC +active + +// actor : 2013-12-12 United TLD Holdco Ltd. +actor + +// adac : 2015-07-16 Allgemeiner Deutscher Automobil-Club e.V. (ADAC) +adac + +// ads : 2014-12-04 Charleston Road Registry Inc. +ads + +// adult : 2014-10-16 ICM Registry AD LLC +adult + +// aeg : 2015-03-19 Aktiebolaget Electrolux +aeg + +// aetna : 2015-05-21 Aetna Life Insurance Company +aetna + +// afamilycompany : 2015-07-23 Johnson Shareholdings, Inc. +afamilycompany + +// afl : 2014-10-02 Australian Football League +afl + +// africa : 2014-03-24 ZA Central Registry NPC trading as Registry.Africa +africa + +// agakhan : 2015-04-23 Fondation Aga Khan (Aga Khan Foundation) +agakhan + +// agency : 2013-11-14 Binky Moon, LLC +agency + +// aig : 2014-12-18 American International Group, Inc. +aig + +// aigo : 2015-08-06 aigo Digital Technology Co,Ltd. +aigo + +// airbus : 2015-07-30 Airbus S.A.S. +airbus + +// airforce : 2014-03-06 United TLD Holdco Ltd. +airforce + +// airtel : 2014-10-24 Bharti Airtel Limited +airtel + +// akdn : 2015-04-23 Fondation Aga Khan (Aga Khan Foundation) +akdn + +// alfaromeo : 2015-07-31 Fiat Chrysler Automobiles N.V. +alfaromeo + +// alibaba : 2015-01-15 Alibaba Group Holding Limited +alibaba + +// alipay : 2015-01-15 Alibaba Group Holding Limited +alipay + +// allfinanz : 2014-07-03 Allfinanz Deutsche Vermögensberatung Aktiengesellschaft +allfinanz + +// allstate : 2015-07-31 Allstate Fire and Casualty Insurance Company +allstate + +// ally : 2015-06-18 Ally Financial Inc. +ally + +// alsace : 2014-07-02 Region Grand Est +alsace + +// alstom : 2015-07-30 ALSTOM +alstom + +// americanexpress : 2015-07-31 American Express Travel Related Services Company, Inc. +americanexpress + +// americanfamily : 2015-07-23 AmFam, Inc. +americanfamily + +// amex : 2015-07-31 American Express Travel Related Services Company, Inc. +amex + +// amfam : 2015-07-23 AmFam, Inc. +amfam + +// amica : 2015-05-28 Amica Mutual Insurance Company +amica + +// amsterdam : 2014-07-24 Gemeente Amsterdam +amsterdam + +// analytics : 2014-12-18 Campus IP LLC +analytics + +// android : 2014-08-07 Charleston Road Registry Inc. +android + +// anquan : 2015-01-08 QIHOO 360 TECHNOLOGY CO. LTD. +anquan + +// anz : 2015-07-31 Australia and New Zealand Banking Group Limited +anz + +// aol : 2015-09-17 Oath Inc. +aol + +// apartments : 2014-12-11 Binky Moon, LLC +apartments + +// app : 2015-05-14 Charleston Road Registry Inc. +app + +// apple : 2015-05-14 Apple Inc. +apple + +// aquarelle : 2014-07-24 Aquarelle.com +aquarelle + +// arab : 2015-11-12 League of Arab States +arab + +// aramco : 2014-11-20 Aramco Services Company +aramco + +// archi : 2014-02-06 Afilias plc +archi + +// army : 2014-03-06 United TLD Holdco Ltd. +army + +// art : 2016-03-24 UK Creative Ideas Limited +art + +// arte : 2014-12-11 Association Relative à la Télévision Européenne G.E.I.E. +arte + +// asda : 2015-07-31 Wal-Mart Stores, Inc. +asda + +// associates : 2014-03-06 Binky Moon, LLC +associates + +// athleta : 2015-07-30 The Gap, Inc. +athleta + +// attorney : 2014-03-20 United TLD Holdco Ltd. +attorney + +// auction : 2014-03-20 United TLD Holdco Ltd. +auction + +// audi : 2015-05-21 AUDI Aktiengesellschaft +audi + +// audible : 2015-06-25 Amazon Registry Services, Inc. +audible + +// audio : 2014-03-20 Uniregistry, Corp. +audio + +// auspost : 2015-08-13 Australian Postal Corporation +auspost + +// author : 2014-12-18 Amazon Registry Services, Inc. +author + +// auto : 2014-11-13 Cars Registry Limited +auto + +// autos : 2014-01-09 DERAutos, LLC +autos + +// avianca : 2015-01-08 Aerovias del Continente Americano S.A. Avianca +avianca + +// aws : 2015-06-25 Amazon Registry Services, Inc. +aws + +// axa : 2013-12-19 AXA SA +axa + +// azure : 2014-12-18 Microsoft Corporation +azure + +// baby : 2015-04-09 Johnson & Johnson Services, Inc. +baby + +// baidu : 2015-01-08 Baidu, Inc. +baidu + +// banamex : 2015-07-30 Citigroup Inc. +banamex + +// bananarepublic : 2015-07-31 The Gap, Inc. +bananarepublic + +// band : 2014-06-12 United TLD Holdco Ltd. +band + +// bank : 2014-09-25 fTLD Registry Services LLC +bank + +// bar : 2013-12-12 Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable +bar + +// barcelona : 2014-07-24 Municipi de Barcelona +barcelona + +// barclaycard : 2014-11-20 Barclays Bank PLC +barclaycard + +// barclays : 2014-11-20 Barclays Bank PLC +barclays + +// barefoot : 2015-06-11 Gallo Vineyards, Inc. +barefoot + +// bargains : 2013-11-14 Binky Moon, LLC +bargains + +// baseball : 2015-10-29 MLB Advanced Media DH, LLC +baseball + +// basketball : 2015-08-20 Fédération Internationale de Basketball (FIBA) +basketball + +// bauhaus : 2014-04-17 Werkhaus GmbH +bauhaus + +// bayern : 2014-01-23 Bayern Connect GmbH +bayern + +// bbc : 2014-12-18 British Broadcasting Corporation +bbc + +// bbt : 2015-07-23 BB&T Corporation +bbt + +// bbva : 2014-10-02 BANCO BILBAO VIZCAYA ARGENTARIA, S.A. +bbva + +// bcg : 2015-04-02 The Boston Consulting Group, Inc. +bcg + +// bcn : 2014-07-24 Municipi de Barcelona +bcn + +// beats : 2015-05-14 Beats Electronics, LLC +beats + +// beauty : 2015-12-03 L'Oréal +beauty + +// beer : 2014-01-09 Minds + Machines Group Limited +beer + +// bentley : 2014-12-18 Bentley Motors Limited +bentley + +// berlin : 2013-10-31 dotBERLIN GmbH & Co. KG +berlin + +// best : 2013-12-19 BestTLD Pty Ltd +best + +// bestbuy : 2015-07-31 BBY Solutions, Inc. +bestbuy + +// bet : 2015-05-07 Afilias plc +bet + +// bharti : 2014-01-09 Bharti Enterprises (Holding) Private Limited +bharti + +// bible : 2014-06-19 American Bible Society +bible + +// bid : 2013-12-19 dot Bid Limited +bid + +// bike : 2013-08-27 Binky Moon, LLC +bike + +// bing : 2014-12-18 Microsoft Corporation +bing + +// bingo : 2014-12-04 Binky Moon, LLC +bingo + +// bio : 2014-03-06 Afilias plc +bio + +// black : 2014-01-16 Afilias plc +black + +// blackfriday : 2014-01-16 Uniregistry, Corp. +blackfriday + +// blockbuster : 2015-07-30 Dish DBS Corporation +blockbuster + +// blog : 2015-05-14 Knock Knock WHOIS There, LLC +blog + +// bloomberg : 2014-07-17 Bloomberg IP Holdings LLC +bloomberg + +// blue : 2013-11-07 Afilias plc +blue + +// bms : 2014-10-30 Bristol-Myers Squibb Company +bms + +// bmw : 2014-01-09 Bayerische Motoren Werke Aktiengesellschaft +bmw + +// bnl : 2014-07-24 Banca Nazionale del Lavoro +bnl + +// bnpparibas : 2014-05-29 BNP Paribas +bnpparibas + +// boats : 2014-12-04 DERBoats, LLC +boats + +// boehringer : 2015-07-09 Boehringer Ingelheim International GmbH +boehringer + +// bofa : 2015-07-31 Bank of America Corporation +bofa + +// bom : 2014-10-16 Núcleo de Informação e Coordenação do Ponto BR - NIC.br +bom + +// bond : 2014-06-05 Bond University Limited +bond + +// boo : 2014-01-30 Charleston Road Registry Inc. +boo + +// book : 2015-08-27 Amazon Registry Services, Inc. +book + +// booking : 2015-07-16 Booking.com B.V. +booking + +// bosch : 2015-06-18 Robert Bosch GMBH +bosch + +// bostik : 2015-05-28 Bostik SA +bostik + +// boston : 2015-12-10 Boston TLD Management, LLC +boston + +// bot : 2014-12-18 Amazon Registry Services, Inc. +bot + +// boutique : 2013-11-14 Binky Moon, LLC +boutique + +// box : 2015-11-12 NS1 Limited +box + +// bradesco : 2014-12-18 Banco Bradesco S.A. +bradesco + +// bridgestone : 2014-12-18 Bridgestone Corporation +bridgestone + +// broadway : 2014-12-22 Celebrate Broadway, Inc. +broadway + +// broker : 2014-12-11 Dotbroker Registry Limited +broker + +// brother : 2015-01-29 Brother Industries, Ltd. +brother + +// brussels : 2014-02-06 DNS.be vzw +brussels + +// budapest : 2013-11-21 Minds + Machines Group Limited +budapest + +// bugatti : 2015-07-23 Bugatti International SA +bugatti + +// build : 2013-11-07 Plan Bee LLC +build + +// builders : 2013-11-07 Binky Moon, LLC +builders + +// business : 2013-11-07 Binky Moon, LLC +business + +// buy : 2014-12-18 Amazon Registry Services, Inc. +buy + +// buzz : 2013-10-02 DOTSTRATEGY CO. +buzz + +// bzh : 2014-02-27 Association www.bzh +bzh + +// cab : 2013-10-24 Binky Moon, LLC +cab + +// cafe : 2015-02-11 Binky Moon, LLC +cafe + +// cal : 2014-07-24 Charleston Road Registry Inc. +cal + +// call : 2014-12-18 Amazon Registry Services, Inc. +call + +// calvinklein : 2015-07-30 PVH gTLD Holdings LLC +calvinklein + +// cam : 2016-04-21 AC Webconnecting Holding B.V. +cam + +// camera : 2013-08-27 Binky Moon, LLC +camera + +// camp : 2013-11-07 Binky Moon, LLC +camp + +// cancerresearch : 2014-05-15 Australian Cancer Research Foundation +cancerresearch + +// canon : 2014-09-12 Canon Inc. +canon + +// capetown : 2014-03-24 ZA Central Registry NPC trading as ZA Central Registry +capetown + +// capital : 2014-03-06 Binky Moon, LLC +capital + +// capitalone : 2015-08-06 Capital One Financial Corporation +capitalone + +// car : 2015-01-22 Cars Registry Limited +car + +// caravan : 2013-12-12 Caravan International, Inc. +caravan + +// cards : 2013-12-05 Binky Moon, LLC +cards + +// care : 2014-03-06 Binky Moon, LLC +care + +// career : 2013-10-09 dotCareer LLC +career + +// careers : 2013-10-02 Binky Moon, LLC +careers + +// cars : 2014-11-13 Cars Registry Limited +cars + +// cartier : 2014-06-23 Richemont DNS Inc. +cartier + +// casa : 2013-11-21 Minds + Machines Group Limited +casa + +// case : 2015-09-03 CNH Industrial N.V. +case + +// caseih : 2015-09-03 CNH Industrial N.V. +caseih + +// cash : 2014-03-06 Binky Moon, LLC +cash + +// casino : 2014-12-18 Binky Moon, LLC +casino + +// catering : 2013-12-05 Binky Moon, LLC +catering + +// catholic : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) +catholic + +// cba : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA +cba + +// cbn : 2014-08-22 The Christian Broadcasting Network, Inc. +cbn + +// cbre : 2015-07-02 CBRE, Inc. +cbre + +// cbs : 2015-08-06 CBS Domains Inc. +cbs + +// ceb : 2015-04-09 The Corporate Executive Board Company +ceb + +// center : 2013-11-07 Binky Moon, LLC +center + +// ceo : 2013-11-07 CEOTLD Pty Ltd +ceo + +// cern : 2014-06-05 European Organization for Nuclear Research ("CERN") +cern + +// cfa : 2014-08-28 CFA Institute +cfa + +// cfd : 2014-12-11 DotCFD Registry Limited +cfd + +// chanel : 2015-04-09 Chanel International B.V. +chanel + +// channel : 2014-05-08 Charleston Road Registry Inc. +channel + +// charity : 2018-04-11 Corn Lake, LLC +charity + +// chase : 2015-04-30 JPMorgan Chase Bank, National Association +chase + +// chat : 2014-12-04 Binky Moon, LLC +chat + +// cheap : 2013-11-14 Binky Moon, LLC +cheap + +// chintai : 2015-06-11 CHINTAI Corporation +chintai + +// christmas : 2013-11-21 Uniregistry, Corp. +christmas + +// chrome : 2014-07-24 Charleston Road Registry Inc. +chrome + +// chrysler : 2015-07-30 FCA US LLC. +chrysler + +// church : 2014-02-06 Binky Moon, LLC +church + +// cipriani : 2015-02-19 Hotel Cipriani Srl +cipriani + +// circle : 2014-12-18 Amazon Registry Services, Inc. +circle + +// cisco : 2014-12-22 Cisco Technology, Inc. +cisco + +// citadel : 2015-07-23 Citadel Domain LLC +citadel + +// citi : 2015-07-30 Citigroup Inc. +citi + +// citic : 2014-01-09 CITIC Group Corporation +citic + +// city : 2014-05-29 Binky Moon, LLC +city + +// cityeats : 2014-12-11 Lifestyle Domain Holdings, Inc. +cityeats + +// claims : 2014-03-20 Binky Moon, LLC +claims + +// cleaning : 2013-12-05 Binky Moon, LLC +cleaning + +// click : 2014-06-05 Uniregistry, Corp. +click + +// clinic : 2014-03-20 Binky Moon, LLC +clinic + +// clinique : 2015-10-01 The Estée Lauder Companies Inc. +clinique + +// clothing : 2013-08-27 Binky Moon, LLC +clothing + +// cloud : 2015-04-16 Aruba PEC S.p.A. +cloud + +// club : 2013-11-08 .CLUB DOMAINS, LLC +club + +// clubmed : 2015-06-25 Club Méditerranée S.A. +clubmed + +// coach : 2014-10-09 Binky Moon, LLC +coach + +// codes : 2013-10-31 Binky Moon, LLC +codes + +// coffee : 2013-10-17 Binky Moon, LLC +coffee + +// college : 2014-01-16 XYZ.COM LLC +college + +// cologne : 2014-02-05 punkt.wien GmbH +cologne + +// comcast : 2015-07-23 Comcast IP Holdings I, LLC +comcast + +// commbank : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA +commbank + +// community : 2013-12-05 Binky Moon, LLC +community + +// company : 2013-11-07 Binky Moon, LLC +company + +// compare : 2015-10-08 iSelect Ltd +compare + +// computer : 2013-10-24 Binky Moon, LLC +computer + +// comsec : 2015-01-08 VeriSign, Inc. +comsec + +// condos : 2013-12-05 Binky Moon, LLC +condos + +// construction : 2013-09-16 Binky Moon, LLC +construction + +// consulting : 2013-12-05 United TLD Holdco Ltd. +consulting + +// contact : 2015-01-08 Top Level Spectrum, Inc. +contact + +// contractors : 2013-09-10 Binky Moon, LLC +contractors + +// cooking : 2013-11-21 Minds + Machines Group Limited +cooking + +// cookingchannel : 2015-07-02 Lifestyle Domain Holdings, Inc. +cookingchannel + +// cool : 2013-11-14 Binky Moon, LLC +cool + +// corsica : 2014-09-25 Collectivité de Corse +corsica + +// country : 2013-12-19 DotCountry LLC +country + +// coupon : 2015-02-26 Amazon Registry Services, Inc. +coupon + +// coupons : 2015-03-26 Binky Moon, LLC +coupons + +// courses : 2014-12-04 OPEN UNIVERSITIES AUSTRALIA PTY LTD +courses + +// credit : 2014-03-20 Binky Moon, LLC +credit + +// creditcard : 2014-03-20 Binky Moon, LLC +creditcard + +// creditunion : 2015-01-22 CUNA Performance Resources, LLC +creditunion + +// cricket : 2014-10-09 dot Cricket Limited +cricket + +// crown : 2014-10-24 Crown Equipment Corporation +crown + +// crs : 2014-04-03 Federated Co-operatives Limited +crs + +// cruise : 2015-12-10 Viking River Cruises (Bermuda) Ltd. +cruise + +// cruises : 2013-12-05 Binky Moon, LLC +cruises + +// csc : 2014-09-25 Alliance-One Services, Inc. +csc + +// cuisinella : 2014-04-03 SALM S.A.S. +cuisinella + +// cymru : 2014-05-08 Nominet UK +cymru + +// cyou : 2015-01-22 Beijing Gamease Age Digital Technology Co., Ltd. +cyou + +// dabur : 2014-02-06 Dabur India Limited +dabur + +// dad : 2014-01-23 Charleston Road Registry Inc. +dad + +// dance : 2013-10-24 United TLD Holdco Ltd. +dance + +// data : 2016-06-02 Dish DBS Corporation +data + +// date : 2014-11-20 dot Date Limited +date + +// dating : 2013-12-05 Binky Moon, LLC +dating + +// datsun : 2014-03-27 NISSAN MOTOR CO., LTD. +datsun + +// day : 2014-01-30 Charleston Road Registry Inc. +day + +// dclk : 2014-11-20 Charleston Road Registry Inc. +dclk + +// dds : 2015-05-07 Minds + Machines Group Limited +dds + +// deal : 2015-06-25 Amazon Registry Services, Inc. +deal + +// dealer : 2014-12-22 Dealer Dot Com, Inc. +dealer + +// deals : 2014-05-22 Binky Moon, LLC +deals + +// degree : 2014-03-06 United TLD Holdco Ltd. +degree + +// delivery : 2014-09-11 Binky Moon, LLC +delivery + +// dell : 2014-10-24 Dell Inc. +dell + +// deloitte : 2015-07-31 Deloitte Touche Tohmatsu +deloitte + +// delta : 2015-02-19 Delta Air Lines, Inc. +delta + +// democrat : 2013-10-24 United TLD Holdco Ltd. +democrat + +// dental : 2014-03-20 Binky Moon, LLC +dental + +// dentist : 2014-03-20 United TLD Holdco Ltd. +dentist + +// desi : 2013-11-14 Desi Networks LLC +desi + +// design : 2014-11-07 Top Level Design, LLC +design + +// dev : 2014-10-16 Charleston Road Registry Inc. +dev + +// dhl : 2015-07-23 Deutsche Post AG +dhl + +// diamonds : 2013-09-22 Binky Moon, LLC +diamonds + +// diet : 2014-06-26 Uniregistry, Corp. +diet + +// digital : 2014-03-06 Binky Moon, LLC +digital + +// direct : 2014-04-10 Binky Moon, LLC +direct + +// directory : 2013-09-20 Binky Moon, LLC +directory + +// discount : 2014-03-06 Binky Moon, LLC +discount + +// discover : 2015-07-23 Discover Financial Services +discover + +// dish : 2015-07-30 Dish DBS Corporation +dish + +// diy : 2015-11-05 Lifestyle Domain Holdings, Inc. +diy + +// dnp : 2013-12-13 Dai Nippon Printing Co., Ltd. +dnp + +// docs : 2014-10-16 Charleston Road Registry Inc. +docs + +// doctor : 2016-06-02 Binky Moon, LLC +doctor + +// dodge : 2015-07-30 FCA US LLC. +dodge + +// dog : 2014-12-04 Binky Moon, LLC +dog + +// doha : 2014-09-18 Communications Regulatory Authority (CRA) +doha + +// domains : 2013-10-17 Binky Moon, LLC +domains + +// dot : 2015-05-21 Dish DBS Corporation +dot + +// download : 2014-11-20 dot Support Limited +download + +// drive : 2015-03-05 Charleston Road Registry Inc. +drive + +// dtv : 2015-06-04 Dish DBS Corporation +dtv + +// dubai : 2015-01-01 Dubai Smart Government Department +dubai + +// duck : 2015-07-23 Johnson Shareholdings, Inc. +duck + +// dunlop : 2015-07-02 The Goodyear Tire & Rubber Company +dunlop + +// duns : 2015-08-06 The Dun & Bradstreet Corporation +duns + +// dupont : 2015-06-25 E. I. du Pont de Nemours and Company +dupont + +// durban : 2014-03-24 ZA Central Registry NPC trading as ZA Central Registry +durban + +// dvag : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG +dvag + +// dvr : 2016-05-26 Hughes Satellite Systems Corporation +dvr + +// earth : 2014-12-04 Interlink Co., Ltd. +earth + +// eat : 2014-01-23 Charleston Road Registry Inc. +eat + +// eco : 2016-07-08 Big Room Inc. +eco + +// edeka : 2014-12-18 EDEKA Verband kaufmännischer Genossenschaften e.V. +edeka + +// education : 2013-11-07 Binky Moon, LLC +education + +// email : 2013-10-31 Binky Moon, LLC +email + +// emerck : 2014-04-03 Merck KGaA +emerck + +// energy : 2014-09-11 Binky Moon, LLC +energy + +// engineer : 2014-03-06 United TLD Holdco Ltd. +engineer + +// engineering : 2014-03-06 Binky Moon, LLC +engineering + +// enterprises : 2013-09-20 Binky Moon, LLC +enterprises + +// epson : 2014-12-04 Seiko Epson Corporation +epson + +// equipment : 2013-08-27 Binky Moon, LLC +equipment + +// ericsson : 2015-07-09 Telefonaktiebolaget L M Ericsson +ericsson + +// erni : 2014-04-03 ERNI Group Holding AG +erni + +// esq : 2014-05-08 Charleston Road Registry Inc. +esq + +// estate : 2013-08-27 Binky Moon, LLC +estate + +// esurance : 2015-07-23 Esurance Insurance Company +esurance + +// etisalat : 2015-09-03 Emirates Telecommunications Corporation (trading as Etisalat) +etisalat + +// eurovision : 2014-04-24 European Broadcasting Union (EBU) +eurovision + +// eus : 2013-12-12 Puntueus Fundazioa +eus + +// events : 2013-12-05 Binky Moon, LLC +events + +// everbank : 2014-05-15 EverBank +everbank + +// exchange : 2014-03-06 Binky Moon, LLC +exchange + +// expert : 2013-11-21 Binky Moon, LLC +expert + +// exposed : 2013-12-05 Binky Moon, LLC +exposed + +// express : 2015-02-11 Binky Moon, LLC +express + +// extraspace : 2015-05-14 Extra Space Storage LLC +extraspace + +// fage : 2014-12-18 Fage International S.A. +fage + +// fail : 2014-03-06 Binky Moon, LLC +fail + +// fairwinds : 2014-11-13 FairWinds Partners, LLC +fairwinds + +// faith : 2014-11-20 dot Faith Limited +faith + +// family : 2015-04-02 United TLD Holdco Ltd. +family + +// fan : 2014-03-06 Asiamix Digital Limited +fan + +// fans : 2014-11-07 Asiamix Digital Limited +fans + +// farm : 2013-11-07 Binky Moon, LLC +farm + +// farmers : 2015-07-09 Farmers Insurance Exchange +farmers + +// fashion : 2014-07-03 Minds + Machines Group Limited +fashion + +// fast : 2014-12-18 Amazon Registry Services, Inc. +fast + +// fedex : 2015-08-06 Federal Express Corporation +fedex + +// feedback : 2013-12-19 Top Level Spectrum, Inc. +feedback + +// ferrari : 2015-07-31 Fiat Chrysler Automobiles N.V. +ferrari + +// ferrero : 2014-12-18 Ferrero Trading Lux S.A. +ferrero + +// fiat : 2015-07-31 Fiat Chrysler Automobiles N.V. +fiat + +// fidelity : 2015-07-30 Fidelity Brokerage Services LLC +fidelity + +// fido : 2015-08-06 Rogers Communications Canada Inc. +fido + +// film : 2015-01-08 Motion Picture Domain Registry Pty Ltd +film + +// final : 2014-10-16 Núcleo de Informação e Coordenação do Ponto BR - NIC.br +final + +// finance : 2014-03-20 Binky Moon, LLC +finance + +// financial : 2014-03-06 Binky Moon, LLC +financial + +// fire : 2015-06-25 Amazon Registry Services, Inc. +fire + +// firestone : 2014-12-18 Bridgestone Licensing Services, Inc +firestone + +// firmdale : 2014-03-27 Firmdale Holdings Limited +firmdale + +// fish : 2013-12-12 Binky Moon, LLC +fish + +// fishing : 2013-11-21 Minds + Machines Group Limited +fishing + +// fit : 2014-11-07 Minds + Machines Group Limited +fit + +// fitness : 2014-03-06 Binky Moon, LLC +fitness + +// flickr : 2015-04-02 Yahoo! Domain Services Inc. +flickr + +// flights : 2013-12-05 Binky Moon, LLC +flights + +// flir : 2015-07-23 FLIR Systems, Inc. +flir + +// florist : 2013-11-07 Binky Moon, LLC +florist + +// flowers : 2014-10-09 Uniregistry, Corp. +flowers + +// fly : 2014-05-08 Charleston Road Registry Inc. +fly + +// foo : 2014-01-23 Charleston Road Registry Inc. +foo + +// food : 2016-04-21 Lifestyle Domain Holdings, Inc. +food + +// foodnetwork : 2015-07-02 Lifestyle Domain Holdings, Inc. +foodnetwork + +// football : 2014-12-18 Binky Moon, LLC +football + +// ford : 2014-11-13 Ford Motor Company +ford + +// forex : 2014-12-11 Dotforex Registry Limited +forex + +// forsale : 2014-05-22 United TLD Holdco Ltd. +forsale + +// forum : 2015-04-02 Fegistry, LLC +forum + +// foundation : 2013-12-05 Binky Moon, LLC +foundation + +// fox : 2015-09-11 FOX Registry, LLC +fox + +// free : 2015-12-10 Amazon Registry Services, Inc. +free + +// fresenius : 2015-07-30 Fresenius Immobilien-Verwaltungs-GmbH +fresenius + +// frl : 2014-05-15 FRLregistry B.V. +frl + +// frogans : 2013-12-19 OP3FT +frogans + +// frontdoor : 2015-07-02 Lifestyle Domain Holdings, Inc. +frontdoor + +// frontier : 2015-02-05 Frontier Communications Corporation +frontier + +// ftr : 2015-07-16 Frontier Communications Corporation +ftr + +// fujitsu : 2015-07-30 Fujitsu Limited +fujitsu + +// fujixerox : 2015-07-23 Xerox DNHC LLC +fujixerox + +// fun : 2016-01-14 DotSpace Inc. +fun + +// fund : 2014-03-20 Binky Moon, LLC +fund + +// furniture : 2014-03-20 Binky Moon, LLC +furniture + +// futbol : 2013-09-20 United TLD Holdco Ltd. +futbol + +// fyi : 2015-04-02 Binky Moon, LLC +fyi + +// gal : 2013-11-07 Asociación puntoGAL +gal + +// gallery : 2013-09-13 Binky Moon, LLC +gallery + +// gallo : 2015-06-11 Gallo Vineyards, Inc. +gallo + +// gallup : 2015-02-19 Gallup, Inc. +gallup + +// game : 2015-05-28 Uniregistry, Corp. +game + +// games : 2015-05-28 United TLD Holdco Ltd. +games + +// gap : 2015-07-31 The Gap, Inc. +gap + +// garden : 2014-06-26 Minds + Machines Group Limited +garden + +// gbiz : 2014-07-17 Charleston Road Registry Inc. +gbiz + +// gdn : 2014-07-31 Joint Stock Company "Navigation-information systems" +gdn + +// gea : 2014-12-04 GEA Group Aktiengesellschaft +gea + +// gent : 2014-01-23 COMBELL NV +gent + +// genting : 2015-03-12 Resorts World Inc Pte. Ltd. +genting + +// george : 2015-07-31 Wal-Mart Stores, Inc. +george + +// ggee : 2014-01-09 GMO Internet, Inc. +ggee + +// gift : 2013-10-17 DotGift, LLC +gift + +// gifts : 2014-07-03 Binky Moon, LLC +gifts + +// gives : 2014-03-06 United TLD Holdco Ltd. +gives + +// giving : 2014-11-13 Giving Limited +giving + +// glade : 2015-07-23 Johnson Shareholdings, Inc. +glade + +// glass : 2013-11-07 Binky Moon, LLC +glass + +// gle : 2014-07-24 Charleston Road Registry Inc. +gle + +// global : 2014-04-17 Dot Global Domain Registry Limited +global + +// globo : 2013-12-19 Globo Comunicação e Participações S.A +globo + +// gmail : 2014-05-01 Charleston Road Registry Inc. +gmail + +// gmbh : 2016-01-29 Binky Moon, LLC +gmbh + +// gmo : 2014-01-09 GMO Internet Pte. Ltd. +gmo + +// gmx : 2014-04-24 1&1 Mail & Media GmbH +gmx + +// godaddy : 2015-07-23 Go Daddy East, LLC +godaddy + +// gold : 2015-01-22 Binky Moon, LLC +gold + +// goldpoint : 2014-11-20 YODOBASHI CAMERA CO.,LTD. +goldpoint + +// golf : 2014-12-18 Binky Moon, LLC +golf + +// goo : 2014-12-18 NTT Resonant Inc. +goo + +// goodyear : 2015-07-02 The Goodyear Tire & Rubber Company +goodyear + +// goog : 2014-11-20 Charleston Road Registry Inc. +goog + +// google : 2014-07-24 Charleston Road Registry Inc. +google + +// gop : 2014-01-16 Republican State Leadership Committee, Inc. +gop + +// got : 2014-12-18 Amazon Registry Services, Inc. +got + +// grainger : 2015-05-07 Grainger Registry Services, LLC +grainger + +// graphics : 2013-09-13 Binky Moon, LLC +graphics + +// gratis : 2014-03-20 Binky Moon, LLC +gratis + +// green : 2014-05-08 Afilias plc +green + +// gripe : 2014-03-06 Binky Moon, LLC +gripe + +// grocery : 2016-06-16 Wal-Mart Stores, Inc. +grocery + +// group : 2014-08-15 Binky Moon, LLC +group + +// guardian : 2015-07-30 The Guardian Life Insurance Company of America +guardian + +// gucci : 2014-11-13 Guccio Gucci S.p.a. +gucci + +// guge : 2014-08-28 Charleston Road Registry Inc. +guge + +// guide : 2013-09-13 Binky Moon, LLC +guide + +// guitars : 2013-11-14 Uniregistry, Corp. +guitars + +// guru : 2013-08-27 Binky Moon, LLC +guru + +// hair : 2015-12-03 L'Oréal +hair + +// hamburg : 2014-02-20 Hamburg Top-Level-Domain GmbH +hamburg + +// hangout : 2014-11-13 Charleston Road Registry Inc. +hangout + +// haus : 2013-12-05 United TLD Holdco Ltd. +haus + +// hbo : 2015-07-30 HBO Registry Services, Inc. +hbo + +// hdfc : 2015-07-30 HOUSING DEVELOPMENT FINANCE CORPORATION LIMITED +hdfc + +// hdfcbank : 2015-02-12 HDFC Bank Limited +hdfcbank + +// health : 2015-02-11 DotHealth, LLC +health + +// healthcare : 2014-06-12 Binky Moon, LLC +healthcare + +// help : 2014-06-26 Uniregistry, Corp. +help + +// helsinki : 2015-02-05 City of Helsinki +helsinki + +// here : 2014-02-06 Charleston Road Registry Inc. +here + +// hermes : 2014-07-10 HERMES INTERNATIONAL +hermes + +// hgtv : 2015-07-02 Lifestyle Domain Holdings, Inc. +hgtv + +// hiphop : 2014-03-06 Uniregistry, Corp. +hiphop + +// hisamitsu : 2015-07-16 Hisamitsu Pharmaceutical Co.,Inc. +hisamitsu + +// hitachi : 2014-10-31 Hitachi, Ltd. +hitachi + +// hiv : 2014-03-13 Uniregistry, Corp. +hiv + +// hkt : 2015-05-14 PCCW-HKT DataCom Services Limited +hkt + +// hockey : 2015-03-19 Binky Moon, LLC +hockey + +// holdings : 2013-08-27 Binky Moon, LLC +holdings + +// holiday : 2013-11-07 Binky Moon, LLC +holiday + +// homedepot : 2015-04-02 Home Depot Product Authority, LLC +homedepot + +// homegoods : 2015-07-16 The TJX Companies, Inc. +homegoods + +// homes : 2014-01-09 DERHomes, LLC +homes + +// homesense : 2015-07-16 The TJX Companies, Inc. +homesense + +// honda : 2014-12-18 Honda Motor Co., Ltd. +honda + +// honeywell : 2015-07-23 Honeywell GTLD LLC +honeywell + +// horse : 2013-11-21 Minds + Machines Group Limited +horse + +// hospital : 2016-10-20 Binky Moon, LLC +hospital + +// host : 2014-04-17 DotHost Inc. +host + +// hosting : 2014-05-29 Uniregistry, Corp. +hosting + +// hot : 2015-08-27 Amazon Registry Services, Inc. +hot + +// hoteles : 2015-03-05 Travel Reservations SRL +hoteles + +// hotels : 2016-04-07 Booking.com B.V. +hotels + +// hotmail : 2014-12-18 Microsoft Corporation +hotmail + +// house : 2013-11-07 Binky Moon, LLC +house + +// how : 2014-01-23 Charleston Road Registry Inc. +how + +// hsbc : 2014-10-24 HSBC Global Services (UK) Limited +hsbc + +// hughes : 2015-07-30 Hughes Satellite Systems Corporation +hughes + +// hyatt : 2015-07-30 Hyatt GTLD, L.L.C. +hyatt + +// hyundai : 2015-07-09 Hyundai Motor Company +hyundai + +// ibm : 2014-07-31 International Business Machines Corporation +ibm + +// icbc : 2015-02-19 Industrial and Commercial Bank of China Limited +icbc + +// ice : 2014-10-30 IntercontinentalExchange, Inc. +ice + +// icu : 2015-01-08 ShortDot SA +icu + +// ieee : 2015-07-23 IEEE Global LLC +ieee + +// ifm : 2014-01-30 ifm electronic gmbh +ifm + +// ikano : 2015-07-09 Ikano S.A. +ikano + +// imamat : 2015-08-06 Fondation Aga Khan (Aga Khan Foundation) +imamat + +// imdb : 2015-06-25 Amazon Registry Services, Inc. +imdb + +// immo : 2014-07-10 Binky Moon, LLC +immo + +// immobilien : 2013-11-07 United TLD Holdco Ltd. +immobilien + +// inc : 2018-03-10 GTLD Limited +inc + +// industries : 2013-12-05 Binky Moon, LLC +industries + +// infiniti : 2014-03-27 NISSAN MOTOR CO., LTD. +infiniti + +// ing : 2014-01-23 Charleston Road Registry Inc. +ing + +// ink : 2013-12-05 Top Level Design, LLC +ink + +// institute : 2013-11-07 Binky Moon, LLC +institute + +// insurance : 2015-02-19 fTLD Registry Services LLC +insurance + +// insure : 2014-03-20 Binky Moon, LLC +insure + +// intel : 2015-08-06 Intel Corporation +intel + +// international : 2013-11-07 Binky Moon, LLC +international + +// intuit : 2015-07-30 Intuit Administrative Services, Inc. +intuit + +// investments : 2014-03-20 Binky Moon, LLC +investments + +// ipiranga : 2014-08-28 Ipiranga Produtos de Petroleo S.A. +ipiranga + +// irish : 2014-08-07 Binky Moon, LLC +irish + +// iselect : 2015-02-11 iSelect Ltd +iselect + +// ismaili : 2015-08-06 Fondation Aga Khan (Aga Khan Foundation) +ismaili + +// ist : 2014-08-28 Istanbul Metropolitan Municipality +ist + +// istanbul : 2014-08-28 Istanbul Metropolitan Municipality +istanbul + +// itau : 2014-10-02 Itau Unibanco Holding S.A. +itau + +// itv : 2015-07-09 ITV Services Limited +itv + +// iveco : 2015-09-03 CNH Industrial N.V. +iveco + +// jaguar : 2014-11-13 Jaguar Land Rover Ltd +jaguar + +// java : 2014-06-19 Oracle Corporation +java + +// jcb : 2014-11-20 JCB Co., Ltd. +jcb + +// jcp : 2015-04-23 JCP Media, Inc. +jcp + +// jeep : 2015-07-30 FCA US LLC. +jeep + +// jetzt : 2014-01-09 Binky Moon, LLC +jetzt + +// jewelry : 2015-03-05 Binky Moon, LLC +jewelry + +// jio : 2015-04-02 Reliance Industries Limited +jio + +// jll : 2015-04-02 Jones Lang LaSalle Incorporated +jll + +// jmp : 2015-03-26 Matrix IP LLC +jmp + +// jnj : 2015-06-18 Johnson & Johnson Services, Inc. +jnj + +// joburg : 2014-03-24 ZA Central Registry NPC trading as ZA Central Registry +joburg + +// jot : 2014-12-18 Amazon Registry Services, Inc. +jot + +// joy : 2014-12-18 Amazon Registry Services, Inc. +joy + +// jpmorgan : 2015-04-30 JPMorgan Chase Bank, National Association +jpmorgan + +// jprs : 2014-09-18 Japan Registry Services Co., Ltd. +jprs + +// juegos : 2014-03-20 Uniregistry, Corp. +juegos + +// juniper : 2015-07-30 JUNIPER NETWORKS, INC. +juniper + +// kaufen : 2013-11-07 United TLD Holdco Ltd. +kaufen + +// kddi : 2014-09-12 KDDI CORPORATION +kddi + +// kerryhotels : 2015-04-30 Kerry Trading Co. Limited +kerryhotels + +// kerrylogistics : 2015-04-09 Kerry Trading Co. Limited +kerrylogistics + +// kerryproperties : 2015-04-09 Kerry Trading Co. Limited +kerryproperties + +// kfh : 2014-12-04 Kuwait Finance House +kfh + +// kia : 2015-07-09 KIA MOTORS CORPORATION +kia + +// kim : 2013-09-23 Afilias plc +kim + +// kinder : 2014-11-07 Ferrero Trading Lux S.A. +kinder + +// kindle : 2015-06-25 Amazon Registry Services, Inc. +kindle + +// kitchen : 2013-09-20 Binky Moon, LLC +kitchen + +// kiwi : 2013-09-20 DOT KIWI LIMITED +kiwi + +// koeln : 2014-01-09 punkt.wien GmbH +koeln + +// komatsu : 2015-01-08 Komatsu Ltd. +komatsu + +// kosher : 2015-08-20 Kosher Marketing Assets LLC +kosher + +// kpmg : 2015-04-23 KPMG International Cooperative (KPMG International Genossenschaft) +kpmg + +// kpn : 2015-01-08 Koninklijke KPN N.V. +kpn + +// krd : 2013-12-05 KRG Department of Information Technology +krd + +// kred : 2013-12-19 KredTLD Pty Ltd +kred + +// kuokgroup : 2015-04-09 Kerry Trading Co. Limited +kuokgroup + +// kyoto : 2014-11-07 Academic Institution: Kyoto Jyoho Gakuen +kyoto + +// lacaixa : 2014-01-09 Fundación Bancaria Caixa d’Estalvis i Pensions de Barcelona, “la Caixa” +lacaixa + +// ladbrokes : 2015-08-06 LADBROKES INTERNATIONAL PLC +ladbrokes + +// lamborghini : 2015-06-04 Automobili Lamborghini S.p.A. +lamborghini + +// lamer : 2015-10-01 The Estée Lauder Companies Inc. +lamer + +// lancaster : 2015-02-12 LANCASTER +lancaster + +// lancia : 2015-07-31 Fiat Chrysler Automobiles N.V. +lancia + +// lancome : 2015-07-23 L'Oréal +lancome + +// land : 2013-09-10 Binky Moon, LLC +land + +// landrover : 2014-11-13 Jaguar Land Rover Ltd +landrover + +// lanxess : 2015-07-30 LANXESS Corporation +lanxess + +// lasalle : 2015-04-02 Jones Lang LaSalle Incorporated +lasalle + +// lat : 2014-10-16 ECOM-LAC Federaciòn de Latinoamèrica y el Caribe para Internet y el Comercio Electrònico +lat + +// latino : 2015-07-30 Dish DBS Corporation +latino + +// latrobe : 2014-06-16 La Trobe University +latrobe + +// law : 2015-01-22 Minds + Machines Group Limited +law + +// lawyer : 2014-03-20 United TLD Holdco Ltd. +lawyer + +// lds : 2014-03-20 IRI Domain Management, LLC ("Applicant") +lds + +// lease : 2014-03-06 Binky Moon, LLC +lease + +// leclerc : 2014-08-07 A.C.D. LEC Association des Centres Distributeurs Edouard Leclerc +leclerc + +// lefrak : 2015-07-16 LeFrak Organization, Inc. +lefrak + +// legal : 2014-10-16 Binky Moon, LLC +legal + +// lego : 2015-07-16 LEGO Juris A/S +lego + +// lexus : 2015-04-23 TOYOTA MOTOR CORPORATION +lexus + +// lgbt : 2014-05-08 Afilias plc +lgbt + +// liaison : 2014-10-02 Liaison Technologies, Incorporated +liaison + +// lidl : 2014-09-18 Schwarz Domains und Services GmbH & Co. KG +lidl + +// life : 2014-02-06 Binky Moon, LLC +life + +// lifeinsurance : 2015-01-15 American Council of Life Insurers +lifeinsurance + +// lifestyle : 2014-12-11 Lifestyle Domain Holdings, Inc. +lifestyle + +// lighting : 2013-08-27 Binky Moon, LLC +lighting + +// like : 2014-12-18 Amazon Registry Services, Inc. +like + +// lilly : 2015-07-31 Eli Lilly and Company +lilly + +// limited : 2014-03-06 Binky Moon, LLC +limited + +// limo : 2013-10-17 Binky Moon, LLC +limo + +// lincoln : 2014-11-13 Ford Motor Company +lincoln + +// linde : 2014-12-04 Linde Aktiengesellschaft +linde + +// link : 2013-11-14 Uniregistry, Corp. +link + +// lipsy : 2015-06-25 Lipsy Ltd +lipsy + +// live : 2014-12-04 United TLD Holdco Ltd. +live + +// living : 2015-07-30 Lifestyle Domain Holdings, Inc. +living + +// lixil : 2015-03-19 LIXIL Group Corporation +lixil + +// llc : 2017-12-14 Afilias plc +llc + +// loan : 2014-11-20 dot Loan Limited +loan + +// loans : 2014-03-20 Binky Moon, LLC +loans + +// locker : 2015-06-04 Dish DBS Corporation +locker + +// locus : 2015-06-25 Locus Analytics LLC +locus + +// loft : 2015-07-30 Annco, Inc. +loft + +// lol : 2015-01-30 Uniregistry, Corp. +lol + +// london : 2013-11-14 Dot London Domains Limited +london + +// lotte : 2014-11-07 Lotte Holdings Co., Ltd. +lotte + +// lotto : 2014-04-10 Afilias plc +lotto + +// love : 2014-12-22 Merchant Law Group LLP +love + +// lpl : 2015-07-30 LPL Holdings, Inc. +lpl + +// lplfinancial : 2015-07-30 LPL Holdings, Inc. +lplfinancial + +// ltd : 2014-09-25 Binky Moon, LLC +ltd + +// ltda : 2014-04-17 InterNetX, Corp +ltda + +// lundbeck : 2015-08-06 H. Lundbeck A/S +lundbeck + +// lupin : 2014-11-07 LUPIN LIMITED +lupin + +// luxe : 2014-01-09 Minds + Machines Group Limited +luxe + +// luxury : 2013-10-17 Luxury Partners, LLC +luxury + +// macys : 2015-07-31 Macys, Inc. +macys + +// madrid : 2014-05-01 Comunidad de Madrid +madrid + +// maif : 2014-10-02 Mutuelle Assurance Instituteur France (MAIF) +maif + +// maison : 2013-12-05 Binky Moon, LLC +maison + +// makeup : 2015-01-15 L'Oréal +makeup + +// man : 2014-12-04 MAN SE +man + +// management : 2013-11-07 Binky Moon, LLC +management + +// mango : 2013-10-24 PUNTO FA S.L. +mango + +// map : 2016-06-09 Charleston Road Registry Inc. +map + +// market : 2014-03-06 United TLD Holdco Ltd. +market + +// marketing : 2013-11-07 Binky Moon, LLC +marketing + +// markets : 2014-12-11 Dotmarkets Registry Limited +markets + +// marriott : 2014-10-09 Marriott Worldwide Corporation +marriott + +// marshalls : 2015-07-16 The TJX Companies, Inc. +marshalls + +// maserati : 2015-07-31 Fiat Chrysler Automobiles N.V. +maserati + +// mattel : 2015-08-06 Mattel Sites, Inc. +mattel + +// mba : 2015-04-02 Binky Moon, LLC +mba + +// mckinsey : 2015-07-31 McKinsey Holdings, Inc. +mckinsey + +// med : 2015-08-06 Medistry LLC +med + +// media : 2014-03-06 Binky Moon, LLC +media + +// meet : 2014-01-16 Charleston Road Registry Inc. +meet + +// melbourne : 2014-05-29 The Crown in right of the State of Victoria, represented by its Department of State Development, Business and Innovation +melbourne + +// meme : 2014-01-30 Charleston Road Registry Inc. +meme + +// memorial : 2014-10-16 Dog Beach, LLC +memorial + +// men : 2015-02-26 Exclusive Registry Limited +men + +// menu : 2013-09-11 Wedding TLD2, LLC +menu + +// merckmsd : 2016-07-14 MSD Registry Holdings, Inc. +merckmsd + +// metlife : 2015-05-07 MetLife Services and Solutions, LLC +metlife + +// miami : 2013-12-19 Minds + Machines Group Limited +miami + +// microsoft : 2014-12-18 Microsoft Corporation +microsoft + +// mini : 2014-01-09 Bayerische Motoren Werke Aktiengesellschaft +mini + +// mint : 2015-07-30 Intuit Administrative Services, Inc. +mint + +// mit : 2015-07-02 Massachusetts Institute of Technology +mit + +// mitsubishi : 2015-07-23 Mitsubishi Corporation +mitsubishi + +// mlb : 2015-05-21 MLB Advanced Media DH, LLC +mlb + +// mls : 2015-04-23 The Canadian Real Estate Association +mls + +// mma : 2014-11-07 MMA IARD +mma + +// mobile : 2016-06-02 Dish DBS Corporation +mobile + +// mobily : 2014-12-18 GreenTech Consultancy Company W.L.L. +mobily + +// moda : 2013-11-07 United TLD Holdco Ltd. +moda + +// moe : 2013-11-13 Interlink Co., Ltd. +moe + +// moi : 2014-12-18 Amazon Registry Services, Inc. +moi + +// mom : 2015-04-16 Uniregistry, Corp. +mom + +// monash : 2013-09-30 Monash University +monash + +// money : 2014-10-16 Binky Moon, LLC +money + +// monster : 2015-09-11 Monster Worldwide, Inc. +monster + +// mopar : 2015-07-30 FCA US LLC. +mopar + +// mormon : 2013-12-05 IRI Domain Management, LLC ("Applicant") +mormon + +// mortgage : 2014-03-20 United TLD Holdco Ltd. +mortgage + +// moscow : 2013-12-19 Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) +moscow + +// moto : 2015-06-04 Motorola Trademark Holdings, LLC +moto + +// motorcycles : 2014-01-09 DERMotorcycles, LLC +motorcycles + +// mov : 2014-01-30 Charleston Road Registry Inc. +mov + +// movie : 2015-02-05 Binky Moon, LLC +movie + +// movistar : 2014-10-16 Telefónica S.A. +movistar + +// msd : 2015-07-23 MSD Registry Holdings, Inc. +msd + +// mtn : 2014-12-04 MTN Dubai Limited +mtn + +// mtr : 2015-03-12 MTR Corporation Limited +mtr + +// mutual : 2015-04-02 Northwestern Mutual MU TLD Registry, LLC +mutual + +// nab : 2015-08-20 National Australia Bank Limited +nab + +// nadex : 2014-12-11 Nadex Domains, Inc. +nadex + +// nagoya : 2013-10-24 GMO Registry, Inc. +nagoya + +// nationwide : 2015-07-23 Nationwide Mutual Insurance Company +nationwide + +// natura : 2015-03-12 NATURA COSMÉTICOS S.A. +natura + +// navy : 2014-03-06 United TLD Holdco Ltd. +navy + +// nba : 2015-07-31 NBA REGISTRY, LLC +nba + +// nec : 2015-01-08 NEC Corporation +nec + +// netbank : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA +netbank + +// netflix : 2015-06-18 Netflix, Inc. +netflix + +// network : 2013-11-14 Binky Moon, LLC +network + +// neustar : 2013-12-05 Registry Services, LLC +neustar + +// new : 2014-01-30 Charleston Road Registry Inc. +new + +// newholland : 2015-09-03 CNH Industrial N.V. +newholland + +// news : 2014-12-18 United TLD Holdco Ltd. +news + +// next : 2015-06-18 Next plc +next + +// nextdirect : 2015-06-18 Next plc +nextdirect + +// nexus : 2014-07-24 Charleston Road Registry Inc. +nexus + +// nfl : 2015-07-23 NFL Reg Ops LLC +nfl + +// ngo : 2014-03-06 Public Interest Registry +ngo + +// nhk : 2014-02-13 Japan Broadcasting Corporation (NHK) +nhk + +// nico : 2014-12-04 DWANGO Co., Ltd. +nico + +// nike : 2015-07-23 NIKE, Inc. +nike + +// nikon : 2015-05-21 NIKON CORPORATION +nikon + +// ninja : 2013-11-07 United TLD Holdco Ltd. +ninja + +// nissan : 2014-03-27 NISSAN MOTOR CO., LTD. +nissan + +// nissay : 2015-10-29 Nippon Life Insurance Company +nissay + +// nokia : 2015-01-08 Nokia Corporation +nokia + +// northwesternmutual : 2015-06-18 Northwestern Mutual Registry, LLC +northwesternmutual + +// norton : 2014-12-04 Symantec Corporation +norton + +// now : 2015-06-25 Amazon Registry Services, Inc. +now + +// nowruz : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. +nowruz + +// nowtv : 2015-05-14 Starbucks (HK) Limited +nowtv + +// nra : 2014-05-22 NRA Holdings Company, INC. +nra + +// nrw : 2013-11-21 Minds + Machines GmbH +nrw + +// ntt : 2014-10-31 NIPPON TELEGRAPH AND TELEPHONE CORPORATION +ntt + +// nyc : 2014-01-23 The City of New York by and through the New York City Department of Information Technology & Telecommunications +nyc + +// obi : 2014-09-25 OBI Group Holding SE & Co. KGaA +obi + +// observer : 2015-04-30 Top Level Spectrum, Inc. +observer + +// off : 2015-07-23 Johnson Shareholdings, Inc. +off + +// office : 2015-03-12 Microsoft Corporation +office + +// okinawa : 2013-12-05 BRregistry, Inc. +okinawa + +// olayan : 2015-05-14 Crescent Holding GmbH +olayan + +// olayangroup : 2015-05-14 Crescent Holding GmbH +olayangroup + +// oldnavy : 2015-07-31 The Gap, Inc. +oldnavy + +// ollo : 2015-06-04 Dish DBS Corporation +ollo + +// omega : 2015-01-08 The Swatch Group Ltd +omega + +// one : 2014-11-07 One.com A/S +one + +// ong : 2014-03-06 Public Interest Registry +ong + +// onl : 2013-09-16 I-Registry Ltd. +onl + +// online : 2015-01-15 DotOnline Inc. +online + +// onyourside : 2015-07-23 Nationwide Mutual Insurance Company +onyourside + +// ooo : 2014-01-09 INFIBEAM INCORPORATION LIMITED +ooo + +// open : 2015-07-31 American Express Travel Related Services Company, Inc. +open + +// oracle : 2014-06-19 Oracle Corporation +oracle + +// orange : 2015-03-12 Orange Brand Services Limited +orange + +// organic : 2014-03-27 Afilias plc +organic + +// origins : 2015-10-01 The Estée Lauder Companies Inc. +origins + +// osaka : 2014-09-04 Osaka Registry Co., Ltd. +osaka + +// otsuka : 2013-10-11 Otsuka Holdings Co., Ltd. +otsuka + +// ott : 2015-06-04 Dish DBS Corporation +ott + +// ovh : 2014-01-16 OVH SAS +ovh + +// page : 2014-12-04 Charleston Road Registry Inc. +page + +// panasonic : 2015-07-30 Panasonic Corporation +panasonic + +// paris : 2014-01-30 City of Paris +paris + +// pars : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. +pars + +// partners : 2013-12-05 Binky Moon, LLC +partners + +// parts : 2013-12-05 Binky Moon, LLC +parts + +// party : 2014-09-11 Blue Sky Registry Limited +party + +// passagens : 2015-03-05 Travel Reservations SRL +passagens + +// pay : 2015-08-27 Amazon Registry Services, Inc. +pay + +// pccw : 2015-05-14 PCCW Enterprises Limited +pccw + +// pet : 2015-05-07 Afilias plc +pet + +// pfizer : 2015-09-11 Pfizer Inc. +pfizer + +// pharmacy : 2014-06-19 National Association of Boards of Pharmacy +pharmacy + +// phd : 2016-07-28 Charleston Road Registry Inc. +phd + +// philips : 2014-11-07 Koninklijke Philips N.V. +philips + +// phone : 2016-06-02 Dish DBS Corporation +phone + +// photo : 2013-11-14 Uniregistry, Corp. +photo + +// photography : 2013-09-20 Binky Moon, LLC +photography + +// photos : 2013-10-17 Binky Moon, LLC +photos + +// physio : 2014-05-01 PhysBiz Pty Ltd +physio + +// piaget : 2014-10-16 Richemont DNS Inc. +piaget + +// pics : 2013-11-14 Uniregistry, Corp. +pics + +// pictet : 2014-06-26 Pictet Europe S.A. +pictet + +// pictures : 2014-03-06 Binky Moon, LLC +pictures + +// pid : 2015-01-08 Top Level Spectrum, Inc. +pid + +// pin : 2014-12-18 Amazon Registry Services, Inc. +pin + +// ping : 2015-06-11 Ping Registry Provider, Inc. +ping + +// pink : 2013-10-01 Afilias plc +pink + +// pioneer : 2015-07-16 Pioneer Corporation +pioneer + +// pizza : 2014-06-26 Binky Moon, LLC +pizza + +// place : 2014-04-24 Binky Moon, LLC +place + +// play : 2015-03-05 Charleston Road Registry Inc. +play + +// playstation : 2015-07-02 Sony Computer Entertainment Inc. +playstation + +// plumbing : 2013-09-10 Binky Moon, LLC +plumbing + +// plus : 2015-02-05 Binky Moon, LLC +plus + +// pnc : 2015-07-02 PNC Domain Co., LLC +pnc + +// pohl : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG +pohl + +// poker : 2014-07-03 Afilias plc +poker + +// politie : 2015-08-20 Politie Nederland +politie + +// porn : 2014-10-16 ICM Registry PN LLC +porn + +// pramerica : 2015-07-30 Prudential Financial, Inc. +pramerica + +// praxi : 2013-12-05 Praxi S.p.A. +praxi + +// press : 2014-04-03 DotPress Inc. +press + +// prime : 2015-06-25 Amazon Registry Services, Inc. +prime + +// prod : 2014-01-23 Charleston Road Registry Inc. +prod + +// productions : 2013-12-05 Binky Moon, LLC +productions + +// prof : 2014-07-24 Charleston Road Registry Inc. +prof + +// progressive : 2015-07-23 Progressive Casualty Insurance Company +progressive + +// promo : 2014-12-18 Afilias plc +promo + +// properties : 2013-12-05 Binky Moon, LLC +properties + +// property : 2014-05-22 Uniregistry, Corp. +property + +// protection : 2015-04-23 XYZ.COM LLC +protection + +// pru : 2015-07-30 Prudential Financial, Inc. +pru + +// prudential : 2015-07-30 Prudential Financial, Inc. +prudential + +// pub : 2013-12-12 United TLD Holdco Ltd. +pub + +// pwc : 2015-10-29 PricewaterhouseCoopers LLP +pwc + +// qpon : 2013-11-14 dotCOOL, Inc. +qpon + +// quebec : 2013-12-19 PointQuébec Inc +quebec + +// quest : 2015-03-26 Quest ION Limited +quest + +// qvc : 2015-07-30 QVC, Inc. +qvc + +// racing : 2014-12-04 Premier Registry Limited +racing + +// radio : 2016-07-21 European Broadcasting Union (EBU) +radio + +// raid : 2015-07-23 Johnson Shareholdings, Inc. +raid + +// read : 2014-12-18 Amazon Registry Services, Inc. +read + +// realestate : 2015-09-11 dotRealEstate LLC +realestate + +// realtor : 2014-05-29 Real Estate Domains LLC +realtor + +// realty : 2015-03-19 Fegistry, LLC +realty + +// recipes : 2013-10-17 Binky Moon, LLC +recipes + +// red : 2013-11-07 Afilias plc +red + +// redstone : 2014-10-31 Redstone Haute Couture Co., Ltd. +redstone + +// redumbrella : 2015-03-26 Travelers TLD, LLC +redumbrella + +// rehab : 2014-03-06 United TLD Holdco Ltd. +rehab + +// reise : 2014-03-13 Binky Moon, LLC +reise + +// reisen : 2014-03-06 Binky Moon, LLC +reisen + +// reit : 2014-09-04 National Association of Real Estate Investment Trusts, Inc. +reit + +// reliance : 2015-04-02 Reliance Industries Limited +reliance + +// ren : 2013-12-12 Beijing Qianxiang Wangjing Technology Development Co., Ltd. +ren + +// rent : 2014-12-04 XYZ.COM LLC +rent + +// rentals : 2013-12-05 Binky Moon, LLC +rentals + +// repair : 2013-11-07 Binky Moon, LLC +repair + +// report : 2013-12-05 Binky Moon, LLC +report + +// republican : 2014-03-20 United TLD Holdco Ltd. +republican + +// rest : 2013-12-19 Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable +rest + +// restaurant : 2014-07-03 Binky Moon, LLC +restaurant + +// review : 2014-11-20 dot Review Limited +review + +// reviews : 2013-09-13 United TLD Holdco Ltd. +reviews + +// rexroth : 2015-06-18 Robert Bosch GMBH +rexroth + +// rich : 2013-11-21 I-Registry Ltd. +rich + +// richardli : 2015-05-14 Pacific Century Asset Management (HK) Limited +richardli + +// ricoh : 2014-11-20 Ricoh Company, Ltd. +ricoh + +// rightathome : 2015-07-23 Johnson Shareholdings, Inc. +rightathome + +// ril : 2015-04-02 Reliance Industries Limited +ril + +// rio : 2014-02-27 Empresa Municipal de Informática SA - IPLANRIO +rio + +// rip : 2014-07-10 United TLD Holdco Ltd. +rip + +// rmit : 2015-11-19 Royal Melbourne Institute of Technology +rmit + +// rocher : 2014-12-18 Ferrero Trading Lux S.A. +rocher + +// rocks : 2013-11-14 United TLD Holdco Ltd. +rocks + +// rodeo : 2013-12-19 Minds + Machines Group Limited +rodeo + +// rogers : 2015-08-06 Rogers Communications Canada Inc. +rogers + +// room : 2014-12-18 Amazon Registry Services, Inc. +room + +// rsvp : 2014-05-08 Charleston Road Registry Inc. +rsvp + +// rugby : 2016-12-15 World Rugby Strategic Developments Limited +rugby + +// ruhr : 2013-10-02 regiodot GmbH & Co. KG +ruhr + +// run : 2015-03-19 Binky Moon, LLC +run + +// rwe : 2015-04-02 RWE AG +rwe + +// ryukyu : 2014-01-09 BRregistry, Inc. +ryukyu + +// saarland : 2013-12-12 dotSaarland GmbH +saarland + +// safe : 2014-12-18 Amazon Registry Services, Inc. +safe + +// safety : 2015-01-08 Safety Registry Services, LLC. +safety + +// sakura : 2014-12-18 SAKURA Internet Inc. +sakura + +// sale : 2014-10-16 United TLD Holdco Ltd. +sale + +// salon : 2014-12-11 Binky Moon, LLC +salon + +// samsclub : 2015-07-31 Wal-Mart Stores, Inc. +samsclub + +// samsung : 2014-04-03 SAMSUNG SDS CO., LTD +samsung + +// sandvik : 2014-11-13 Sandvik AB +sandvik + +// sandvikcoromant : 2014-11-07 Sandvik AB +sandvikcoromant + +// sanofi : 2014-10-09 Sanofi +sanofi + +// sap : 2014-03-27 SAP AG +sap + +// sarl : 2014-07-03 Binky Moon, LLC +sarl + +// sas : 2015-04-02 Research IP LLC +sas + +// save : 2015-06-25 Amazon Registry Services, Inc. +save + +// saxo : 2014-10-31 Saxo Bank A/S +saxo + +// sbi : 2015-03-12 STATE BANK OF INDIA +sbi + +// sbs : 2014-11-07 SPECIAL BROADCASTING SERVICE CORPORATION +sbs + +// sca : 2014-03-13 SVENSKA CELLULOSA AKTIEBOLAGET SCA (publ) +sca + +// scb : 2014-02-20 The Siam Commercial Bank Public Company Limited ("SCB") +scb + +// schaeffler : 2015-08-06 Schaeffler Technologies AG & Co. KG +schaeffler + +// schmidt : 2014-04-03 SALM S.A.S. +schmidt + +// scholarships : 2014-04-24 Scholarships.com, LLC +scholarships + +// school : 2014-12-18 Binky Moon, LLC +school + +// schule : 2014-03-06 Binky Moon, LLC +schule + +// schwarz : 2014-09-18 Schwarz Domains und Services GmbH & Co. KG +schwarz + +// science : 2014-09-11 dot Science Limited +science + +// scjohnson : 2015-07-23 Johnson Shareholdings, Inc. +scjohnson + +// scor : 2014-10-31 SCOR SE +scor + +// scot : 2014-01-23 Dot Scot Registry Limited +scot + +// search : 2016-06-09 Charleston Road Registry Inc. +search + +// seat : 2014-05-22 SEAT, S.A. (Sociedad Unipersonal) +seat + +// secure : 2015-08-27 Amazon Registry Services, Inc. +secure + +// security : 2015-05-14 XYZ.COM LLC +security + +// seek : 2014-12-04 Seek Limited +seek + +// select : 2015-10-08 iSelect Ltd +select + +// sener : 2014-10-24 Sener Ingeniería y Sistemas, S.A. +sener + +// services : 2014-02-27 Binky Moon, LLC +services + +// ses : 2015-07-23 SES +ses + +// seven : 2015-08-06 Seven West Media Ltd +seven + +// sew : 2014-07-17 SEW-EURODRIVE GmbH & Co KG +sew + +// sex : 2014-11-13 ICM Registry SX LLC +sex + +// sexy : 2013-09-11 Uniregistry, Corp. +sexy + +// sfr : 2015-08-13 Societe Francaise du Radiotelephone - SFR +sfr + +// shangrila : 2015-09-03 Shangri‐La International Hotel Management Limited +shangrila + +// sharp : 2014-05-01 Sharp Corporation +sharp + +// shaw : 2015-04-23 Shaw Cablesystems G.P. +shaw + +// shell : 2015-07-30 Shell Information Technology International Inc +shell + +// shia : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. +shia + +// shiksha : 2013-11-14 Afilias plc +shiksha + +// shoes : 2013-10-02 Binky Moon, LLC +shoes + +// shop : 2016-04-08 GMO Registry, Inc. +shop + +// shopping : 2016-03-31 Binky Moon, LLC +shopping + +// shouji : 2015-01-08 QIHOO 360 TECHNOLOGY CO. LTD. +shouji + +// show : 2015-03-05 Binky Moon, LLC +show + +// showtime : 2015-08-06 CBS Domains Inc. +showtime + +// shriram : 2014-01-23 Shriram Capital Ltd. +shriram + +// silk : 2015-06-25 Amazon Registry Services, Inc. +silk + +// sina : 2015-03-12 Sina Corporation +sina + +// singles : 2013-08-27 Binky Moon, LLC +singles + +// site : 2015-01-15 DotSite Inc. +site + +// ski : 2015-04-09 Afilias plc +ski + +// skin : 2015-01-15 L'Oréal +skin + +// sky : 2014-06-19 Sky International AG +sky + +// skype : 2014-12-18 Microsoft Corporation +skype + +// sling : 2015-07-30 Hughes Satellite Systems Corporation +sling + +// smart : 2015-07-09 Smart Communications, Inc. (SMART) +smart + +// smile : 2014-12-18 Amazon Registry Services, Inc. +smile + +// sncf : 2015-02-19 Société Nationale des Chemins de fer Francais S N C F +sncf + +// soccer : 2015-03-26 Binky Moon, LLC +soccer + +// social : 2013-11-07 United TLD Holdco Ltd. +social + +// softbank : 2015-07-02 SoftBank Corp. +softbank + +// software : 2014-03-20 United TLD Holdco Ltd. +software + +// sohu : 2013-12-19 Sohu.com Limited +sohu + +// solar : 2013-11-07 Binky Moon, LLC +solar + +// solutions : 2013-11-07 Binky Moon, LLC +solutions + +// song : 2015-02-26 Amazon Registry Services, Inc. +song + +// sony : 2015-01-08 Sony Corporation +sony + +// soy : 2014-01-23 Charleston Road Registry Inc. +soy + +// space : 2014-04-03 DotSpace Inc. +space + +// sport : 2017-11-16 Global Association of International Sports Federations (GAISF) +sport + +// spot : 2015-02-26 Amazon Registry Services, Inc. +spot + +// spreadbetting : 2014-12-11 Dotspreadbetting Registry Limited +spreadbetting + +// srl : 2015-05-07 InterNetX, Corp +srl + +// srt : 2015-07-30 FCA US LLC. +srt + +// stada : 2014-11-13 STADA Arzneimittel AG +stada + +// staples : 2015-07-30 Staples, Inc. +staples + +// star : 2015-01-08 Star India Private Limited +star + +// starhub : 2015-02-05 StarHub Ltd +starhub + +// statebank : 2015-03-12 STATE BANK OF INDIA +statebank + +// statefarm : 2015-07-30 State Farm Mutual Automobile Insurance Company +statefarm + +// stc : 2014-10-09 Saudi Telecom Company +stc + +// stcgroup : 2014-10-09 Saudi Telecom Company +stcgroup + +// stockholm : 2014-12-18 Stockholms kommun +stockholm + +// storage : 2014-12-22 XYZ.COM LLC +storage + +// store : 2015-04-09 DotStore Inc. +store + +// stream : 2016-01-08 dot Stream Limited +stream + +// studio : 2015-02-11 United TLD Holdco Ltd. +studio + +// study : 2014-12-11 OPEN UNIVERSITIES AUSTRALIA PTY LTD +study + +// style : 2014-12-04 Binky Moon, LLC +style + +// sucks : 2014-12-22 Vox Populi Registry Ltd. +sucks + +// supplies : 2013-12-19 Binky Moon, LLC +supplies + +// supply : 2013-12-19 Binky Moon, LLC +supply + +// support : 2013-10-24 Binky Moon, LLC +support + +// surf : 2014-01-09 Minds + Machines Group Limited +surf + +// surgery : 2014-03-20 Binky Moon, LLC +surgery + +// suzuki : 2014-02-20 SUZUKI MOTOR CORPORATION +suzuki + +// swatch : 2015-01-08 The Swatch Group Ltd +swatch + +// swiftcover : 2015-07-23 Swiftcover Insurance Services Limited +swiftcover + +// swiss : 2014-10-16 Swiss Confederation +swiss + +// sydney : 2014-09-18 State of New South Wales, Department of Premier and Cabinet +sydney + +// symantec : 2014-12-04 Symantec Corporation +symantec + +// systems : 2013-11-07 Binky Moon, LLC +systems + +// tab : 2014-12-04 Tabcorp Holdings Limited +tab + +// taipei : 2014-07-10 Taipei City Government +taipei + +// talk : 2015-04-09 Amazon Registry Services, Inc. +talk + +// taobao : 2015-01-15 Alibaba Group Holding Limited +taobao + +// target : 2015-07-31 Target Domain Holdings, LLC +target + +// tatamotors : 2015-03-12 Tata Motors Ltd +tatamotors + +// tatar : 2014-04-24 Limited Liability Company "Coordination Center of Regional Domain of Tatarstan Republic" +tatar + +// tattoo : 2013-08-30 Uniregistry, Corp. +tattoo + +// tax : 2014-03-20 Binky Moon, LLC +tax + +// taxi : 2015-03-19 Binky Moon, LLC +taxi + +// tci : 2014-09-12 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. +tci + +// tdk : 2015-06-11 TDK Corporation +tdk + +// team : 2015-03-05 Binky Moon, LLC +team + +// tech : 2015-01-30 Personals TLD Inc. +tech + +// technology : 2013-09-13 Binky Moon, LLC +technology + +// telefonica : 2014-10-16 Telefónica S.A. +telefonica + +// temasek : 2014-08-07 Temasek Holdings (Private) Limited +temasek + +// tennis : 2014-12-04 Binky Moon, LLC +tennis + +// teva : 2015-07-02 Teva Pharmaceutical Industries Limited +teva + +// thd : 2015-04-02 Home Depot Product Authority, LLC +thd + +// theater : 2015-03-19 Binky Moon, LLC +theater + +// theatre : 2015-05-07 XYZ.COM LLC +theatre + +// tiaa : 2015-07-23 Teachers Insurance and Annuity Association of America +tiaa + +// tickets : 2015-02-05 Accent Media Limited +tickets + +// tienda : 2013-11-14 Binky Moon, LLC +tienda + +// tiffany : 2015-01-30 Tiffany and Company +tiffany + +// tips : 2013-09-20 Binky Moon, LLC +tips + +// tires : 2014-11-07 Binky Moon, LLC +tires + +// tirol : 2014-04-24 punkt Tirol GmbH +tirol + +// tjmaxx : 2015-07-16 The TJX Companies, Inc. +tjmaxx + +// tjx : 2015-07-16 The TJX Companies, Inc. +tjx + +// tkmaxx : 2015-07-16 The TJX Companies, Inc. +tkmaxx + +// tmall : 2015-01-15 Alibaba Group Holding Limited +tmall + +// today : 2013-09-20 Binky Moon, LLC +today + +// tokyo : 2013-11-13 GMO Registry, Inc. +tokyo + +// tools : 2013-11-21 Binky Moon, LLC +tools + +// top : 2014-03-20 .TOP Registry +top + +// toray : 2014-12-18 Toray Industries, Inc. +toray + +// toshiba : 2014-04-10 TOSHIBA Corporation +toshiba + +// total : 2015-08-06 Total SA +total + +// tours : 2015-01-22 Binky Moon, LLC +tours + +// town : 2014-03-06 Binky Moon, LLC +town + +// toyota : 2015-04-23 TOYOTA MOTOR CORPORATION +toyota + +// toys : 2014-03-06 Binky Moon, LLC +toys + +// trade : 2014-01-23 Elite Registry Limited +trade + +// trading : 2014-12-11 Dottrading Registry Limited +trading + +// training : 2013-11-07 Binky Moon, LLC +training + +// travel : Dog Beach, LLC +travel + +// travelchannel : 2015-07-02 Lifestyle Domain Holdings, Inc. +travelchannel + +// travelers : 2015-03-26 Travelers TLD, LLC +travelers + +// travelersinsurance : 2015-03-26 Travelers TLD, LLC +travelersinsurance + +// trust : 2014-10-16 NCC Group Inc. +trust + +// trv : 2015-03-26 Travelers TLD, LLC +trv + +// tube : 2015-06-11 Latin American Telecom LLC +tube + +// tui : 2014-07-03 TUI AG +tui + +// tunes : 2015-02-26 Amazon Registry Services, Inc. +tunes + +// tushu : 2014-12-18 Amazon Registry Services, Inc. +tushu + +// tvs : 2015-02-19 T V SUNDRAM IYENGAR & SONS LIMITED +tvs + +// ubank : 2015-08-20 National Australia Bank Limited +ubank + +// ubs : 2014-12-11 UBS AG +ubs + +// uconnect : 2015-07-30 FCA US LLC. +uconnect + +// unicom : 2015-10-15 China United Network Communications Corporation Limited +unicom + +// university : 2014-03-06 Binky Moon, LLC +university + +// uno : 2013-09-11 Dot Latin LLC +uno + +// uol : 2014-05-01 UBN INTERNET LTDA. +uol + +// ups : 2015-06-25 UPS Market Driver, Inc. +ups + +// vacations : 2013-12-05 Binky Moon, LLC +vacations + +// vana : 2014-12-11 Lifestyle Domain Holdings, Inc. +vana + +// vanguard : 2015-09-03 The Vanguard Group, Inc. +vanguard + +// vegas : 2014-01-16 Dot Vegas, Inc. +vegas + +// ventures : 2013-08-27 Binky Moon, LLC +ventures + +// verisign : 2015-08-13 VeriSign, Inc. +verisign + +// versicherung : 2014-03-20 TLD-BOX Registrydienstleistungen GmbH +versicherung + +// vet : 2014-03-06 United TLD Holdco Ltd. +vet + +// viajes : 2013-10-17 Binky Moon, LLC +viajes + +// video : 2014-10-16 United TLD Holdco Ltd. +video + +// vig : 2015-05-14 VIENNA INSURANCE GROUP AG Wiener Versicherung Gruppe +vig + +// viking : 2015-04-02 Viking River Cruises (Bermuda) Ltd. +viking + +// villas : 2013-12-05 Binky Moon, LLC +villas + +// vin : 2015-06-18 Binky Moon, LLC +vin + +// vip : 2015-01-22 Minds + Machines Group Limited +vip + +// virgin : 2014-09-25 Virgin Enterprises Limited +virgin + +// visa : 2015-07-30 Visa Worldwide Pte. Limited +visa + +// vision : 2013-12-05 Binky Moon, LLC +vision + +// vistaprint : 2014-09-18 Vistaprint Limited +vistaprint + +// viva : 2014-11-07 Saudi Telecom Company +viva + +// vivo : 2015-07-31 Telefonica Brasil S.A. +vivo + +// vlaanderen : 2014-02-06 DNS.be vzw +vlaanderen + +// vodka : 2013-12-19 Minds + Machines Group Limited +vodka + +// volkswagen : 2015-05-14 Volkswagen Group of America Inc. +volkswagen + +// volvo : 2015-11-12 Volvo Holding Sverige Aktiebolag +volvo + +// vote : 2013-11-21 Monolith Registry LLC +vote + +// voting : 2013-11-13 Valuetainment Corp. +voting + +// voto : 2013-11-21 Monolith Registry LLC +voto + +// voyage : 2013-08-27 Binky Moon, LLC +voyage + +// vuelos : 2015-03-05 Travel Reservations SRL +vuelos + +// wales : 2014-05-08 Nominet UK +wales + +// walmart : 2015-07-31 Wal-Mart Stores, Inc. +walmart + +// walter : 2014-11-13 Sandvik AB +walter + +// wang : 2013-10-24 Zodiac Wang Limited +wang + +// wanggou : 2014-12-18 Amazon Registry Services, Inc. +wanggou + +// warman : 2015-06-18 Weir Group IP Limited +warman + +// watch : 2013-11-14 Binky Moon, LLC +watch + +// watches : 2014-12-22 Richemont DNS Inc. +watches + +// weather : 2015-01-08 International Business Machines Corporation +weather + +// weatherchannel : 2015-03-12 International Business Machines Corporation +weatherchannel + +// webcam : 2014-01-23 dot Webcam Limited +webcam + +// weber : 2015-06-04 Saint-Gobain Weber SA +weber + +// website : 2014-04-03 DotWebsite Inc. +website + +// wed : 2013-10-01 Atgron, Inc. +wed + +// wedding : 2014-04-24 Minds + Machines Group Limited +wedding + +// weibo : 2015-03-05 Sina Corporation +weibo + +// weir : 2015-01-29 Weir Group IP Limited +weir + +// whoswho : 2014-02-20 Who's Who Registry +whoswho + +// wien : 2013-10-28 punkt.wien GmbH +wien + +// wiki : 2013-11-07 Top Level Design, LLC +wiki + +// williamhill : 2014-03-13 William Hill Organization Limited +williamhill + +// win : 2014-11-20 First Registry Limited +win + +// windows : 2014-12-18 Microsoft Corporation +windows + +// wine : 2015-06-18 Binky Moon, LLC +wine + +// winners : 2015-07-16 The TJX Companies, Inc. +winners + +// wme : 2014-02-13 William Morris Endeavor Entertainment, LLC +wme + +// wolterskluwer : 2015-08-06 Wolters Kluwer N.V. +wolterskluwer + +// woodside : 2015-07-09 Woodside Petroleum Limited +woodside + +// work : 2013-12-19 Minds + Machines Group Limited +work + +// works : 2013-11-14 Binky Moon, LLC +works + +// world : 2014-06-12 Binky Moon, LLC +world + +// wow : 2015-10-08 Amazon Registry Services, Inc. +wow + +// wtc : 2013-12-19 World Trade Centers Association, Inc. +wtc + +// wtf : 2014-03-06 Binky Moon, LLC +wtf + +// xbox : 2014-12-18 Microsoft Corporation +xbox + +// xerox : 2014-10-24 Xerox DNHC LLC +xerox + +// xfinity : 2015-07-09 Comcast IP Holdings I, LLC +xfinity + +// xihuan : 2015-01-08 QIHOO 360 TECHNOLOGY CO. LTD. +xihuan + +// xin : 2014-12-11 Elegant Leader Limited +xin + +// xn--11b4c3d : 2015-01-15 VeriSign Sarl +कॉम + +// xn--1ck2e1b : 2015-02-26 Amazon Registry Services, Inc. +セール + +// xn--1qqw23a : 2014-01-09 Guangzhou YU Wei Information Technology Co., Ltd. +佛山 + +// xn--30rr7y : 2014-06-12 Excellent First Limited +慈善 + +// xn--3bst00m : 2013-09-13 Eagle Horizon Limited +集团 + +// xn--3ds443g : 2013-09-08 TLD REGISTRY LIMITED +在线 + +// xn--3oq18vl8pn36a : 2015-07-02 Volkswagen (China) Investment Co., Ltd. +大众汽车 + +// xn--3pxu8k : 2015-01-15 VeriSign Sarl +点看 + +// xn--42c2d9a : 2015-01-15 VeriSign Sarl +คอม + +// xn--45q11c : 2013-11-21 Zodiac Gemini Ltd +八卦 + +// xn--4gbrim : 2013-10-04 Suhub Electronic Establishment +موقع + +// xn--55qw42g : 2013-11-08 China Organizational Name Administration Center +公益 + +// xn--55qx5d : 2013-11-14 China Internet Network Information Center (CNNIC) +公司 + +// xn--5su34j936bgsg : 2015-09-03 Shangri‐La International Hotel Management Limited +香格里拉 + +// xn--5tzm5g : 2014-12-22 Global Website TLD Asia Limited +网站 + +// xn--6frz82g : 2013-09-23 Afilias plc +移动 + +// xn--6qq986b3xl : 2013-09-13 Tycoon Treasure Limited +我爱你 + +// xn--80adxhks : 2013-12-19 Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) +москва + +// xn--80aqecdr1a : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) +католик + +// xn--80asehdb : 2013-07-14 CORE Association +онлайн + +// xn--80aswg : 2013-07-14 CORE Association +сайт + +// xn--8y0a063a : 2015-03-26 China United Network Communications Corporation Limited +联通 + +// xn--9dbq2a : 2015-01-15 VeriSign Sarl +קום + +// xn--9et52u : 2014-06-12 RISE VICTORY LIMITED +时尚 + +// xn--9krt00a : 2015-03-12 Sina Corporation +微博 + +// xn--b4w605ferd : 2014-08-07 Temasek Holdings (Private) Limited +淡马锡 + +// xn--bck1b9a5dre4c : 2015-02-26 Amazon Registry Services, Inc. +ファッション + +// xn--c1avg : 2013-11-14 Public Interest Registry +орг + +// xn--c2br7g : 2015-01-15 VeriSign Sarl +नेट + +// xn--cck2b3b : 2015-02-26 Amazon Registry Services, Inc. +ストア + +// xn--cg4bki : 2013-09-27 SAMSUNG SDS CO., LTD +삼성 + +// xn--czr694b : 2014-01-16 Dot Trademark TLD Holding Company Limited +商标 + +// xn--czrs0t : 2013-12-19 Binky Moon, LLC +商店 + +// xn--czru2d : 2013-11-21 Zodiac Aquarius Limited +商城 + +// xn--d1acj3b : 2013-11-20 The Foundation for Network Initiatives “The Smart Internet” +дети + +// xn--eckvdtc9d : 2014-12-18 Amazon Registry Services, Inc. +ポイント + +// xn--efvy88h : 2014-08-22 Guangzhou YU Wei Information Technology Co., Ltd. +新闻 + +// xn--estv75g : 2015-02-19 Industrial and Commercial Bank of China Limited +工行 + +// xn--fct429k : 2015-04-09 Amazon Registry Services, Inc. +家電 + +// xn--fhbei : 2015-01-15 VeriSign Sarl +كوم + +// xn--fiq228c5hs : 2013-09-08 TLD REGISTRY LIMITED +中文网 + +// xn--fiq64b : 2013-10-14 CITIC Group Corporation +中信 + +// xn--fjq720a : 2014-05-22 Binky Moon, LLC +娱乐 + +// xn--flw351e : 2014-07-31 Charleston Road Registry Inc. +谷歌 + +// xn--fzys8d69uvgm : 2015-05-14 PCCW Enterprises Limited +電訊盈科 + +// xn--g2xx48c : 2015-01-30 Minds + Machines Group Limited +购物 + +// xn--gckr3f0f : 2015-02-26 Amazon Registry Services, Inc. +クラウド + +// xn--gk3at1e : 2015-10-08 Amazon Registry Services, Inc. +通販 + +// xn--hxt814e : 2014-05-15 Zodiac Taurus Limited +网店 + +// xn--i1b6b1a6a2e : 2013-11-14 Public Interest Registry +संगठन + +// xn--imr513n : 2014-12-11 Dot Trademark TLD Holding Company Limited +餐厅 + +// xn--io0a7i : 2013-11-14 China Internet Network Information Center (CNNIC) +网络 + +// xn--j1aef : 2015-01-15 VeriSign Sarl +ком + +// xn--jlq61u9w7b : 2015-01-08 Nokia Corporation +诺基亚 + +// xn--jvr189m : 2015-02-26 Amazon Registry Services, Inc. +食品 + +// xn--kcrx77d1x4a : 2014-11-07 Koninklijke Philips N.V. +飞利浦 + +// xn--kpu716f : 2014-12-22 Richemont DNS Inc. +手表 + +// xn--kput3i : 2014-02-13 Beijing RITT-Net Technology Development Co., Ltd +手机 + +// xn--mgba3a3ejt : 2014-11-20 Aramco Services Company +ارامكو + +// xn--mgba7c0bbn0a : 2015-05-14 Crescent Holding GmbH +العليان + +// xn--mgbaakc7dvf : 2015-09-03 Emirates Telecommunications Corporation (trading as Etisalat) +اتصالات + +// xn--mgbab2bd : 2013-10-31 CORE Association +بازار + +// xn--mgbb9fbpob : 2014-12-18 GreenTech Consultancy Company W.L.L. +موبايلي + +// xn--mgbca7dzdo : 2015-07-30 Abu Dhabi Systems and Information Centre +ابوظبي + +// xn--mgbi4ecexp : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) +كاثوليك + +// xn--mgbt3dhd : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. +همراه + +// xn--mk1bu44c : 2015-01-15 VeriSign Sarl +닷컴 + +// xn--mxtq1m : 2014-03-06 Net-Chinese Co., Ltd. +政府 + +// xn--ngbc5azd : 2013-07-13 International Domain Registry Pty. Ltd. +شبكة + +// xn--ngbe9e0a : 2014-12-04 Kuwait Finance House +بيتك + +// xn--ngbrx : 2015-11-12 League of Arab States +عرب + +// xn--nqv7f : 2013-11-14 Public Interest Registry +机构 + +// xn--nqv7fs00ema : 2013-11-14 Public Interest Registry +组织机构 + +// xn--nyqy26a : 2014-11-07 Stable Tone Limited +健康 + +// xn--otu796d : 2017-08-06 Dot Trademark TLD Holding Company Limited +招聘 + +// xn--p1acf : 2013-12-12 Rusnames Limited +рус + +// xn--pbt977c : 2014-12-22 Richemont DNS Inc. +珠宝 + +// xn--pssy2u : 2015-01-15 VeriSign Sarl +大拿 + +// xn--q9jyb4c : 2013-09-17 Charleston Road Registry Inc. +みんな + +// xn--qcka1pmc : 2014-07-31 Charleston Road Registry Inc. +グーグル + +// xn--rhqv96g : 2013-09-11 Stable Tone Limited +世界 + +// xn--rovu88b : 2015-02-26 Amazon Registry Services, Inc. +書籍 + +// xn--ses554g : 2014-01-16 KNET Co., Ltd. +网址 + +// xn--t60b56a : 2015-01-15 VeriSign Sarl +닷넷 + +// xn--tckwe : 2015-01-15 VeriSign Sarl +コム + +// xn--tiq49xqyj : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) +天主教 + +// xn--unup4y : 2013-07-14 Binky Moon, LLC +游戏 + +// xn--vermgensberater-ctb : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG +vermögensberater + +// xn--vermgensberatung-pwb : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG +vermögensberatung + +// xn--vhquv : 2013-08-27 Binky Moon, LLC +企业 + +// xn--vuq861b : 2014-10-16 Beijing Tele-info Network Technology Co., Ltd. +信息 + +// xn--w4r85el8fhu5dnra : 2015-04-30 Kerry Trading Co. Limited +嘉里大酒店 + +// xn--w4rs40l : 2015-07-30 Kerry Trading Co. Limited +嘉里 + +// xn--xhq521b : 2013-11-14 Guangzhou YU Wei Information Technology Co., Ltd. +广东 + +// xn--zfr164b : 2013-11-08 China Organizational Name Administration Center +政务 + +// xyz : 2013-12-05 XYZ.COM LLC +xyz + +// yachts : 2014-01-09 DERYachts, LLC +yachts + +// yahoo : 2015-04-02 Yahoo! Domain Services Inc. +yahoo + +// yamaxun : 2014-12-18 Amazon Registry Services, Inc. +yamaxun + +// yandex : 2014-04-10 YANDEX, LLC +yandex + +// yodobashi : 2014-11-20 YODOBASHI CAMERA CO.,LTD. +yodobashi + +// yoga : 2014-05-29 Minds + Machines Group Limited +yoga + +// yokohama : 2013-12-12 GMO Registry, Inc. +yokohama + +// you : 2015-04-09 Amazon Registry Services, Inc. +you + +// youtube : 2014-05-01 Charleston Road Registry Inc. +youtube + +// yun : 2015-01-08 QIHOO 360 TECHNOLOGY CO. LTD. +yun + +// zappos : 2015-06-25 Amazon Registry Services, Inc. +zappos + +// zara : 2014-11-07 Industria de Diseño Textil, S.A. (INDITEX, S.A.) +zara + +// zero : 2014-12-18 Amazon Registry Services, Inc. +zero + +// zip : 2014-05-08 Charleston Road Registry Inc. +zip + +// zone : 2013-11-14 Binky Moon, LLC +zone + +// zuerich : 2014-11-07 Kanton Zürich (Canton of Zurich) +zuerich + + +// ===END ICANN DOMAINS=== +// ===BEGIN PRIVATE DOMAINS=== +// (Note: these are in alphabetical order by company name) + +// 1GB LLC : https://www.1gb.ua/ +// Submitted by 1GB LLC +cc.ua +inf.ua +ltd.ua + +// Agnat sp. z o.o. : https://domena.pl +// Submitted by Przemyslaw Plewa +beep.pl + +// Alces Software Ltd : http://alces-software.com +// Submitted by Mark J. Titorenko +*.compute.estate +*.alces.network + +// alwaysdata : https://www.alwaysdata.com +// Submitted by Cyril +alwaysdata.net + +// Amazon CloudFront : https://aws.amazon.com/cloudfront/ +// Submitted by Donavan Miller +cloudfront.net + +// Amazon Elastic Compute Cloud : https://aws.amazon.com/ec2/ +// Submitted by Luke Wells +*.compute.amazonaws.com +*.compute-1.amazonaws.com +*.compute.amazonaws.com.cn +us-east-1.amazonaws.com + +// Amazon Elastic Beanstalk : https://aws.amazon.com/elasticbeanstalk/ +// Submitted by Luke Wells +cn-north-1.eb.amazonaws.com.cn +cn-northwest-1.eb.amazonaws.com.cn +elasticbeanstalk.com +ap-northeast-1.elasticbeanstalk.com +ap-northeast-2.elasticbeanstalk.com +ap-northeast-3.elasticbeanstalk.com +ap-south-1.elasticbeanstalk.com +ap-southeast-1.elasticbeanstalk.com +ap-southeast-2.elasticbeanstalk.com +ca-central-1.elasticbeanstalk.com +eu-central-1.elasticbeanstalk.com +eu-west-1.elasticbeanstalk.com +eu-west-2.elasticbeanstalk.com +eu-west-3.elasticbeanstalk.com +sa-east-1.elasticbeanstalk.com +us-east-1.elasticbeanstalk.com +us-east-2.elasticbeanstalk.com +us-gov-west-1.elasticbeanstalk.com +us-west-1.elasticbeanstalk.com +us-west-2.elasticbeanstalk.com + +// Amazon Elastic Load Balancing : https://aws.amazon.com/elasticloadbalancing/ +// Submitted by Luke Wells +*.elb.amazonaws.com +*.elb.amazonaws.com.cn + +// Amazon S3 : https://aws.amazon.com/s3/ +// Submitted by Luke Wells +s3.amazonaws.com +s3-ap-northeast-1.amazonaws.com +s3-ap-northeast-2.amazonaws.com +s3-ap-south-1.amazonaws.com +s3-ap-southeast-1.amazonaws.com +s3-ap-southeast-2.amazonaws.com +s3-ca-central-1.amazonaws.com +s3-eu-central-1.amazonaws.com +s3-eu-west-1.amazonaws.com +s3-eu-west-2.amazonaws.com +s3-eu-west-3.amazonaws.com +s3-external-1.amazonaws.com +s3-fips-us-gov-west-1.amazonaws.com +s3-sa-east-1.amazonaws.com +s3-us-gov-west-1.amazonaws.com +s3-us-east-2.amazonaws.com +s3-us-west-1.amazonaws.com +s3-us-west-2.amazonaws.com +s3.ap-northeast-2.amazonaws.com +s3.ap-south-1.amazonaws.com +s3.cn-north-1.amazonaws.com.cn +s3.ca-central-1.amazonaws.com +s3.eu-central-1.amazonaws.com +s3.eu-west-2.amazonaws.com +s3.eu-west-3.amazonaws.com +s3.us-east-2.amazonaws.com +s3.dualstack.ap-northeast-1.amazonaws.com +s3.dualstack.ap-northeast-2.amazonaws.com +s3.dualstack.ap-south-1.amazonaws.com +s3.dualstack.ap-southeast-1.amazonaws.com +s3.dualstack.ap-southeast-2.amazonaws.com +s3.dualstack.ca-central-1.amazonaws.com +s3.dualstack.eu-central-1.amazonaws.com +s3.dualstack.eu-west-1.amazonaws.com +s3.dualstack.eu-west-2.amazonaws.com +s3.dualstack.eu-west-3.amazonaws.com +s3.dualstack.sa-east-1.amazonaws.com +s3.dualstack.us-east-1.amazonaws.com +s3.dualstack.us-east-2.amazonaws.com +s3-website-us-east-1.amazonaws.com +s3-website-us-west-1.amazonaws.com +s3-website-us-west-2.amazonaws.com +s3-website-ap-northeast-1.amazonaws.com +s3-website-ap-southeast-1.amazonaws.com +s3-website-ap-southeast-2.amazonaws.com +s3-website-eu-west-1.amazonaws.com +s3-website-sa-east-1.amazonaws.com +s3-website.ap-northeast-2.amazonaws.com +s3-website.ap-south-1.amazonaws.com +s3-website.ca-central-1.amazonaws.com +s3-website.eu-central-1.amazonaws.com +s3-website.eu-west-2.amazonaws.com +s3-website.eu-west-3.amazonaws.com +s3-website.us-east-2.amazonaws.com + +// Amune : https://amune.org/ +// Submitted by Team Amune +t3l3p0rt.net +tele.amune.org + +// Apigee : https://apigee.com/ +// Submitted by Apigee Security Team +apigee.io + +// Aptible : https://www.aptible.com/ +// Submitted by Thomas Orozco +on-aptible.com + +// Asociación Amigos de la Informática "Euskalamiga" : http://encounter.eus/ +// Submitted by Hector Martin +user.party.eus + +// Association potager.org : https://potager.org/ +// Submitted by Lunar +pimienta.org +poivron.org +potager.org +sweetpepper.org + +// ASUSTOR Inc. : http://www.asustor.com +// Submitted by Vincent Tseng +myasustor.com + +// Automattic Inc. : https://automattic.com/ +// Submitted by Alex Concha +go-vip.co +wpcomstaging.com + +// AVM : https://avm.de +// Submitted by Andreas Weise +myfritz.net + +// AW AdvisorWebsites.com Software Inc : https://advisorwebsites.com +// Submitted by James Kennedy +*.awdev.ca +*.advisor.ws + +// backplane : https://www.backplane.io +// Submitted by Anthony Voutas +backplaneapp.io + +// BetaInABox +// Submitted by Adrian +betainabox.com + +// BinaryLane : http://www.binarylane.com +// Submitted by Nathan O'Sullivan +bnr.la + +// Blackbaud, Inc. : https://www.blackbaud.com +// Submitted by Paul Crowder +blackbaudcdn.net + +// Boomla : https://boomla.com +// Submitted by Tibor Halter +boomla.net + +// Boxfuse : https://boxfuse.com +// Submitted by Axel Fontaine +boxfuse.io + +// bplaced : https://www.bplaced.net/ +// Submitted by Miroslav Bozic +square7.ch +bplaced.com +bplaced.de +square7.de +bplaced.net +square7.net + +// BrowserSafetyMark +// Submitted by Dave Tharp +browsersafetymark.io + +// Bytemark Hosting : https://www.bytemark.co.uk +// Submitted by Paul Cammish +dh.bytemark.co.uk +vm.bytemark.co.uk + +// callidomus : https://www.callidomus.com/ +// Submitted by Marcus Popp +mycd.eu + +// CentralNic : http://www.centralnic.com/names/domains +// Submitted by registry +ae.org +ar.com +br.com +cn.com +com.de +com.se +de.com +eu.com +gb.com +gb.net +hu.com +hu.net +jp.net +jpn.com +kr.com +mex.com +no.com +qc.com +ru.com +sa.com +se.net +uk.com +uk.net +us.com +uy.com +za.bz +za.com + +// Africa.com Web Solutions Ltd : https://registry.africa.com +// Submitted by Gavin Brown +africa.com + +// iDOT Services Limited : http://www.domain.gr.com +// Submitted by Gavin Brown +gr.com + +// Radix FZC : http://domains.in.net +// Submitted by Gavin Brown +in.net + +// US REGISTRY LLC : http://us.org +// Submitted by Gavin Brown +us.org + +// co.com Registry, LLC : https://registry.co.com +// Submitted by Gavin Brown +co.com + +// c.la : http://www.c.la/ +c.la + +// certmgr.org : https://certmgr.org +// Submitted by B. Blechschmidt +certmgr.org + +// Citrix : https://citrix.com +// Submitted by Alex Stoddard +xenapponazure.com + +// ClearVox : http://www.clearvox.nl/ +// Submitted by Leon Rowland +virtueeldomein.nl + +// Clever Cloud : https://www.clever-cloud.com/ +// Submitted by Quentin Adam +cleverapps.io + +// Cloud66 : https://www.cloud66.com/ +// Submitted by Khash Sajadi +c66.me +cloud66.ws + +// CloudAccess.net : https://www.cloudaccess.net/ +// Submitted by Pawel Panek +jdevcloud.com +wpdevcloud.com +cloudaccess.host +freesite.host +cloudaccess.net + +// cloudControl : https://www.cloudcontrol.com/ +// Submitted by Tobias Wilken +cloudcontrolled.com +cloudcontrolapp.com + +// co.ca : http://registry.co.ca/ +co.ca + +// Co & Co : https://co-co.nl/ +// Submitted by Govert Versluis +*.otap.co + +// i-registry s.r.o. : http://www.i-registry.cz/ +// Submitted by Martin Semrad +co.cz + +// CDN77.com : http://www.cdn77.com +// Submitted by Jan Krpes +c.cdn77.org +cdn77-ssl.net +r.cdn77.net +rsc.cdn77.org +ssl.origin.cdn77-secure.org + +// Cloud DNS Ltd : http://www.cloudns.net +// Submitted by Aleksander Hristov +cloudns.asia +cloudns.biz +cloudns.club +cloudns.cc +cloudns.eu +cloudns.in +cloudns.info +cloudns.org +cloudns.pro +cloudns.pw +cloudns.us + +// Cloudeity Inc : https://cloudeity.com +// Submitted by Stefan Dimitrov +cloudeity.net + +// CNPY : https://cnpy.gdn +// Submitted by Angelo Gladding +cnpy.gdn + +// CoDNS B.V. +co.nl +co.no + +// Combell.com : https://www.combell.com +// Submitted by Thomas Wouters +webhosting.be +hosting-cluster.nl + +// COSIMO GmbH : http://www.cosimo.de +// Submitted by Rene Marticke +dyn.cosidns.de +dynamisches-dns.de +dnsupdater.de +internet-dns.de +l-o-g-i-n.de +dynamic-dns.info +feste-ip.net +knx-server.net +static-access.net + +// Craynic, s.r.o. : http://www.craynic.com/ +// Submitted by Ales Krajnik +realm.cz + +// Cryptonomic : https://cryptonomic.net/ +// Submitted by Andrew Cady +*.cryptonomic.net + +// Cupcake : https://cupcake.io/ +// Submitted by Jonathan Rudenberg +cupcake.is + +// cyon GmbH : https://www.cyon.ch/ +// Submitted by Dominic Luechinger +cyon.link +cyon.site + +// Daplie, Inc : https://daplie.com +// Submitted by AJ ONeal +daplie.me +localhost.daplie.me + +// Datto, Inc. : https://www.datto.com/ +// Submitted by Philipp Heckel +dattolocal.com +dattorelay.com +dattoweb.com +mydatto.com +dattolocal.net +mydatto.net + +// Dansk.net : http://www.dansk.net/ +// Submitted by Anani Voule +biz.dk +co.dk +firm.dk +reg.dk +store.dk + +// dapps.earth : https://dapps.earth/ +// Submitted by Daniil Burdakov +*.dapps.earth +*.bzz.dapps.earth + +// Debian : https://www.debian.org/ +// Submitted by Peter Palfrader / Debian Sysadmin Team +debian.net + +// deSEC : https://desec.io/ +// Submitted by Peter Thomassen +dedyn.io + +// DNShome : https://www.dnshome.de/ +// Submitted by Norbert Auler +dnshome.de + +// DotArai : https://www.dotarai.com/ +// Submitted by Atsadawat Netcharadsang +online.th +shop.th + +// DrayTek Corp. : https://www.draytek.com/ +// Submitted by Paul Fang +drayddns.com + +// DreamHost : http://www.dreamhost.com/ +// Submitted by Andrew Farmer +dreamhosters.com + +// Drobo : http://www.drobo.com/ +// Submitted by Ricardo Padilha +mydrobo.com + +// Drud Holdings, LLC. : https://www.drud.com/ +// Submitted by Kevin Bridges +drud.io +drud.us + +// DuckDNS : http://www.duckdns.org/ +// Submitted by Richard Harper +duckdns.org + +// dy.fi : http://dy.fi/ +// Submitted by Heikki Hannikainen +dy.fi +tunk.org + +// DynDNS.com : http://www.dyndns.com/services/dns/dyndns/ +dyndns-at-home.com +dyndns-at-work.com +dyndns-blog.com +dyndns-free.com +dyndns-home.com +dyndns-ip.com +dyndns-mail.com +dyndns-office.com +dyndns-pics.com +dyndns-remote.com +dyndns-server.com +dyndns-web.com +dyndns-wiki.com +dyndns-work.com +dyndns.biz +dyndns.info +dyndns.org +dyndns.tv +at-band-camp.net +ath.cx +barrel-of-knowledge.info +barrell-of-knowledge.info +better-than.tv +blogdns.com +blogdns.net +blogdns.org +blogsite.org +boldlygoingnowhere.org +broke-it.net +buyshouses.net +cechire.com +dnsalias.com +dnsalias.net +dnsalias.org +dnsdojo.com +dnsdojo.net +dnsdojo.org +does-it.net +doesntexist.com +doesntexist.org +dontexist.com +dontexist.net +dontexist.org +doomdns.com +doomdns.org +dvrdns.org +dyn-o-saur.com +dynalias.com +dynalias.net +dynalias.org +dynathome.net +dyndns.ws +endofinternet.net +endofinternet.org +endoftheinternet.org +est-a-la-maison.com +est-a-la-masion.com +est-le-patron.com +est-mon-blogueur.com +for-better.biz +for-more.biz +for-our.info +for-some.biz +for-the.biz +forgot.her.name +forgot.his.name +from-ak.com +from-al.com +from-ar.com +from-az.net +from-ca.com +from-co.net +from-ct.com +from-dc.com +from-de.com +from-fl.com +from-ga.com +from-hi.com +from-ia.com +from-id.com +from-il.com +from-in.com +from-ks.com +from-ky.com +from-la.net +from-ma.com +from-md.com +from-me.org +from-mi.com +from-mn.com +from-mo.com +from-ms.com +from-mt.com +from-nc.com +from-nd.com +from-ne.com +from-nh.com +from-nj.com +from-nm.com +from-nv.com +from-ny.net +from-oh.com +from-ok.com +from-or.com +from-pa.com +from-pr.com +from-ri.com +from-sc.com +from-sd.com +from-tn.com +from-tx.com +from-ut.com +from-va.com +from-vt.com +from-wa.com +from-wi.com +from-wv.com +from-wy.com +ftpaccess.cc +fuettertdasnetz.de +game-host.org +game-server.cc +getmyip.com +gets-it.net +go.dyndns.org +gotdns.com +gotdns.org +groks-the.info +groks-this.info +ham-radio-op.net +here-for-more.info +hobby-site.com +hobby-site.org +home.dyndns.org +homedns.org +homeftp.net +homeftp.org +homeip.net +homelinux.com +homelinux.net +homelinux.org +homeunix.com +homeunix.net +homeunix.org +iamallama.com +in-the-band.net +is-a-anarchist.com +is-a-blogger.com +is-a-bookkeeper.com +is-a-bruinsfan.org +is-a-bulls-fan.com +is-a-candidate.org +is-a-caterer.com +is-a-celticsfan.org +is-a-chef.com +is-a-chef.net +is-a-chef.org +is-a-conservative.com +is-a-cpa.com +is-a-cubicle-slave.com +is-a-democrat.com +is-a-designer.com +is-a-doctor.com +is-a-financialadvisor.com +is-a-geek.com +is-a-geek.net +is-a-geek.org +is-a-green.com +is-a-guru.com +is-a-hard-worker.com +is-a-hunter.com +is-a-knight.org +is-a-landscaper.com +is-a-lawyer.com +is-a-liberal.com +is-a-libertarian.com +is-a-linux-user.org +is-a-llama.com +is-a-musician.com +is-a-nascarfan.com +is-a-nurse.com +is-a-painter.com +is-a-patsfan.org +is-a-personaltrainer.com +is-a-photographer.com +is-a-player.com +is-a-republican.com +is-a-rockstar.com +is-a-socialist.com +is-a-soxfan.org +is-a-student.com +is-a-teacher.com +is-a-techie.com +is-a-therapist.com +is-an-accountant.com +is-an-actor.com +is-an-actress.com +is-an-anarchist.com +is-an-artist.com +is-an-engineer.com +is-an-entertainer.com +is-by.us +is-certified.com +is-found.org +is-gone.com +is-into-anime.com +is-into-cars.com +is-into-cartoons.com +is-into-games.com +is-leet.com +is-lost.org +is-not-certified.com +is-saved.org +is-slick.com +is-uberleet.com +is-very-bad.org +is-very-evil.org +is-very-good.org +is-very-nice.org +is-very-sweet.org +is-with-theband.com +isa-geek.com +isa-geek.net +isa-geek.org +isa-hockeynut.com +issmarterthanyou.com +isteingeek.de +istmein.de +kicks-ass.net +kicks-ass.org +knowsitall.info +land-4-sale.us +lebtimnetz.de +leitungsen.de +likes-pie.com +likescandy.com +merseine.nu +mine.nu +misconfused.org +mypets.ws +myphotos.cc +neat-url.com +office-on-the.net +on-the-web.tv +podzone.net +podzone.org +readmyblog.org +saves-the-whales.com +scrapper-site.net +scrapping.cc +selfip.biz +selfip.com +selfip.info +selfip.net +selfip.org +sells-for-less.com +sells-for-u.com +sells-it.net +sellsyourhome.org +servebbs.com +servebbs.net +servebbs.org +serveftp.net +serveftp.org +servegame.org +shacknet.nu +simple-url.com +space-to-rent.com +stuff-4-sale.org +stuff-4-sale.us +teaches-yoga.com +thruhere.net +traeumtgerade.de +webhop.biz +webhop.info +webhop.net +webhop.org +worse-than.tv +writesthisblog.com + +// ddnss.de : https://www.ddnss.de/ +// Submitted by Robert Niedziela +ddnss.de +dyn.ddnss.de +dyndns.ddnss.de +dyndns1.de +dyn-ip24.de +home-webserver.de +dyn.home-webserver.de +myhome-server.de +ddnss.org + +// Definima : http://www.definima.com/ +// Submitted by Maxence Bitterli +definima.net +definima.io + +// dnstrace.pro : https://dnstrace.pro/ +// Submitted by Chris Partridge +bci.dnstrace.pro + +// Dynu.com : https://www.dynu.com/ +// Submitted by Sue Ye +ddnsfree.com +ddnsgeek.com +giize.com +gleeze.com +kozow.com +loseyourip.com +ooguy.com +theworkpc.com +casacam.net +dynu.net +accesscam.org +camdvr.org +freeddns.org +mywire.org +webredirect.org +myddns.rocks +blogsite.xyz + +// dynv6 : https://dynv6.com +// Submitted by Dominik Menke +dynv6.net + +// E4YOU spol. s.r.o. : https://e4you.cz/ +// Submitted by Vladimir Dudr +e4.cz + +// Enalean SAS: https://www.enalean.com +// Submitted by Thomas Cottier +mytuleap.com + +// Enonic : http://enonic.com/ +// Submitted by Erik Kaareng-Sunde +enonic.io +customer.enonic.io + +// EU.org https://eu.org/ +// Submitted by Pierre Beyssac +eu.org +al.eu.org +asso.eu.org +at.eu.org +au.eu.org +be.eu.org +bg.eu.org +ca.eu.org +cd.eu.org +ch.eu.org +cn.eu.org +cy.eu.org +cz.eu.org +de.eu.org +dk.eu.org +edu.eu.org +ee.eu.org +es.eu.org +fi.eu.org +fr.eu.org +gr.eu.org +hr.eu.org +hu.eu.org +ie.eu.org +il.eu.org +in.eu.org +int.eu.org +is.eu.org +it.eu.org +jp.eu.org +kr.eu.org +lt.eu.org +lu.eu.org +lv.eu.org +mc.eu.org +me.eu.org +mk.eu.org +mt.eu.org +my.eu.org +net.eu.org +ng.eu.org +nl.eu.org +no.eu.org +nz.eu.org +paris.eu.org +pl.eu.org +pt.eu.org +q-a.eu.org +ro.eu.org +ru.eu.org +se.eu.org +si.eu.org +sk.eu.org +tr.eu.org +uk.eu.org +us.eu.org + +// Evennode : http://www.evennode.com/ +// Submitted by Michal Kralik +eu-1.evennode.com +eu-2.evennode.com +eu-3.evennode.com +eu-4.evennode.com +us-1.evennode.com +us-2.evennode.com +us-3.evennode.com +us-4.evennode.com + +// eDirect Corp. : https://hosting.url.com.tw/ +// Submitted by C.S. chang +twmail.cc +twmail.net +twmail.org +mymailer.com.tw +url.tw + +// Facebook, Inc. +// Submitted by Peter Ruibal +apps.fbsbx.com + +// FAITID : https://faitid.org/ +// Submitted by Maxim Alzoba +// https://www.flexireg.net/stat_info +ru.net +adygeya.ru +bashkiria.ru +bir.ru +cbg.ru +com.ru +dagestan.ru +grozny.ru +kalmykia.ru +kustanai.ru +marine.ru +mordovia.ru +msk.ru +mytis.ru +nalchik.ru +nov.ru +pyatigorsk.ru +spb.ru +vladikavkaz.ru +vladimir.ru +abkhazia.su +adygeya.su +aktyubinsk.su +arkhangelsk.su +armenia.su +ashgabad.su +azerbaijan.su +balashov.su +bashkiria.su +bryansk.su +bukhara.su +chimkent.su +dagestan.su +east-kazakhstan.su +exnet.su +georgia.su +grozny.su +ivanovo.su +jambyl.su +kalmykia.su +kaluga.su +karacol.su +karaganda.su +karelia.su +khakassia.su +krasnodar.su +kurgan.su +kustanai.su +lenug.su +mangyshlak.su +mordovia.su +msk.su +murmansk.su +nalchik.su +navoi.su +north-kazakhstan.su +nov.su +obninsk.su +penza.su +pokrovsk.su +sochi.su +spb.su +tashkent.su +termez.su +togliatti.su +troitsk.su +tselinograd.su +tula.su +tuva.su +vladikavkaz.su +vladimir.su +vologda.su + +// Fancy Bits, LLC : http://getchannels.com +// Submitted by Aman Gupta +channelsdvr.net + +// Fastly Inc. : http://www.fastly.com/ +// Submitted by Fastly Security +fastly-terrarium.com +fastlylb.net +map.fastlylb.net +freetls.fastly.net +map.fastly.net +a.prod.fastly.net +global.prod.fastly.net +a.ssl.fastly.net +b.ssl.fastly.net +global.ssl.fastly.net + +// FASTVPS EESTI OU : https://fastvps.ru/ +// Submitted by Likhachev Vasiliy +fastpanel.direct +fastvps-server.com + +// Featherhead : https://featherhead.xyz/ +// Submitted by Simon Menke +fhapp.xyz + +// Fedora : https://fedoraproject.org/ +// submitted by Patrick Uiterwijk +fedorainfracloud.org +fedorapeople.org +cloud.fedoraproject.org +app.os.fedoraproject.org +app.os.stg.fedoraproject.org + +// Fermax : https://fermax.com/ +// submitted by Koen Van Isterdael +mydobiss.com + +// Filegear Inc. : https://www.filegear.com +// Submitted by Jason Zhu +filegear.me +filegear-au.me +filegear-de.me +filegear-gb.me +filegear-ie.me +filegear-jp.me +filegear-sg.me + +// Firebase, Inc. +// Submitted by Chris Raynor +firebaseapp.com + +// Flynn : https://flynn.io +// Submitted by Jonathan Rudenberg +flynnhub.com +flynnhosting.net + +// Freebox : http://www.freebox.fr +// Submitted by Romain Fliedel +freebox-os.com +freeboxos.com +fbx-os.fr +fbxos.fr +freebox-os.fr +freeboxos.fr + +// freedesktop.org : https://www.freedesktop.org +// Submitted by Daniel Stone +freedesktop.org + +// Futureweb OG : http://www.futureweb.at +// Submitted by Andreas Schnederle-Wagner +*.futurecms.at +*.ex.futurecms.at +*.in.futurecms.at +futurehosting.at +futuremailing.at +*.ex.ortsinfo.at +*.kunden.ortsinfo.at +*.statics.cloud + +// GDS : https://www.gov.uk/service-manual/operations/operating-servicegovuk-subdomains +// Submitted by David Illsley +service.gov.uk + +// GitHub, Inc. +// Submitted by Patrick Toomey +github.io +githubusercontent.com + +// GitLab, Inc. +// Submitted by Alex Hanselka +gitlab.io + +// UKHomeOffice : https://www.gov.uk/government/organisations/home-office +// Submitted by Jon Shanks +homeoffice.gov.uk + +// GlobeHosting, Inc. +// Submitted by Zoltan Egresi +ro.im +shop.ro + +// GoIP DNS Services : http://www.goip.de +// Submitted by Christian Poulter +goip.de + +// Google, Inc. +// Submitted by Eduardo Vela +run.app +a.run.app +*.0emm.com +appspot.com +blogspot.ae +blogspot.al +blogspot.am +blogspot.ba +blogspot.be +blogspot.bg +blogspot.bj +blogspot.ca +blogspot.cf +blogspot.ch +blogspot.cl +blogspot.co.at +blogspot.co.id +blogspot.co.il +blogspot.co.ke +blogspot.co.nz +blogspot.co.uk +blogspot.co.za +blogspot.com +blogspot.com.ar +blogspot.com.au +blogspot.com.br +blogspot.com.by +blogspot.com.co +blogspot.com.cy +blogspot.com.ee +blogspot.com.eg +blogspot.com.es +blogspot.com.mt +blogspot.com.ng +blogspot.com.tr +blogspot.com.uy +blogspot.cv +blogspot.cz +blogspot.de +blogspot.dk +blogspot.fi +blogspot.fr +blogspot.gr +blogspot.hk +blogspot.hr +blogspot.hu +blogspot.ie +blogspot.in +blogspot.is +blogspot.it +blogspot.jp +blogspot.kr +blogspot.li +blogspot.lt +blogspot.lu +blogspot.md +blogspot.mk +blogspot.mr +blogspot.mx +blogspot.my +blogspot.nl +blogspot.no +blogspot.pe +blogspot.pt +blogspot.qa +blogspot.re +blogspot.ro +blogspot.rs +blogspot.ru +blogspot.se +blogspot.sg +blogspot.si +blogspot.sk +blogspot.sn +blogspot.td +blogspot.tw +blogspot.ug +blogspot.vn +cloudfunctions.net +cloud.goog +codespot.com +googleapis.com +googlecode.com +pagespeedmobilizer.com +publishproxy.com +withgoogle.com +withyoutube.com + +// Hashbang : https://hashbang.sh +hashbang.sh + +// Hasura : https://hasura.io +// Submitted by Shahidh K Muhammed +hasura.app +hasura-app.io + +// Hepforge : https://www.hepforge.org +// Submitted by David Grellscheid +hepforge.org + +// Heroku : https://www.heroku.com/ +// Submitted by Tom Maher +herokuapp.com +herokussl.com + +// Hibernating Rhinos +// Submitted by Oren Eini +myravendb.com +ravendb.community +ravendb.me +development.run +ravendb.run + +// Ici la Lune : http://www.icilalune.com/ +// Submitted by Simon Morvan +moonscale.net + +// iki.fi +// Submitted by Hannu Aronsson +iki.fi + +// Individual Network Berlin e.V. : https://www.in-berlin.de/ +// Submitted by Christian Seitz +dyn-berlin.de +in-berlin.de +in-brb.de +in-butter.de +in-dsl.de +in-dsl.net +in-dsl.org +in-vpn.de +in-vpn.net +in-vpn.org + +// info.at : http://www.info.at/ +biz.at +info.at + +// info.cx : http://info.cx +// Submitted by Jacob Slater +info.cx + +// Interlegis : http://www.interlegis.leg.br +// Submitted by Gabriel Ferreira +ac.leg.br +al.leg.br +am.leg.br +ap.leg.br +ba.leg.br +ce.leg.br +df.leg.br +es.leg.br +go.leg.br +ma.leg.br +mg.leg.br +ms.leg.br +mt.leg.br +pa.leg.br +pb.leg.br +pe.leg.br +pi.leg.br +pr.leg.br +rj.leg.br +rn.leg.br +ro.leg.br +rr.leg.br +rs.leg.br +sc.leg.br +se.leg.br +sp.leg.br +to.leg.br + +// intermetrics GmbH : https://pixolino.com/ +// Submitted by Wolfgang Schwarz +pixolino.com + +// IPiFony Systems, Inc. : https://www.ipifony.com/ +// Submitted by Matthew Hardeman +ipifony.net + +// IServ GmbH : https://iserv.eu +// Submitted by Kim-Alexander Brodowski +mein-iserv.de +test-iserv.de + +// Jino : https://www.jino.ru +// Submitted by Sergey Ulyashin +myjino.ru +*.hosting.myjino.ru +*.landing.myjino.ru +*.spectrum.myjino.ru +*.vps.myjino.ru + +// Joyent : https://www.joyent.com/ +// Submitted by Brian Bennett +*.triton.zone +*.cns.joyent.com + +// JS.ORG : http://dns.js.org +// Submitted by Stefan Keim +js.org + +// Keyweb AG : https://www.keyweb.de +// Submitted by Martin Dannehl +keymachine.de + +// KnightPoint Systems, LLC : http://www.knightpoint.com/ +// Submitted by Roy Keene +knightpoint.systems + +// .KRD : http://nic.krd/data/krd/Registration%20Policy.pdf +co.krd +edu.krd + +// LCube - Professional hosting e.K. : https://www.lcube-webhosting.de +// Submitted by Lars Laehn +git-repos.de +lcube-server.de +svn-repos.de + +// Leadpages : https://www.leadpages.net +// Submitted by Greg Dallavalle +leadpages.co +lpages.co +lpusercontent.com + +// Lifetime Hosting : https://Lifetime.Hosting/ +// Submitted by Mike Fillator +co.business +co.education +co.events +co.financial +co.network +co.place +co.technology + +// Lightmaker Property Manager, Inc. : https://app.lmpm.com/ +// Submitted by Greg Holland +app.lmpm.com + +// Linki Tools UG : https://linki.tools +// Submitted by Paulo Matos +linkitools.space + +// linkyard ldt: https://www.linkyard.ch/ +// Submitted by Mario Siegenthaler +linkyard.cloud +linkyard-cloud.ch + +// LiquidNet Ltd : http://www.liquidnetlimited.com/ +// Submitted by Victor Velchev +we.bs + +// LubMAN UMCS Sp. z o.o : https://lubman.pl/ +// Submitted by Ireneusz Maliszewski +krasnik.pl +leczna.pl +lubartow.pl +lublin.pl +poniatowa.pl +swidnik.pl + +// Lug.org.uk : https://lug.org.uk +// Submitted by Jon Spriggs +uklugs.org +glug.org.uk +lug.org.uk +lugs.org.uk + +// Lukanet Ltd : https://lukanet.com +// Submitted by Anton Avramov +barsy.bg +barsy.co.uk +barsyonline.co.uk +barsycenter.com +barsyonline.com +barsy.club +barsy.de +barsy.eu +barsy.in +barsy.info +barsy.io +barsy.me +barsy.menu +barsy.mobi +barsy.net +barsy.online +barsy.org +barsy.pro +barsy.pub +barsy.shop +barsy.site +barsy.support +barsy.uk + +// Magento Commerce +// Submitted by Damien Tournoud +*.magentosite.cloud + +// May First - People Link : https://mayfirst.org/ +// Submitted by Jamie McClelland +mayfirst.info +mayfirst.org + +// Mail.Ru Group : https://hb.cldmail.ru +// Submitted by Ilya Zaretskiy +hb.cldmail.ru + +// Memset hosting : https://www.memset.com +// Submitted by Tom Whitwell +miniserver.com +memset.net + +// MetaCentrum, CESNET z.s.p.o. : https://www.metacentrum.cz/en/ +// Submitted by Zdeněk Šustr +cloud.metacentrum.cz +custom.metacentrum.cz + +// MetaCentrum, CESNET z.s.p.o. : https://www.metacentrum.cz/en/ +// Submitted by Radim Janča +flt.cloud.muni.cz +usr.cloud.muni.cz + +// Meteor Development Group : https://www.meteor.com/hosting +// Submitted by Pierre Carrier +meteorapp.com +eu.meteorapp.com + +// Michau Enterprises Limited : http://www.co.pl/ +co.pl + +// Microsoft Corporation : http://microsoft.com +// Submitted by Justin Luk +azurecontainer.io +azurewebsites.net +azure-mobile.net +cloudapp.net + +// Mozilla Corporation : https://mozilla.com +// Submitted by Ben Francis +mozilla-iot.org + +// Mozilla Foundation : https://mozilla.org/ +// Submitted by glob +bmoattachments.org + +// MSK-IX : https://www.msk-ix.ru/ +// Submitted by Khannanov Roman +net.ru +org.ru +pp.ru + +// Netlify : https://www.netlify.com +// Submitted by Jessica Parsons +bitballoon.com +netlify.com + +// Neustar Inc. +// Submitted by Trung Tran +4u.com + +// ngrok : https://ngrok.com/ +// Submitted by Alan Shreve +ngrok.io + +// Nimbus Hosting Ltd. : https://www.nimbushosting.co.uk/ +// Submitted by Nicholas Ford +nh-serv.co.uk + +// NFSN, Inc. : https://www.NearlyFreeSpeech.NET/ +// Submitted by Jeff Wheelhouse +nfshost.com + +// Now-DNS : https://now-dns.com +// Submitted by Steve Russell +dnsking.ch +mypi.co +n4t.co +001www.com +ddnslive.com +myiphost.com +forumz.info +16-b.it +32-b.it +64-b.it +soundcast.me +tcp4.me +dnsup.net +hicam.net +now-dns.net +ownip.net +vpndns.net +dynserv.org +now-dns.org +x443.pw +now-dns.top +ntdll.top +freeddns.us +crafting.xyz +zapto.xyz + +// nsupdate.info : https://www.nsupdate.info/ +// Submitted by Thomas Waldmann +nsupdate.info +nerdpol.ovh + +// No-IP.com : https://noip.com/ +// Submitted by Deven Reza +blogsyte.com +brasilia.me +cable-modem.org +ciscofreak.com +collegefan.org +couchpotatofries.org +damnserver.com +ddns.me +ditchyourip.com +dnsfor.me +dnsiskinky.com +dvrcam.info +dynns.com +eating-organic.net +fantasyleague.cc +geekgalaxy.com +golffan.us +health-carereform.com +homesecuritymac.com +homesecuritypc.com +hopto.me +ilovecollege.info +loginto.me +mlbfan.org +mmafan.biz +myactivedirectory.com +mydissent.net +myeffect.net +mymediapc.net +mypsx.net +mysecuritycamera.com +mysecuritycamera.net +mysecuritycamera.org +net-freaks.com +nflfan.org +nhlfan.net +no-ip.ca +no-ip.co.uk +no-ip.net +noip.us +onthewifi.com +pgafan.net +point2this.com +pointto.us +privatizehealthinsurance.net +quicksytes.com +read-books.org +securitytactics.com +serveexchange.com +servehumour.com +servep2p.com +servesarcasm.com +stufftoread.com +ufcfan.org +unusualperson.com +workisboring.com +3utilities.com +bounceme.net +ddns.net +ddnsking.com +gotdns.ch +hopto.org +myftp.biz +myftp.org +myvnc.com +no-ip.biz +no-ip.info +no-ip.org +noip.me +redirectme.net +servebeer.com +serveblog.net +servecounterstrike.com +serveftp.com +servegame.com +servehalflife.com +servehttp.com +serveirc.com +serveminecraft.net +servemp3.com +servepics.com +servequake.com +sytes.net +webhop.me +zapto.org + +// NodeArt : https://nodeart.io +// Submitted by Konstantin Nosov +stage.nodeart.io + +// Nodum B.V. : https://nodum.io/ +// Submitted by Wietse Wind +nodum.co +nodum.io + +// Nucleos Inc. : https://nucleos.com +// Submitted by Piotr Zduniak +pcloud.host + +// NYC.mn : http://www.information.nyc.mn +// Submitted by Matthew Brown +nyc.mn + +// NymNom : https://nymnom.com/ +// Submitted by Dave McCormack +nom.ae +nom.af +nom.ai +nom.al +nym.by +nym.bz +nom.cl +nom.gd +nom.ge +nom.gl +nym.gr +nom.gt +nym.gy +nom.hn +nym.ie +nom.im +nom.ke +nym.kz +nym.la +nym.lc +nom.li +nym.li +nym.lt +nym.lu +nym.me +nom.mk +nym.mn +nym.mx +nom.nu +nym.nz +nym.pe +nym.pt +nom.pw +nom.qa +nym.ro +nom.rs +nom.si +nym.sk +nom.st +nym.su +nym.sx +nom.tj +nym.tw +nom.ug +nom.uy +nom.vc +nom.vg + +// Octopodal Solutions, LLC. : https://ulterius.io/ +// Submitted by Andrew Sampson +cya.gg + +// Omnibond Systems, LLC. : https://www.omnibond.com +// Submitted by Cole Estep +cloudycluster.net + +// One Fold Media : http://www.onefoldmedia.com/ +// Submitted by Eddie Jones +nid.io + +// OpenCraft GmbH : http://opencraft.com/ +// Submitted by Sven Marnach +opencraft.hosting + +// Opera Software, A.S.A. +// Submitted by Yngve Pettersen +operaunite.com + +// OutSystems +// Submitted by Duarte Santos +outsystemscloud.com + +// OwnProvider GmbH: http://www.ownprovider.com +// Submitted by Jan Moennich +ownprovider.com +own.pm + +// OX : http://www.ox.rs +// Submitted by Adam Grand +ox.rs + +// oy.lc +// Submitted by Charly Coste +oy.lc + +// Pagefog : https://pagefog.com/ +// Submitted by Derek Myers +pgfog.com + +// Pagefront : https://www.pagefronthq.com/ +// Submitted by Jason Kriss +pagefrontapp.com + +// .pl domains (grandfathered) +art.pl +gliwice.pl +krakow.pl +poznan.pl +wroc.pl +zakopane.pl + +// Pantheon Systems, Inc. : https://pantheon.io/ +// Submitted by Gary Dylina +pantheonsite.io +gotpantheon.com + +// Peplink | Pepwave : http://peplink.com/ +// Submitted by Steve Leung +mypep.link + +// Planet-Work : https://www.planet-work.com/ +// Submitted by Frédéric VANNIÈRE +on-web.fr + +// Platform.sh : https://platform.sh +// Submitted by Nikola Kotur +*.platform.sh +*.platformsh.site + +// prgmr.com : https://prgmr.com/ +// Submitted by Sarah Newman +xen.prgmr.com + +// priv.at : http://www.nic.priv.at/ +// Submitted by registry +priv.at + +// Protonet GmbH : http://protonet.io +// Submitted by Martin Meier +protonet.io + +// Publication Presse Communication SARL : https://ppcom.fr +// Submitted by Yaacov Akiba Slama +chirurgiens-dentistes-en-france.fr +byen.site + +// Redstar Consultants : https://www.redstarconsultants.com/ +// Submitted by Jons Slemmer +instantcloud.cn + +// Russian Academy of Sciences +// Submitted by Tech Support +ras.ru + +// QA2 +// Submitted by Daniel Dent (https://www.danieldent.com/) +qa2.com + +// QNAP System Inc : https://www.qnap.com +// Submitted by Nick Chang +dev-myqnapcloud.com +alpha-myqnapcloud.com +myqnapcloud.com + +// Quip : https://quip.com +// Submitted by Patrick Linehan +*.quipelements.com + +// Qutheory LLC : http://qutheory.io +// Submitted by Jonas Schwartz +vapor.cloud +vaporcloud.io + +// Rackmaze LLC : https://www.rackmaze.com +// Submitted by Kirill Pertsev +rackmaze.com +rackmaze.net + +// Read The Docs, Inc : https://www.readthedocs.org +// Submitted by David Fischer +readthedocs.io + +// Red Hat, Inc. OpenShift : https://openshift.redhat.com/ +// Submitted by Tim Kramer +rhcloud.com + +// Resin.io : https://resin.io +// Submitted by Tim Perry +resindevice.io +devices.resinstaging.io + +// RethinkDB : https://www.rethinkdb.com/ +// Submitted by Chris Kastorff +hzc.io + +// Revitalised Limited : http://www.revitalised.co.uk +// Submitted by Jack Price +wellbeingzone.eu +ptplus.fit +wellbeingzone.co.uk + +// Sandstorm Development Group, Inc. : https://sandcats.io/ +// Submitted by Asheesh Laroia +sandcats.io + +// SBE network solutions GmbH : https://www.sbe.de/ +// Submitted by Norman Meilick +logoip.de +logoip.com + +// schokokeks.org GbR : https://schokokeks.org/ +// Submitted by Hanno Böck +schokokeks.net + +// Scry Security : http://www.scrysec.com +// Submitted by Shante Adam +scrysec.com + +// Securepoint GmbH : https://www.securepoint.de +// Submitted by Erik Anders +firewall-gateway.com +firewall-gateway.de +my-gateway.de +my-router.de +spdns.de +spdns.eu +firewall-gateway.net +my-firewall.org +myfirewall.org +spdns.org + +// SensioLabs, SAS : https://sensiolabs.com/ +// Submitted by Fabien Potencier +*.s5y.io +*.sensiosite.cloud + +// Service Online LLC : http://drs.ua/ +// Submitted by Serhii Bulakh +biz.ua +co.ua +pp.ua + +// ShiftEdit : https://shiftedit.net/ +// Submitted by Adam Jimenez +shiftedit.io + +// Shopblocks : http://www.shopblocks.com/ +// Submitted by Alex Bowers +myshopblocks.com + +// SinaAppEngine : http://sae.sina.com.cn/ +// Submitted by SinaAppEngine +1kapp.com +appchizi.com +applinzi.com +sinaapp.com +vipsinaapp.com + +// Siteleaf : https://www.siteleaf.com/ +// Submitted by Skylar Challand +siteleaf.net + +// Skyhat : http://www.skyhat.io +// Submitted by Shante Adam +bounty-full.com +alpha.bounty-full.com +beta.bounty-full.com + +// staticland : https://static.land +// Submitted by Seth Vincent +static.land +dev.static.land +sites.static.land + +// SourceLair PC : https://www.sourcelair.com +// Submitted by Antonis Kalipetis +apps.lair.io +*.stolos.io + +// SpaceKit : https://www.spacekit.io/ +// Submitted by Reza Akhavan +spacekit.io + +// SpeedPartner GmbH: https://www.speedpartner.de/ +// Submitted by Stefan Neufeind +customer.speedpartner.de + +// Standard Library : https://stdlib.com +// Submitted by Jacob Lee +api.stdlib.com + +// Storj Labs Inc. : https://storj.io/ +// Submitted by Philip Hutchins +storj.farm + +// Studenten Net Twente : http://www.snt.utwente.nl/ +// Submitted by Silke Hofstra +utwente.io + +// Sub 6 Limited: http://www.sub6.com +// Submitted by Dan Miller +temp-dns.com + +// Swisscom Application Cloud: https://developer.swisscom.com +// Submitted by Matthias.Winzeler +applicationcloud.io +scapp.io + +// Synology, Inc. : https://www.synology.com/ +// Submitted by Rony Weng +diskstation.me +dscloud.biz +dscloud.me +dscloud.mobi +dsmynas.com +dsmynas.net +dsmynas.org +familyds.com +familyds.net +familyds.org +i234.me +myds.me +synology.me +vpnplus.to + +// TAIFUN Software AG : http://taifun-software.de +// Submitted by Bjoern Henke +taifun-dns.de + +// TASK geographical domains (www.task.gda.pl/uslugi/dns) +gda.pl +gdansk.pl +gdynia.pl +med.pl +sopot.pl + +// Telebit : https://telebit.cloud +// Submitted by AJ ONeal +telebit.app +telebit.io +*.telebit.xyz + +// The Gwiddle Foundation : https://gwiddlefoundation.org.uk +// Submitted by Joshua Bayfield +gwiddle.co.uk + +// Thingdust AG : https://thingdust.com/ +// Submitted by Adrian Imboden +cust.dev.thingdust.io +cust.disrec.thingdust.io +cust.prod.thingdust.io +cust.testing.thingdust.io + +// TownNews.com : http://www.townnews.com +// Submitted by Dustin Ward +bloxcms.com +townnews-staging.com + +// TrafficPlex GmbH : https://www.trafficplex.de/ +// Submitted by Phillipp Röll +12hp.at +2ix.at +4lima.at +lima-city.at +12hp.ch +2ix.ch +4lima.ch +lima-city.ch +trafficplex.cloud +de.cool +12hp.de +2ix.de +4lima.de +lima-city.de +1337.pictures +clan.rip +lima-city.rocks +webspace.rocks +lima.zone + +// TransIP : https://www.transip.nl +// Submitted by Rory Breuk +*.transurl.be +*.transurl.eu +*.transurl.nl + +// TuxFamily : http://tuxfamily.org +// Submitted by TuxFamily administrators +tuxfamily.org + +// TwoDNS : https://www.twodns.de/ +// Submitted by TwoDNS-Support +dd-dns.de +diskstation.eu +diskstation.org +dray-dns.de +draydns.de +dyn-vpn.de +dynvpn.de +mein-vigor.de +my-vigor.de +my-wan.de +syno-ds.de +synology-diskstation.de +synology-ds.de + +// Uberspace : https://uberspace.de +// Submitted by Moritz Werner +uber.space +*.uberspace.de + +// UDR Limited : http://www.udr.hk.com +// Submitted by registry +hk.com +hk.org +ltd.hk +inc.hk + +// United Gameserver GmbH : https://united-gameserver.de +// Submitted by Stefan Schwarz +virtualuser.de +virtual-user.de + +// .US +// Submitted by Ed Moore +lib.de.us + +// VeryPositive SIA : http://very.lv +// Submitted by Danko Aleksejevs +2038.io + +// Viprinet Europe GmbH : http://www.viprinet.com +// Submitted by Simon Kissel +router.management + +// Virtual-Info : https://www.virtual-info.info/ +// Submitted by Adnan RIHAN +v-info.info + +// WeDeploy by Liferay, Inc. : https://www.wedeploy.com +// Submitted by Henrique Vicente +wedeploy.io +wedeploy.me +wedeploy.sh + +// Western Digital Technologies, Inc : https://www.wdc.com +// Submitted by Jung Jin +remotewd.com + +// Wikimedia Labs : https://wikitech.wikimedia.org +// Submitted by Yuvi Panda +wmflabs.org + +// XenonCloud GbR: https://xenoncloud.net +// Submitted by Julian Uphoff +half.host + +// XnBay Technology : http://www.xnbay.com/ +// Submitted by XnBay Developer +xnbay.com +u2.xnbay.com +u2-local.xnbay.com + +// XS4ALL Internet bv : https://www.xs4all.nl/ +// Submitted by Daniel Mostertman +cistron.nl +demon.nl +xs4all.space + +// YesCourse Pty Ltd : https://yescourse.com +// Submitted by Atul Bhouraskar +official.academy + +// Yola : https://www.yola.com/ +// Submitted by Stefano Rivera +yolasite.com + +// Yombo : https://yombo.net +// Submitted by Mitch Schwenk +ybo.faith +yombo.me +homelink.one +ybo.party +ybo.review +ybo.science +ybo.trade + +// Yunohost : https://yunohost.org +// Submitted by Valentin Grimaud +nohost.me +noho.st + +// ZaNiC : http://www.za.net/ +// Submitted by registry +za.net +za.org + +// Zeit, Inc. : https://zeit.domains/ +// Submitted by Olli Vanhoja +now.sh + +// Zine EOOD : https://zine.bg/ +// Submitted by Martin Angelov +bss.design + +// Zone.id : https://zone.id/ +// Submitted by Su Hendro +zone.id + +// ===END PRIVATE DOMAINS=== diff --git a/src/keepass2android-appSdkStyle/AttachmentContentProvider.cs b/src/keepass2android-appSdkStyle/AttachmentContentProvider.cs new file mode 100644 index 00000000..00cdb8f7 --- /dev/null +++ b/src/keepass2android-appSdkStyle/AttachmentContentProvider.cs @@ -0,0 +1,93 @@ +using System; +using Android.Content; +using Android.OS; +using Android.Util; +using Java.IO; + +namespace keepass2android +{ + /// + /// Makes attachments of PwEntries accessible when they are stored in the app cache + /// + [ContentProvider(new[]{"keepass2android."+AppNames.PackagePart+".provider"},Exported = true)] + public class AttachmentContentProvider : ContentProvider { + public const string AttachmentCacheSubDir = "AttachmentCache"; + + private const String ClassName = "AttachmentContentProvider"; + + // The authority is the symbolic name for the provider class + public const String Authority = "keepass2android."+AppNames.PackagePart+".provider"; + + + public override bool OnCreate() { + return true; + } + + public override ParcelFileDescriptor OpenFile(Android.Net.Uri uri, String mode) + { + + const string logTag = ClassName + " - openFile"; + + Log.Verbose(logTag, + "Called with uri: '" + uri + "'." + uri.LastPathSegment); + + if (uri.ToString().StartsWith("content://" + Authority)) + { + // The desired file name is specified by the last segment of the + // path + // E.g. + // 'content://keepass2android.provider/Test.txt' + // Take this and build the path to the file + + //Protect against path traversal with an uri like content://keepass2android.keepass2android.provider/..%2F..%2Fshared_prefs%2FKP2A.Plugin.keepass2android.plugin.qr.xml + if (uri.LastPathSegment.Contains("/")) + throw new Exception("invalid path "); + + String fileLocation = Context.CacheDir + File.Separator + AttachmentCacheSubDir + File.Separator + + uri.LastPathSegment; + + // Create & return a ParcelFileDescriptor pointing to the file + // Note: I don't care what mode they ask for - they're only getting + // read only + ParcelFileDescriptor pfd = ParcelFileDescriptor.Open(new File( + fileLocation), ParcelFileMode.ReadOnly); + return pfd; + + } + Log.Verbose(logTag, "Unsupported uri: '" + uri + "'."); + throw new FileNotFoundException("Unsupported uri: " + + uri.ToString()); + } + + // ////////////////////////////////////////////////////////////// + // Not supported / used / required for this example + // ////////////////////////////////////////////////////////////// + + + public override int Update(Android.Net.Uri uri, ContentValues contentvalues, String s, + String[] strings) { + return 0; + } + + public override int Delete(Android.Net.Uri uri, String s, String[] strings) { + return 0; + } + + + public override Android.Net.Uri Insert(Android.Net.Uri uri, ContentValues contentvalues) { + return null; + } + + + public override String GetType(Android.Net.Uri uri) { + return null; + } + + public override Android.Database.ICursor Query(Android.Net.Uri uri, string[] projection, string selection, string[] selectionArgs, string sortOrder) + { + return null; + } + + } +} + diff --git a/src/keepass2android-appSdkStyle/AutoOpenEdit.cs b/src/keepass2android-appSdkStyle/AutoOpenEdit.cs new file mode 100644 index 00000000..730d5f14 --- /dev/null +++ b/src/keepass2android-appSdkStyle/AutoOpenEdit.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Android.App; +using KeePassLib; +using KeePassLib.Security; +using KeePassLib.Serialization; +using Object = Java.Lang.Object; + +namespace keepass2android +{ + /// + /// Edit mode implementation for AutoOpen entries + /// + public class AutoOpenEdit : EditModeBase + { + private const string strVisible = "Visible"; + private const string strEnabled = "Enabled"; + private const string strUiKeyFile = "_ui_KeyFile"; + private const string strUiDatabaseFile = "_ui_DatabaseFile"; + private const string strUiIfDevice = "_ui_IfDevice_"; + + public AutoOpenEdit(PwEntry entry) + { + + } + + public override bool IsVisible(string fieldKey) + { + if (fieldKey == PwDefs.TitleField + || fieldKey == PwDefs.PasswordField + || fieldKey == strVisible + || fieldKey == strEnabled + || fieldKey.StartsWith("_ui_")) + { + return true; + } + return false; + } + + public override IEnumerable SortExtraFieldKeys(IEnumerable keys) + { + return keys.OrderBy(s => + { + if (s == strUiDatabaseFile) return 1; + if (s == strEnabled) return 2; + + if (s == strUiKeyFile) return 10000; + if (s == strVisible) return 10001; + return 10; + + }).ThenBy(s => s); + + } + + public override bool ShowAddAttachments + { + get { return false; } + } + + public override bool ShowAddExtras + { + get { return false; } + } + + public override string GetTitle(string key) + { + if (key == strVisible) + return LocaleManager.LocalizedAppContext.GetString(Resource.String.Visible_title); + if (key == strEnabled) + return LocaleManager.LocalizedAppContext.GetString(Resource.String.child_db_Enabled_title); + if (key == strUiKeyFile) + return LocaleManager.LocalizedAppContext.GetString(Resource.String.keyfile_heading); + if (key == strUiDatabaseFile) + return LocaleManager.LocalizedAppContext.GetString(Resource.String.database_file_heading); + if (key.StartsWith(strUiIfDevice)) + { + return LocaleManager.LocalizedAppContext.GetString(Resource.String.if_device_text,new Object[]{key.Substring(strUiIfDevice.Length)}); + } + return key; + } + + public override string GetFieldType(string key) + { + if ((key == strEnabled) + || key == strVisible + || key.StartsWith(strUiIfDevice)) + return "bool"; + + if ((key == strUiDatabaseFile) + || (key == strUiKeyFile)) + return "file"; + + return ""; + } + + public override void InitializeEntry(PwEntry entry) + { + base.InitializeEntry(entry); + if (!entry.Strings.Exists(strVisible)) + { + entry.Strings.Set(strVisible, new ProtectedString(false, "true")); + } + if (!entry.Strings.Exists(strEnabled)) + { + entry.Strings.Set(strEnabled, new ProtectedString(false, "true")); + } + var autoExecItem = KeeAutoExecExt.MakeAutoExecItem(App.Kp2a.CurrentDb.KpDatabase, entry, 0); + IOConnectionInfo ioc; + if (!KeeAutoExecExt.TryGetDatabaseIoc(autoExecItem, out ioc)) + ioc = IOConnectionInfo.FromPath(entry.Strings.ReadSafe(PwDefs.UrlField)); + string path = ioc.Path; + try + { + var filestorage = App.Kp2a.GetFileStorage(ioc); + if (filestorage != null) + { + path = filestorage.IocToPath(ioc); + } + } + catch (NoFileStorageFoundException) + { + + } + + + entry.Strings.Set(strUiDatabaseFile, new ProtectedString(false, path)); + entry.Strings.Set(strUiKeyFile,new ProtectedString(false,entry.Strings.ReadSafe(PwDefs.UserNameField))); + + var devices = + KeeAutoExecExt.GetIfDevice(KeeAutoExecExt.MakeAutoExecItem(App.Kp2a.CurrentDb.KpDatabase, entry, 0)); + //make sure user can enable/disable on this device explicitly: + if (!devices.ContainsKey(KeeAutoExecExt.ThisDeviceId)) + devices[KeeAutoExecExt.ThisDeviceId] = false; + foreach (var ifDevice in devices) + { + entry.Strings.Set(strUiIfDevice + ifDevice.Key, new ProtectedString(false, ifDevice.Value.ToString())); + } + } + + public override void PrepareForSaving(PwEntry entry) + { + entry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, entry.Strings.ReadSafe(strUiDatabaseFile))); + entry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, entry.Strings.ReadSafe(strUiKeyFile))); + entry.Strings.Remove(strUiDatabaseFile); + entry.Strings.Remove(strUiKeyFile); + + Dictionary devices = new Dictionary(); + foreach (string key in entry.Strings.GetKeys()) + { + if (key.StartsWith(strUiIfDevice)) + { + string device = key.Substring(strUiIfDevice.Length); + devices[device] = entry.Strings.ReadSafe(key).Equals("true", StringComparison.OrdinalIgnoreCase); + } + } + entry.Strings.Set(KeeAutoExecExt._ifDevice,new ProtectedString(false,KeeAutoExecExt.BuildIfDevice(devices))); + foreach (string device in devices.Keys) + { + entry.Strings.Remove(strUiIfDevice + device); + } + + base.PrepareForSaving(entry); + + + } + } +} \ No newline at end of file diff --git a/src/keepass2android-appSdkStyle/BiometricModule.cs b/src/keepass2android-appSdkStyle/BiometricModule.cs new file mode 100644 index 00000000..13afd03b --- /dev/null +++ b/src/keepass2android-appSdkStyle/BiometricModule.cs @@ -0,0 +1,492 @@ +using System; +using Android.Content; +using Javax.Crypto; +using Java.Security; +using Java.Lang; +using Android.Views.InputMethods; +using Android.App; +using Android.OS; +using Android.Security.Keystore; +using Android.Preferences; +using Android.Util; +using Android.Widget; +using AndroidX.Biometric; +using AndroidX.Fragment.App; +using Java.IO; +using Java.Security.Cert; +using Java.Util.Concurrent; +using Javax.Crypto.Spec; +using Exception = System.Exception; +using File = System.IO.File; + +namespace keepass2android +{ + public interface IBiometricAuthCallback + { + void OnBiometricAuthSucceeded(); + void OnBiometricError(string toString); + void OnBiometricAttemptFailed(string message); + } + + public class BiometricModule + { + public AndroidX.Fragment.App.FragmentActivity Activity { get; set; } + + public BiometricModule(AndroidX.Fragment.App.FragmentActivity activity) + { + Activity = activity; + } + + + public KeyguardManager KeyguardManager + { + get + { + return (KeyguardManager)Activity.GetSystemService("keyguard"); + } + } + + + public KeyStore Keystore + { + get + { + try + { + return KeyStore.GetInstance("AndroidKeyStore"); + } + catch (KeyStoreException e) + { + throw new RuntimeException("Failed to get an instance of KeyStore", e); + } + } + } + + public KeyGenerator KeyGenerator + { + get + { + try + { + return KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, "AndroidKeyStore"); + } + catch (NoSuchAlgorithmException e) + { + throw new RuntimeException("Failed to get an instance of KeyGenerator", e); + } + catch (NoSuchProviderException e) + { + throw new RuntimeException("Failed to get an instance of KeyGenerator", e); + } + } + } + + public Cipher Cipher + { + get + { + try + { + return Cipher.GetInstance(KeyProperties.KeyAlgorithmAes + "/" + + KeyProperties.BlockModeCbc + "/" + + KeyProperties.EncryptionPaddingPkcs7); + } + catch (NoSuchAlgorithmException e) + { + throw new RuntimeException("Failed to get an instance of Cipher", e); + } + catch (NoSuchPaddingException e) + { + throw new RuntimeException("Failed to get an instance of Cipher", e); + } + } + } + + public ISharedPreferences SharedPreferences + { + get { return PreferenceManager.GetDefaultSharedPreferences(Activity); } + } + + public bool IsAvailable + { + get + { + return BiometricManager.From(Activity).CanAuthenticate() == + BiometricManager.BiometricSuccess; + } + } + + public bool IsHardwareAvailable + { + get + { + var result = BiometricManager.From(Activity).CanAuthenticate(); + Kp2aLog.Log("BiometricHardware available = " + result); + return result == BiometricManager.BiometricSuccess + || result == BiometricManager.BiometricErrorNoneEnrolled; + } + } + } + + public abstract class BiometricCrypt : IBiometricIdentifier + { + protected const string FailedToInitCipher = "Failed to init Cipher"; + + protected readonly string _keyId; + + protected Cipher _cipher; + private CancellationSignal _cancellationSignal; + protected BiometricPrompt.CryptoObject _cryptoObject; + + protected KeyStore _keystore; + + private BiometricPrompt _biometricPrompt; + private FragmentActivity _activity; + private BiometricAuthCallbackAdapter _biometricAuthCallbackAdapter; + + public BiometricCrypt(BiometricModule biometric, string keyId) + { + Kp2aLog.Log("FP: Create " + this.GetType().Name); + _keyId = keyId; + _cipher = biometric.Cipher; + _keystore = biometric.Keystore; + _activity = biometric.Activity; + + + + } + + public abstract bool Init(); + + + protected static string GetAlias(string keyId) + { + return "keepass2android." + keyId; + } + protected static string GetIvPrefKey(string prefKey) + { + return prefKey + "_iv"; + } + + public void StartListening(IBiometricAuthCallback callback) + { + _biometricAuthCallbackAdapter = new BiometricAuthCallbackAdapter(callback, _activity); + StartListening(_biometricAuthCallbackAdapter); + } + + public void StopListening() + { + Kp2aLog.Log("Fingerprint: StopListening " + (_biometricPrompt != null ? " having prompt " : " without prompt")); + _biometricAuthCallbackAdapter?.IgnoreNextError(); + _biometricPrompt?.CancelAuthentication(); + } + + public bool HasUserInterface + { + get { return true; } + + } + + public void StartListening(BiometricPrompt.AuthenticationCallback callback) + { + + Kp2aLog.Log("Fingerprint: StartListening "); + + var executor = Executors.NewSingleThreadExecutor(); + _biometricPrompt = new BiometricPrompt(_activity, executor, callback); + + BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder() + .SetTitle(_activity.GetString(AppNames.AppNameResource)) + .SetSubtitle(_activity.GetString(Resource.String.unlock_database_title)) + .SetNegativeButtonText(_activity.GetString(Android.Resource.String.Cancel)) + .SetConfirmationRequired(false) + .Build(); + + + _biometricPrompt.Authenticate(promptInfo, _cryptoObject); + + } + + public string Encrypt(string textToEncrypt) + { + Kp2aLog.Log("FP: Encrypting"); + return Base64.EncodeToString(_cipher.DoFinal(System.Text.Encoding.UTF8.GetBytes(textToEncrypt)), 0); + } + + + public void StoreEncrypted(string textToEncrypt, string prefKey, Context context) + { + var edit = PreferenceManager.GetDefaultSharedPreferences(context).Edit(); + StoreEncrypted(textToEncrypt, prefKey, edit); + edit.Commit(); + } + + public void StoreEncrypted(string textToEncrypt, string prefKey, ISharedPreferencesEditor edit) + { + edit.PutString(prefKey, Encrypt(textToEncrypt)); + edit.PutString(GetIvPrefKey(prefKey), Base64.EncodeToString(CipherIv, 0)); + + } + + + private byte[] CipherIv + { + get { return _cipher.GetIV(); } + } + } + + public interface IBiometricIdentifier + { + bool Init(); + void StartListening(IBiometricAuthCallback callback); + + void StopListening(); + bool HasUserInterface { get; } + } + + public class BiometricDecryption : BiometricCrypt + { + private readonly Context _context; + private readonly byte[] _iv; + + + public BiometricDecryption(BiometricModule biometric, string keyId, byte[] iv) : base(biometric, keyId) + { + _iv = iv; + } + + public BiometricDecryption(BiometricModule biometric, string keyId, Context context, string prefKey) + : base(biometric, keyId) + { + _context = context; + _iv = Base64.Decode(PreferenceManager.GetDefaultSharedPreferences(context).GetString(GetIvPrefKey(prefKey), null), 0); + } + + public static bool IsSetUp(Context context, string prefKey) + { + return PreferenceManager.GetDefaultSharedPreferences(context).GetString(GetIvPrefKey(prefKey), null) != null; + } + + public override bool Init() + { + Kp2aLog.Log("FP: Init for Dec"); + try + { + _keystore.Load(null); + var aliases = _keystore.Aliases(); + if (aliases == null) + { + Kp2aLog.Log("KS: no aliases"); + } + else + { + while (aliases.HasMoreElements) + { + var o = aliases.NextElement(); + Kp2aLog.Log("alias: " + o?.ToString()); + } + Kp2aLog.Log("KS: end aliases"); + + } + var key = _keystore.GetKey(GetAlias(_keyId), null); + if (key == null) + throw new Exception("Failed to init cipher for fingerprint Init: key is null"); + var ivParams = new IvParameterSpec(_iv); + _cipher.Init(CipherMode.DecryptMode, key, ivParams); + + _cryptoObject = new BiometricPrompt.CryptoObject(_cipher); + return true; + } + catch (KeyPermanentlyInvalidatedException e) + { + Kp2aLog.Log("FP: KeyPermanentlyInvalidatedException." + e.ToString()); + return false; + } + catch (KeyStoreException e) + { + throw new RuntimeException(FailedToInitCipher + " (keystore)", e); + } + catch (CertificateException e) + { + throw new RuntimeException(FailedToInitCipher + " (CertificateException)", e); + } + catch (UnrecoverableKeyException e) + { + throw new RuntimeException(FailedToInitCipher + " (UnrecoverableKeyException)", e); + } + catch (IOException e) + { + throw new RuntimeException(FailedToInitCipher + " (IOException)", e); + } + catch (NoSuchAlgorithmException e) + { + throw new RuntimeException(FailedToInitCipher + " (NoSuchAlgorithmException)", e); + } + catch (InvalidKeyException e) + { + throw new RuntimeException(FailedToInitCipher + " (InvalidKeyException)" + e.ToString(), e); + } + } + + + public string Decrypt(string encryted) + { + Kp2aLog.Log("FP: Decrypting "); + byte[] encryptedBytes = Base64.Decode(encryted, 0); + return System.Text.Encoding.UTF8.GetString(_cipher.DoFinal(encryptedBytes)); + } + + public string DecryptStored(string prefKey) + { + string enc = PreferenceManager.GetDefaultSharedPreferences(_context).GetString(prefKey, null); + return Decrypt(enc); + } + } + + public class BiometricEncryption : BiometricCrypt + { + + private KeyGenerator _keyGen; + + + public BiometricEncryption(BiometricModule biometric, string keyId) : + base(biometric, keyId) + { + _keyGen = biometric.KeyGenerator; + Kp2aLog.Log("FP: CreateKey "); + CreateKey(); + } + + + /// + /// Creates a symmetric key in the Android Key Store which can only be used after the user + /// has authenticated with biometry. + /// + private void CreateKey() + { + try + { + _keystore.Load(null); + KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(GetAlias(_keyId), + KeyStorePurpose.Encrypt | KeyStorePurpose.Decrypt) + .SetBlockModes(KeyProperties.BlockModeCbc) + // Require the user to authenticate with biometry to authorize every use + // of the key + .SetEncryptionPaddings(KeyProperties.EncryptionPaddingPkcs7) + .SetUserAuthenticationRequired(true); + + if ((int)Build.VERSION.SdkInt >= 24) + builder.SetInvalidatedByBiometricEnrollment(true); + + _keyGen.Init( + builder + .Build()); + _keyGen.GenerateKey(); + } + catch (NoSuchAlgorithmException e) + { + throw new RuntimeException(e); + } + catch (InvalidAlgorithmParameterException e) + { + throw new RuntimeException(e); + } + catch (CertificateException e) + { + throw new RuntimeException(e); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + catch (System.Exception e) + { + Kp2aLog.LogUnexpectedError(e); + } + } + + public override bool Init() + { + Kp2aLog.Log("FP: Init for Enc "); + try + { + _keystore.Load(null); + var key = _keystore.GetKey(GetAlias(_keyId), null); + _cipher.Init(CipherMode.EncryptMode, key); + + _cryptoObject = new BiometricPrompt.CryptoObject(_cipher); + return true; + } + catch (KeyPermanentlyInvalidatedException) + { + return false; + } + catch (KeyStoreException e) + { + throw new RuntimeException(FailedToInitCipher, e); + } + catch (CertificateException e) + { + throw new RuntimeException(FailedToInitCipher, e); + } + catch (UnrecoverableKeyException e) + { + throw new RuntimeException(FailedToInitCipher, e); + } + catch (IOException e) + { + throw new RuntimeException(FailedToInitCipher, e); + } + catch (NoSuchAlgorithmException e) + { + throw new RuntimeException(FailedToInitCipher, e); + } + catch (InvalidKeyException e) + { + throw new RuntimeException(FailedToInitCipher, e); + } + } + + } + + public class BiometricAuthCallbackAdapter : BiometricPrompt.AuthenticationCallback + { + private readonly IBiometricAuthCallback _callback; + private readonly Context _context; + private bool _ignoreNextError; + + public BiometricAuthCallbackAdapter(IBiometricAuthCallback callback, Context context) + { + _callback = callback; + _context = context; + } + + + public override void OnAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) + { + new Handler(Looper.MainLooper).Post(() => _callback.OnBiometricAuthSucceeded()); + } + + public override void OnAuthenticationError(int errorCode, ICharSequence errString) + { + if (_ignoreNextError) + { + _ignoreNextError = false; + return; + } + new Handler(Looper.MainLooper).Post(() => _callback.OnBiometricError(errString.ToString())); + } + + + public override void OnAuthenticationFailed() + { + new Handler(Looper.MainLooper).Post(() => _callback.OnBiometricAttemptFailed(_context.Resources.GetString(Resource.String.fingerprint_not_recognized))); + } + + public void IgnoreNextError() + { + _ignoreNextError = true; + } + } + +} \ No newline at end of file diff --git a/src/keepass2android-appSdkStyle/CancelDialog.cs b/src/keepass2android-appSdkStyle/CancelDialog.cs new file mode 100644 index 00000000..8053d5eb --- /dev/null +++ b/src/keepass2android-appSdkStyle/CancelDialog.cs @@ -0,0 +1,42 @@ +/* +This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. + + Keepass2Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Keepass2Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Keepass2Android. If not, see . + */ + +using Android.App; +using Android.Content; + +namespace keepass2android +{ + + public class CancelDialog : Dialog { + protected readonly Activity _activity; + + public CancelDialog(Activity activity): base(activity) + { + _activity = activity; + } + + public bool Canceled { get; private set; } + + + public override void Cancel() { + base.Cancel(); + Canceled = true; + } + } + +} + diff --git a/src/keepass2android-appSdkStyle/ChallengeInfo.cs b/src/keepass2android-appSdkStyle/ChallengeInfo.cs new file mode 100644 index 00000000..0251c7fe --- /dev/null +++ b/src/keepass2android-appSdkStyle/ChallengeInfo.cs @@ -0,0 +1,217 @@ +// +// ChallengeInfo.cs +// +// Author: +// Ben.Rush <> +// +// Copyright (c) 2014 Ben.Rush +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +using System; +using System.Xml; +using System.IO; +using keepass2android; +using KeePassLib.Serialization; +using System.Xml.Serialization; + +namespace KeeChallenge +{ + public class ChallengeInfo + { + private bool m_LT64; + + public byte[] EncryptedSecret { + get; + private set; + } + + public byte[] IV { + get; + private set; + } + + public byte[] Challenge { + get; + private set; + } + + public byte[] Verification { + get; + private set; + } + + public bool LT64 + { + get { return m_LT64; } + private set { m_LT64 = value; } + } + + private ChallengeInfo() + { + LT64 = false; + } + + public ChallengeInfo(byte[] encryptedSecret, byte[] iv, byte[] challenge, byte[] verification, bool lt64) + { + EncryptedSecret = encryptedSecret; + IV = iv; + Challenge = challenge; + Verification = verification; + LT64 = lt64; + } + + public static ChallengeInfo Load(IOConnectionInfo ioc) + { + Stream sIn = null; + ChallengeInfo inf = new ChallengeInfo(); + try + { + sIn = App.Kp2a.GetOtpAuxFileStorage(ioc).OpenFileForRead(ioc); + + if (!inf.LoadStream(sIn)) return null; + } + catch (Exception e) + { + Kp2aLog.LogUnexpectedError(e); + } + finally + { + if(sIn != null) sIn.Close(); + } + + return inf; + } + + private bool LoadStream(Stream AuxFile) + { + //read file + XmlReader xml; + try + { + XmlReaderSettings settings = new XmlReaderSettings(); + settings.CloseInput = true; + settings.XmlResolver = null; + xml = XmlReader.Create(AuxFile,settings); + } + catch (Exception) + { + return false; + } + + try + { + while (xml.Read()) + { + if (xml.IsStartElement()) + { + switch (xml.Name) + { + case "encrypted": + xml.Read(); + EncryptedSecret = Convert.FromBase64String(xml.Value.Trim()); + break; + case "iv": + xml.Read(); + IV = Convert.FromBase64String(xml.Value.Trim()); + break; + case "challenge": + xml.Read(); + Challenge = Convert.FromBase64String(xml.Value.Trim()); + break; + case "verification": + xml.Read(); + Verification = Convert.FromBase64String(xml.Value.Trim()); + break; + case "lt64": + xml.Read(); + if (!bool.TryParse(xml.Value.Trim(), out m_LT64)) throw new Exception("Unable to parse LT64 flag"); + break; + } + } + } + } + catch (Exception) + { + return false; + } + + xml.Close(); + //if failed, return false + return true; + } + + public bool Save(IOConnectionInfo ioc) + { + Stream sOut = null; + try + { + using (var trans = App.Kp2a.GetOtpAuxFileStorage(ioc) + .OpenWriteTransaction(ioc, App.Kp2a.GetBooleanPreference(PreferenceKey.UseFileTransactions))) + { + sOut = trans.OpenFile(); + if (SaveStream(sOut)) + { + trans.CommitWrite(); + } + else return false; + } + return true; + } + catch(Exception) { return false; } + finally + { + if (sOut != null) sOut.Close(); + } + } + + private bool SaveStream(Stream file) + { + try + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.CloseOutput = true; + settings.Indent = true; + settings.IndentChars = "\t"; + settings.NewLineOnAttributes = true; + + XmlWriter xml = XmlWriter.Create(file,settings); + xml.WriteStartDocument(); + xml.WriteStartElement("data"); + + xml.WriteStartElement("aes"); + xml.WriteElementString("encrypted", Convert.ToBase64String(EncryptedSecret)); + xml.WriteElementString("iv", Convert.ToBase64String(IV)); + xml.WriteEndElement(); + + xml.WriteElementString("challenge", Convert.ToBase64String(Challenge)); + xml.WriteElementString("verification", Convert.ToBase64String(Verification)); + xml.WriteElementString("lt64", LT64.ToString()); + + xml.WriteEndElement(); + xml.WriteEndDocument(); + xml.Close(); + } + catch (Exception) + { + return false; + } + + return true; + } + + } +} + diff --git a/src/keepass2android-appSdkStyle/ChallengeXCKey.cs b/src/keepass2android-appSdkStyle/ChallengeXCKey.cs new file mode 100644 index 00000000..8303cfe5 --- /dev/null +++ b/src/keepass2android-appSdkStyle/ChallengeXCKey.cs @@ -0,0 +1,95 @@ +using Java.Lang; +using KeePassLib.Cryptography; +using KeePassLib.Keys; +using KeePassLib.Security; +using KeePassLib.Serialization; +using Exception = System.Exception; + +namespace keepass2android +{ + public class ChallengeXCKey : IUserKey, ISeedBasedUserKey + { + private readonly int _requestCode; + + public ProtectedBinary KeyData + { + get + { + if (Activity == null) + throw new Exception("Need an active Keepass2Android activity to challenge Yubikey!"); + Activity.RunOnUiThread( + () => + { + byte[] challenge = _kdfSeed; + byte[] challenge64 = new byte[64]; + for (int i = 0; i < 64; i++) + { + if (i < challenge.Length) + { + challenge64[i] = challenge[i]; + } + else + { + challenge64[i] = (byte)(challenge64.Length - challenge.Length); + } + + } + var chalIntent = Activity.TryGetYubichallengeIntentOrPrompt(challenge64, true); + + if (chalIntent == null) + { + Error = Activity.GetString(Resource.String.NoChallengeApp); + } + else + { + Activity.StartActivityForResult(chalIntent, _requestCode); + } + + }); + while ((Response == null) && (Error == null)) + { + Thread.Sleep(50); + } + if (Error != null) + { + var error = Error; + Error = null; + throw new Exception("YubiChallenge failed: " + error); + } + + var result = CryptoUtil.HashSha256(Response); + Response = null; + return new ProtectedBinary(true, result); + } + } + + public uint GetMinKdbxVersion() + { + return KdbxFile.FileVersion32_4; + } + + private byte[] _kdfSeed; + + public ChallengeXCKey(LockingActivity activity, int requestCode) + { + this.Activity = activity; + _requestCode = requestCode; + Response = null; + } + + public void SetParams(byte[] masterSeed, byte[] mPbKdfSeed) + { + _kdfSeed = mPbKdfSeed; + } + + public byte[] Response { get; set; } + + public string Error { get; set; } + + public LockingActivity Activity + { + get; + set; + } + } +} \ No newline at end of file diff --git a/src/keepass2android-appSdkStyle/ChangeLog.cs b/src/keepass2android-appSdkStyle/ChangeLog.cs new file mode 100644 index 00000000..807b4ccb --- /dev/null +++ b/src/keepass2android-appSdkStyle/ChangeLog.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Android.App; +using Android.Content; +using Android.Content.PM; +using Android.Content.Res; +using Android.Graphics; +using Android.OS; +using Android.Preferences; +using Android.Runtime; +using Android.Text; +using Android.Text.Method; +using Android.Text.Util; +using Android.Views; +using Android.Webkit; +using Android.Widget; + +namespace keepass2android +{ + public static class ChangeLog + { + public static void ShowChangeLog(Context ctx, Action onDismiss) + { + AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(ctx, Android.Resource.Style.ThemeHoloLightDialog)); + builder.SetTitle(ctx.GetString(Resource.String.ChangeLog_title)); + List changeLog = new List{ + BuildChangelogString(ctx, new List{Resource.Array.ChangeLog_1_11,Resource.Array.ChangeLog_1_11_net}, "1.11"), + BuildChangelogString(ctx, Resource.Array.ChangeLog_1_10, "1.10"), + BuildChangelogString(ctx, Resource.Array.ChangeLog_1_09e, "1.09e"), + BuildChangelogString(ctx, Resource.Array.ChangeLog_1_09d, "1.09d"), + BuildChangelogString(ctx, Resource.Array.ChangeLog_1_09c, "1.09c"), + BuildChangelogString(ctx, Resource.Array.ChangeLog_1_09b, "1.09b"), + BuildChangelogString(ctx, Resource.Array.ChangeLog_1_09a, "1.09a"), + BuildChangelogString(ctx, Resource.Array.ChangeLog_1_08d, "1.08d"), + BuildChangelogString(ctx, Resource.Array.ChangeLog_1_08c, "1.08c"), + BuildChangelogString(ctx, Resource.Array.ChangeLog_1_08b, "1.08b"), + BuildChangelogString(ctx, Resource.Array.ChangeLog_1_08, "1.08"), + ctx.GetString(Resource.String.ChangeLog_1_07b), + ctx.GetString(Resource.String.ChangeLog_1_07), + ctx.GetString(Resource.String.ChangeLog_1_06), + ctx.GetString(Resource.String.ChangeLog_1_05), + ctx.GetString(Resource.String.ChangeLog_1_04b), + ctx.GetString(Resource.String.ChangeLog_1_04), + ctx.GetString(Resource.String.ChangeLog_1_03), + ctx.GetString(Resource.String.ChangeLog_1_02), +#if !NoNet + ctx.GetString(Resource.String.ChangeLog_1_01g), + ctx.GetString(Resource.String.ChangeLog_1_01d), +#endif + ctx.GetString(Resource.String.ChangeLog_1_01), + ctx.GetString(Resource.String.ChangeLog_1_0_0e), + ctx.GetString(Resource.String.ChangeLog_1_0_0), + ctx.GetString(Resource.String.ChangeLog_0_9_9c), + ctx.GetString(Resource.String.ChangeLog_0_9_9), + ctx.GetString(Resource.String.ChangeLog_0_9_8c), + ctx.GetString(Resource.String.ChangeLog_0_9_8b), + ctx.GetString(Resource.String.ChangeLog_0_9_8), +#if !NoNet + //0.9.7b fixes were already included in 0.9.7 offline + ctx.GetString(Resource.String.ChangeLog_0_9_7b), +#endif + ctx.GetString(Resource.String.ChangeLog_0_9_7), + ctx.GetString(Resource.String.ChangeLog_0_9_6), + ctx.GetString(Resource.String.ChangeLog_0_9_5), + ctx.GetString(Resource.String.ChangeLog_0_9_4), + ctx.GetString(Resource.String.ChangeLog_0_9_3_r5), + ctx.GetString(Resource.String.ChangeLog_0_9_3), + ctx.GetString(Resource.String.ChangeLog_0_9_2), + ctx.GetString(Resource.String.ChangeLog_0_9_1), + ctx.GetString(Resource.String.ChangeLog_0_9), + ctx.GetString(Resource.String.ChangeLog_0_8_6), + ctx.GetString(Resource.String.ChangeLog_0_8_5), + ctx.GetString(Resource.String.ChangeLog_0_8_4), + ctx.GetString(Resource.String.ChangeLog_0_8_3), + ctx.GetString(Resource.String.ChangeLog_0_8_2), + ctx.GetString(Resource.String.ChangeLog_0_8_1), + ctx.GetString(Resource.String.ChangeLog_0_8), + ctx.GetString(Resource.String.ChangeLog_0_7), + ctx.GetString(Resource.String.ChangeLog) + }; + + String version; + try { + PackageInfo packageInfo = ctx.PackageManager.GetPackageInfo(ctx.PackageName, 0); + version = packageInfo.VersionName; + + } catch (PackageManager.NameNotFoundException) { + version = ""; + } + + string warning = ""; + if (version.Contains("pre")) + { + warning = ctx.GetString(Resource.String.PreviewWarning); + } + + builder.SetPositiveButton(Android.Resource.String.Ok, (dlgSender, dlgEvt) => {((AlertDialog)dlgSender).Dismiss(); }); + builder.SetCancelable(false); + + WebView wv = new WebView(ctx); + + wv.SetBackgroundColor(Color.White); + wv.LoadDataWithBaseURL(null, GetLog(changeLog, warning, ctx), "text/html", "UTF-8", null); + + + //builder.SetMessage(""); + builder.SetView(wv); + Dialog dialog = builder.Create(); + dialog.DismissEvent += (sender, e) => + { + onDismiss(); + }; + dialog.Show(); + /*TextView message = (TextView)dialog.FindViewById(Android.Resource.Id.Message); + + + message.TextFormatted = Html.FromHtml(ConcatChangeLog(ctx, changeLog.ToArray())); + message.AutoLinkMask=MatchOptions.WebUrls;*/ + + } + + private static string BuildChangelogString(Context ctx, int changeLogResId, string version) + { + return BuildChangelogString(ctx, new List() { changeLogResId }, version); + + } + + + private static string BuildChangelogString(Context ctx, List changeLogResIds, string version) + { + string result = "Version " + version + "\n"; + string previous = ""; + foreach (var changeLogResId in changeLogResIds) + { + foreach (var item in ctx.Resources.GetStringArray(changeLogResId)) + { + if (item == previous) //there was some trouble with crowdin translations, remove duplicates + continue; + result += " * " + item + "\n"; + previous = item; + } + } + + return result; + + } + + private const string HtmlStart = @" + + + + "; + private const string HtmlEnd = @" +"; + private static string GetLog(List changeLog, string warning, Context ctx) + { + StringBuilder sb = new StringBuilder(HtmlStart); + if (!string.IsNullOrEmpty(warning)) + { + sb.Append(warning); + } + bool inList = false; + bool isFirst = true; + foreach (string versionLog in changeLog) + { + string versionLog2 = versionLog; + bool title = true; + if (isFirst) + { + + bool showDonateOption = true; + ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(ctx); + if (prefs.GetBoolean(ctx.GetString(Resource.String.NoDonationReminder_key), false)) + showDonateOption = false; + + long usageCount = prefs.GetLong(ctx.GetString(Resource.String.UsageCount_key), 0); + + if (usageCount <= 5) + showDonateOption = false; + + if (showDonateOption) + { + if (versionLog2.EndsWith("\n") == false) + versionLog2 += "\n"; + string donateUrl = ctx.GetString(Resource.String.donate_url, + new Java.Lang.Object[]{ctx.Resources.Configuration.Locale.Language, + ctx.PackageName + }); + + versionLog2 += " * " + + ctx.GetString(Resource.String.ChangeLog_keptDonate) + + ""; + } + isFirst = false; + } + foreach (string line in versionLog2.Split('\n')) + { + string w = line.Trim(); + if (title) + { + if (inList) + { + sb.Append("\n"); + inList = false; + } + w = w.Replace("",""); + w = w.Replace("", ""); + w = w.Replace("\\n", ""); + sb.Append("
" + + w.Trim() + "
\n"); + title = false; + } + else + { + w = w.Replace("\\n", "
"); + if ((w.StartsWith("*") || (w.StartsWith("")))) + { + if (!inList) + { + sb.Append("
    \n"); + inList = true; + } + sb.Append("
  • "); + sb.Append(w.Substring(1).Trim()); + sb.Append("
  • \n"); + } + else + { + if (inList) + { + sb.Append("
\n"); + inList = false; + } + sb.Append(w); + } + } + } + } + sb.Append(HtmlEnd); + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/src/keepass2android-appSdkStyle/CloseImmediatelyActivity.cs b/src/keepass2android-appSdkStyle/CloseImmediatelyActivity.cs new file mode 100644 index 00000000..1d7baac1 --- /dev/null +++ b/src/keepass2android-appSdkStyle/CloseImmediatelyActivity.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Android.App; +using Android.Content; +using Android.Content.PM; +using Android.OS; +using Android.Runtime; +using Android.Support.V7.App; +using Android.Views; +using Android.Widget; + +namespace keepass2android +{ + [Activity(Label = AppNames.AppName, Theme = "@style/MyTheme_ActionBar", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden)] + public class CloseImmediatelyActivity : AndroidX.AppCompat.App.AppCompatActivity + { + protected override void OnResume() + { + SetContentView(Resource.Layout.group); + base.OnResume(); + SetResult(Result.Ok); + FindViewById(Resource.Id.bottom_bar).PostDelayed(() => + { + Finish(); + OverridePendingTransition(0, 0); + }, 200); + } + } +} \ No newline at end of file diff --git a/src/keepass2android-appSdkStyle/ConfigureChildDatabasesActivity.cs b/src/keepass2android-appSdkStyle/ConfigureChildDatabasesActivity.cs new file mode 100644 index 00000000..293e9f86 --- /dev/null +++ b/src/keepass2android-appSdkStyle/ConfigureChildDatabasesActivity.cs @@ -0,0 +1,435 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; + +using Android.App; +using Android.Content; +using Android.Content.PM; +using Android.Graphics; +using Android.Graphics.Drawables; +using Android.OS; +using Android.Provider; +using Android.Runtime; +using Android.Support.V7.App; +using Android.Text; +using Android.Util; +using Android.Views; +using Android.Widget; +using keepass2android.database.edit; +using KeePass.Util.Spr; +using KeePassLib; +using KeePassLib.Keys; +using KeePassLib.Security; +using KeePassLib.Serialization; +using AlertDialog = Android.App.AlertDialog; +using Object = Java.Lang.Object; + +namespace keepass2android +{ + [Activity(Label = "@string/child_dbs_title", MainLauncher = false, Theme = "@style/MyTheme_Blue", LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden, Exported = true)] + [IntentFilter(new[] { "kp2a.action.ConfigureChildDatabasesActivity" }, Categories = new[] { Intent.CategoryDefault })] + public class ConfigureChildDatabasesActivity : LockCloseActivity + { + private ChildDatabasesAdapter _adapter; + + public class ChildDatabasesAdapter : BaseAdapter + { + + private readonly ConfigureChildDatabasesActivity _context; + internal List _displayedChildDatabases; + + public ChildDatabasesAdapter(ConfigureChildDatabasesActivity context) + { + _context = context; + Update(); + + } + + public override Object GetItem(int position) + { + return position; + } + + public override long GetItemId(int position) + { + return position; + } + + + + private LayoutInflater cursorInflater; + + public override View GetView(int position, View convertView, ViewGroup parent) + { + if (cursorInflater == null) + cursorInflater = (LayoutInflater)_context.GetSystemService(Context.LayoutInflaterService); + + View view; + + if (convertView == null) + { + // if it's not recycled, initialize some attributes + + view = cursorInflater.Inflate(Resource.Layout.child_db_config_row, parent, false); + + + view.FindViewById