Compare commits
	
		
			204 Commits
		
	
	
		
			2818-feat-
			...
			feature/sc
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 9be215c295 | ||
|   | bb97a023de | ||
|   | edb4907bf5 | ||
|   | a718c7ed7e | ||
|   | 4f11789f26 | ||
|   | eb15861b13 | ||
|   | 8c2c8049c8 | ||
|   | 2f3761b0a7 | ||
|   | 260bc8adb2 | ||
|   | 87e979635b | ||
|   | 0c9c163755 | ||
|   | 74ceea562b | ||
|   | c6a5362ecb | ||
|   | f655a89be0 | ||
|   | 0d6f837578 | ||
|   | 8c61b028b7 | ||
|   | a3d5273285 | ||
|   | 93cf4f790c | ||
|   | cd323c0a22 | ||
|   | 99ca8bf953 | ||
|   | d40b3dc15c | ||
|   | 057a7e2f7a | ||
|   | 1b73c536d5 | ||
|   | 2593a8548f | ||
|   | 13306a9076 | ||
|   | cfb5098b38 | ||
|   | d04d455fbd | ||
|   | b83c4b3772 | ||
|   | f03c11381e | ||
|   | 8a03ddb7f3 | ||
|   | 913222d7cb | ||
|   | 3e6d86c206 | ||
|   | d6ce2a32e9 | ||
|   | 21f1c8404c | ||
|   | 16ff81cf81 | ||
|   | 0636f687ac | ||
|   | 60d8900473 | ||
|   | 4b2d2ef768 | ||
|   | 48899ba9a0 | ||
|   | 092b8689b8 | ||
|   | 3d3ba45cb1 | ||
|   | b380100307 | ||
|   | 4c5ddd59d8 | ||
|   | 5ed183f318 | ||
|   | 9c27fd3e78 | ||
|   | 62c361feb0 | ||
|   | 0ee2495528 | ||
|   | 7dc635a625 | ||
|   | e15112c3b4 | ||
|   | 1d85fffb18 | ||
|   | 5e2f29e737 | ||
|   | 89a09ea142 | ||
|   | 628c0d2c19 | ||
|   | 584feabe44 | ||
|   | ae2cfde897 | ||
|   | 42c66670b8 | ||
|   | 288539b902 | ||
|   | 61fd32f121 | ||
|   | 43108ec4a6 | ||
|   | 51089c6b98 | ||
|   | cf18fcf91c | ||
|   | da0513c768 | ||
|   | 37f520cdbe | ||
|   | c98572bee0 | ||
|   | b1774ffc4b | ||
|   | 57aaa0c4cd | ||
|   | b961ae1b33 | ||
|   | 5e418e2b1b | ||
|   | 6d22a213f3 | ||
|   | a76addc43f | ||
|   | 1d96217713 | ||
|   | d2b8fdcfff | ||
|   | 426fbc2510 | ||
|   | e89a961c02 | ||
|   | 766c29b7a9 | ||
|   | 507b671448 | ||
|   | 3118ffaeb5 | ||
|   | 0abe29bd77 | ||
|   | f3a7831390 | ||
|   | 37cd58f7ba | ||
|   | 7dd80a8ef7 | ||
|   | c78636264b | ||
|   | 035506a5a3 | ||
|   | 4cf46ef062 | ||
|   | c7b8063171 | ||
|   | 0a8b149c9a | ||
|   | 9240a27791 | ||
|   | e90d5b903c | ||
|   | 50b4a9f1b9 | ||
|   | 9783c3b5fe | ||
|   | 7a837e3237 | ||
|   | c8f6714373 | ||
|   | bc0313aa6a | ||
|   | 0f98668bcd | ||
|   | c4206e58bf | ||
|   | 630ededf3b | ||
|   | 8d1195ac96 | ||
|   | df731ac1b3 | ||
|   | bd784fa13d | ||
|   | a6bc5e657c | ||
|   | 56c4cdb321 | ||
|   | 42a4a83c7d | ||
|   | fe3933e154 | ||
|   | 3efe130ee8 | ||
|   | 5808857749 | ||
|   | a7397c3316 | ||
|   | d12f936898 | ||
|   | 67f7d74bb9 | ||
|   | b0d0f06073 | ||
|   | 67ee571c27 | ||
|   | a360695271 | ||
|   | 149857a516 | ||
|   | fb8ffb802f | ||
|   | e957073457 | ||
|   | da86b0f50b | ||
|   | 0aa78ffd66 | ||
|   | ce0087af99 | ||
|   | 576bfeecfe | ||
|   | c0e2f34b79 | ||
|   | 84d0c32610 | ||
|   | 40184dbd55 | ||
|   | 2d17bdde19 | ||
|   | e2babde1fa | ||
|   | e1f26fb045 | ||
|   | adbbfa0ac1 | ||
|   | 37a013135e | ||
|   | acc6ea7f85 | ||
|   | 62f012713a | ||
|   | 0d726c1789 | ||
|   | f162e868b9 | ||
|   | 0a1f95653f | ||
|   | 27451825c6 | ||
|   | bdd6f1033e | ||
|   | c0413f9b74 | ||
|   | 59d6fc8fdb | ||
|   | c500245647 | ||
|   | 026a263f10 | ||
|   | 839e6d3cb4 | ||
|   | 735f4caf89 | ||
|   | 11af71ef82 | ||
|   | e09577d17f | ||
|   | c1dbf171f5 | ||
|   | 4ca4ec10be | ||
|   | 77fded4964 | ||
|   | 578491b1c7 | ||
|   | eee3ffd861 | ||
|   | 89696d7f0d | ||
|   | e5d28f0979 | ||
|   | 0e581a66c5 | ||
|   | a202c76bf0 | ||
|   | c9936ab76b | ||
|   | 7ac6f7ed51 | ||
|   | ceb31c54b1 | ||
|   | 42d8be593e | ||
|   | 313adb6c3e | ||
|   | 668ba4cdee | ||
|   | a36bfa7ff5 | ||
|   | 26c37bcd2a | ||
|   | 1980f05a7c | ||
|   | dbf10ba9fb | ||
|   | 4be18d8373 | ||
|   | 831b290d81 | ||
|   | 9d4c15f7bc | ||
|   | 4c4afa792d | ||
|   | 8e256ac94d | ||
|   | ba7b02cd1e | ||
|   | 65ff09f866 | ||
|   | aec9441de4 | ||
|   | 8e9c2824cf | ||
|   | 92b8ff5c8d | ||
|   | 223c3bfb8e | ||
|   | b4e03a8374 | ||
|   | fb2df35d37 | ||
|   | 50d6598b02 | ||
|   | 90f04b76f4 | ||
|   | 8b4314c394 | ||
|   | 17241bc422 | ||
|   | c4a73bf107 | ||
|   | 5edf42254d | ||
|   | 4ba40ba24f | ||
|   | e2711b709d | ||
|   | 4764b15e75 | ||
|   | 1b389ef12e | ||
|   | b32c2dbc7e | ||
|   | f06937dab3 | ||
|   | 14efce62ff | ||
|   | 3c8b530e2e | ||
|   | 9939e07b7d | ||
|   | ecf416febc | ||
|   | 1cb036941e | ||
|   | a53ff37e89 | ||
|   | dc3ee35c8b | ||
|   | e05fe94650 | ||
|   | b0cb0b06a2 | ||
|   | 6d7b4810da | ||
|   | 585b747612 | ||
|   | 55887e1a89 | ||
|   | 39a7a1298a | ||
|   | 90059c5ae6 | ||
|   | ad63179484 | ||
|   | 6eaba9d3a8 | ||
|   | 11ce68902c | ||
|   | 70ca059e0f | ||
|   | a51bfb102f | 
							
								
								
									
										45
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										45
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -78,7 +78,7 @@ jobs: | ||||
|  | ||||
|   #   - name: Build keepass2android (net) | ||||
|   #     run: | | ||||
|   #       make msbuild Flavor=Net | ||||
|   #       make dotnetbuild Flavor=Net | ||||
|  | ||||
|   #   - name: Build APK (net) | ||||
|   #     run: | | ||||
| @@ -96,7 +96,7 @@ jobs: | ||||
|  | ||||
|   #   - name: Build keepass2android (nonet) | ||||
|   #     run: | | ||||
|   #       make msbuild Flavor=NoNet | ||||
|   #       make dotnetbuild Flavor=NoNet | ||||
|  | ||||
|   #   - name: Build APK (nonet) | ||||
|   #     run: | | ||||
| @@ -212,7 +212,7 @@ jobs: | ||||
|  | ||||
|   #   - name: Build keepass2android (net) | ||||
|   #     run: | | ||||
|   #       make msbuild Flavor=Net | ||||
|   #       make dotnetbuild Flavor=Net | ||||
|  | ||||
|   #   - name: Build APK (net) | ||||
|   #     run: | | ||||
| @@ -230,7 +230,7 @@ jobs: | ||||
|  | ||||
|   #   - name: Build keepass2android (nonet) | ||||
|   #     run: | | ||||
|   #       make msbuild Flavor=NoNet | ||||
|   #       make dotnetbuild Flavor=NoNet | ||||
|  | ||||
|   #   - name: Build APK (nonet) | ||||
|   #     run: | | ||||
| @@ -279,7 +279,7 @@ jobs: | ||||
|       with: | ||||
|         minimum-size: 8GB | ||||
|  | ||||
|     - name: Add msbuild to PATH | ||||
|     - name: Add dotnetbuild to PATH | ||||
|       uses: microsoft/setup-msbuild@v2 | ||||
|       # If we want to also have nmake, use this instead | ||||
|       #uses: ilammy/msvc-dev-cmd@v1 | ||||
| @@ -309,30 +309,49 @@ jobs: | ||||
|       run: | | ||||
|         make java | ||||
|  | ||||
|     - name: Update dotnet workloads | ||||
|       run: | | ||||
|         dotnet workload update         | ||||
|  | ||||
|     - name: Select the manifest | ||||
|       run: | | ||||
|         make manifestlink Flavor=Net | ||||
|  | ||||
|     - name: Install NuGet dependencies (net) | ||||
|       run: make nuget Flavor=Net | ||||
|  | ||||
|     - name: Build keepass2android (net) | ||||
|       run: | | ||||
|         make msbuild Flavor=Net | ||||
|         make dotnetbuild Flavor=Net           | ||||
|  | ||||
|     - name: Build APK (net) | ||||
|       if: github.ref == 'refs/heads/master' | ||||
|       env: | ||||
|         DropboxAppKey: ${{ secrets.DROPBOX_APP_KEY }} | ||||
|         DropboxAppSecret: ${{ secrets.DROPBOX_APP_SECRET }} | ||||
|         DropboxAppFolderAppKey: ${{ secrets.DROPBOX_APP_FOLDER_APP_KEY }} | ||||
|         DropboxAppFolderAppSecret: ${{ secrets.DROPBOX_APP_FOLDER_APP_SECRET }} | ||||
|       run: | | ||||
|         make apk Flavor=Net | ||||
|         make apk Configuration=Release Flavor=Net | ||||
|  | ||||
|     - name: Archive production artifacts (net) | ||||
|       uses: actions/upload-artifact@v4 | ||||
|       with: | ||||
|         name: signed APK ('net' built on ${{ github.job }}) | ||||
|         name: archive APK ('net' built on ${{ github.job }}) | ||||
|         path: | | ||||
|           src/keepass2android/bin/*/*-Signed.apk | ||||
|           src/keepass2android-app/bin/Release/net9.0-android/publish/*.apk | ||||
|  | ||||
|     - name: Select the manifest | ||||
|       run: | | ||||
|         make manifestlink Flavor=NoNet | ||||
|  | ||||
|     - name: Install NuGet dependencies (nonet) | ||||
|       run: make nuget Flavor=NoNet | ||||
|  | ||||
|     - name: Build keepass2android (nonet) | ||||
|       run: | | ||||
|         make msbuild Flavor=NoNet | ||||
|         make dotnetbuild Flavor=NoNet | ||||
|  | ||||
|     - name: Test Autofill | ||||
|       working-directory: ./src/Kp2aAutofillParser.Tests | ||||
|       run: dotnet test | ||||
| @@ -344,9 +363,7 @@ jobs: | ||||
|     - name: Archive production artifacts (nonet) | ||||
|       uses: actions/upload-artifact@v4 | ||||
|       with: | ||||
|         name: signed APK ('nonet' built on ${{ github.job }}) | ||||
|         name: archive APK ('nonet' built on ${{ github.job }}) | ||||
|         path: | | ||||
|           src/keepass2android/bin/*/*-Signed.apk | ||||
|           src/keepass2android-app/bin/Release/net9.0-android/publish/*.apk | ||||
|  | ||||
|     - name: Perform "make distclean" | ||||
|       run: make distclean | ||||
|   | ||||
							
								
								
									
										146
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,146 @@ | ||||
| name: Create keepass2android release | ||||
| env: | ||||
|   NAME: 'Release' | ||||
|  | ||||
| on: | ||||
|   # the workflow is always triggered manually. This allows to test the apks | ||||
|   # before publishing the release and not having a broken tag in the repo if that test fails. | ||||
|   workflow_dispatch:  | ||||
| jobs: | ||||
|  | ||||
|   build-release: | ||||
|  | ||||
|     runs-on: windows-2022 | ||||
|  | ||||
|     strategy: | ||||
|       matrix: | ||||
|         flavor: [Net, NoNet] | ||||
|         target: [apk, apk_split] | ||||
|  | ||||
|     steps: | ||||
|      | ||||
|     - uses: actions/checkout@v4 | ||||
|       with: | ||||
|         submodules: true | ||||
|  | ||||
|     - name: Extract key store | ||||
|       env:  | ||||
|         KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} | ||||
|         KeyStore: "${{ github.workspace }}/kp2a.keystore" | ||||
|          | ||||
|       shell: bash | ||||
|       run: | | ||||
|         echo $KeyStore | ||||
|         echo $KEYSTORE_BASE64 | base64 --decode > $KeyStore | ||||
|  | ||||
|          | ||||
|  | ||||
|     - name: Setup Gradle | ||||
|       uses: gradle/actions/setup-gradle@v3 | ||||
|  | ||||
|     - name: Cache NuGet packages | ||||
|       uses: actions/cache@v4 | ||||
|       with: | ||||
|         path: ~/.nuget/packages | ||||
|         key: ${{ runner.os }}-nuget-${{ hashFiles('src/**/*.csproj', 'src/**/packages.config') }} | ||||
|         restore-keys: | | ||||
|           ${{ runner.os }}-nuget- | ||||
|  | ||||
|     # Workaround an issue when building on windows-2022. Error was | ||||
|     #       D8 : OpenJDK 64-Bit Server VM warning : INFO: os::commit_memory(0x00000000ae400000, 330301440, 0) failed; error='The paging file is too small for this operation to complete' (DOS error/errno=1455) [D:\a\keepass2android\keepass2android\src\keepass2android\keepass2android-app.csproj] | ||||
|     #       C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Xamarin\Android\Xamarin.Android.D8.targets(81,5): error MSB6006: "java.exe" exited with code 1. [D:\a\keepass2android\keepass2android\src\keepass2android\keepass2android-app.csproj] | ||||
|     - name: Configure Pagefile | ||||
|       uses: al-cheb/configure-pagefile-action@a3b6ebd6b634da88790d9c58d4b37a7f4a7b8708 # v1.4 | ||||
|       with: | ||||
|         minimum-size: 8GB | ||||
|  | ||||
|     - name: Add msbuild/dotnet to PATH | ||||
|       uses: microsoft/setup-msbuild@v2 | ||||
|       # If we want to also have nmake, use this instead | ||||
|       #uses: ilammy/msvc-dev-cmd@v1 | ||||
|  | ||||
|     - name: Switch to JDK-17 | ||||
|       uses: actions/setup-java@v4 | ||||
|       with: | ||||
|         java-version: '17' | ||||
|         distribution: 'temurin' | ||||
|  | ||||
|     - name: Display java version | ||||
|       run: java -version | ||||
|  | ||||
|     - name: Build native dependencies | ||||
|       shell: cmd | ||||
|       run: | | ||||
|         make native | ||||
|  | ||||
|     - name: Build java dependencies | ||||
|       shell: cmd | ||||
|       run: | | ||||
|         make java | ||||
|  | ||||
|     - name: List apks | ||||
|       run:  find . -type f -name "*.apk" | ||||
|       shell: bash | ||||
|  | ||||
|     - name: Update dotnet workloads | ||||
|       run: | | ||||
|         dotnet workload update         | ||||
|  | ||||
|     - name: List apks | ||||
|       run:  find . -type f -name "*.apk" | ||||
|       shell: bash | ||||
|  | ||||
|  | ||||
|     - name: Select the manifest | ||||
|       run: | | ||||
|         make manifestlink Flavor=${{ matrix.flavor }} | ||||
|      | ||||
|     - name: List apks | ||||
|       run:  find . -type f -name "*.apk" | ||||
|       shell: bash | ||||
|  | ||||
|  | ||||
|     - name: Install NuGet dependencies | ||||
|       run: make nuget Flavor=${{ matrix.flavor }} | ||||
|  | ||||
|     - name: List apks | ||||
|       run:  find . -type f -name "*.apk" | ||||
|       shell: bash | ||||
|  | ||||
|     - name: Build APK (net) | ||||
|       env: | ||||
|         KeyStore: "${{ github.workspace }}/kp2a.keystore" | ||||
|         MyAndroidSigningStorePass: ${{ secrets.KEY_STORE_PASSWORD }} | ||||
|         MyAndroidSigningKeyPass: ${{ secrets.KEY_PASSWORD }} | ||||
|         DropboxAppKey: ${{ secrets.DROPBOX_APP_KEY }} | ||||
|         DropboxAppSecret: ${{ secrets.DROPBOX_APP_SECRET }} | ||||
|         DropboxAppFolderAppKey: ${{ secrets.DROPBOX_APP_FOLDER_APP_KEY }} | ||||
|         DropboxAppFolderAppSecret: ${{ secrets.DROPBOX_APP_FOLDER_APP_SECRET }} | ||||
|  | ||||
|       run: | | ||||
|         make ${{ matrix.target }} Configuration=Release Flavor=${{ matrix.flavor }} | ||||
|      | ||||
|     - name: List apks | ||||
|       run:  find . -type f -name "*.apk" | ||||
|       shell: bash | ||||
|      | ||||
|     - name: Archive production artifacts | ||||
|       uses: actions/upload-artifact@v4 | ||||
|       with: | ||||
|         name: keepass2android_${{ matrix.target }}_${{ matrix.flavor }} | ||||
|         # the first line is for "apk" target, the second line is for "apk_split" target | ||||
|         path: | | ||||
|           src/keepass2android-app/bin/Release/net9.0-android/publish/*.apk | ||||
|           src/keepass2android-app/bin/Release/net9.0-android/*/publish/*.apk | ||||
|      | ||||
|     - name: List apks | ||||
|       run:  find . -type f -name "*.apk" | ||||
|       shell: bash | ||||
|        | ||||
|     - name: Upload APK to GitHub Release | ||||
|       uses: softprops/action-gh-release@v2 | ||||
|       with: | ||||
|         draft: true | ||||
|         files: | | ||||
|           src/keepass2android-app/bin/Release/net9.0-android/publish/*.apk | ||||
|           src/keepass2android-app/bin/Release/net9.0-android/*/publish/*.apk | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -64,7 +64,7 @@ Thumbs.db | ||||
| /src/java/android-filechooser/code/projectzip/project.zip | ||||
| /src/java/android-filechooser/code/unused.txt | ||||
|  | ||||
| /src/Kp2aBusinessLogic/Io/DropboxFileStorageKeys.cs | ||||
| /src/Kp2aBusinessLogic/Io/DropboxFileStorage.g.cs | ||||
|  | ||||
| /src/java/workspace/DriveTest | ||||
|  | ||||
|   | ||||
							
								
								
									
										89
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										89
									
								
								Makefile
									
									
									
									
									
								
							| @@ -4,10 +4,10 @@ | ||||
| # This Makefile can be used on both unix-like (use make) & windows (with GNU make) | ||||
| # | ||||
| # append the Configuration variable to 'make' call with value to use in '/p:Configuration=' | ||||
| # of msbuild command. | ||||
| # of dotnetbuild command. | ||||
| # | ||||
| # append the Flavor variable to 'make' call with value to use in '/p:Flavor=' | ||||
| # of msbuild command. | ||||
| # of dotnetbuild command. | ||||
| # | ||||
| # Example: | ||||
| #    make Configuration=Release Flavor=NoNet | ||||
| @@ -18,7 +18,7 @@ | ||||
| #  - native: build the native libs | ||||
| #  - java: build the java libs | ||||
| #  - nuget: restore NuGet packages | ||||
| #  - msbuild: build the project | ||||
| #  - dotnetbuild: build the project | ||||
| #  - apk: same as all | ||||
| #  - manifestlink: creates a symlink (to be used in building) to the AndroidManifest corresponding to the selected Flavor | ||||
| # | ||||
| @@ -27,7 +27,7 @@ | ||||
| #  - clean_native: clean native lib | ||||
| #  - clean_java: call clean target of java libs | ||||
| #  - clean_nuget: cleanup the 'nuget restore' | ||||
| #  - clean_msbuild: call clean target of msbuild | ||||
| #  - clean_dotnet: call clean target of dotnetbuild | ||||
| # | ||||
| # | ||||
| # | ||||
| @@ -60,45 +60,23 @@ $(info MAKESHELL: $(MAKESHELL)) | ||||
| $(info SHELL: $(SHELL)) | ||||
| $(info ) | ||||
|  | ||||
| # On linux use xabuild, on Windows use MSBuild.exe, otherwise (macos?) use msbuild. | ||||
| ifeq ($(detected_OS),Linux) | ||||
|   MSBUILD_binary := xabuild | ||||
|   MSBUILD := $(shell $(WHICH) $(MSBUILD_binary)) | ||||
|   DOTNET_binary := dotnet | ||||
|   DOTNET := $(shell $(WHICH) $(DOTNET_binary)) | ||||
| else ifeq ($(detected_OS),Windows) | ||||
|   MSBUILD_binary := MSBuild.exe | ||||
|   MSBUILD := $(shell $(WHICH) $(MSBUILD_binary) 2> nul) | ||||
|   ifeq ($(MSBUILD),) | ||||
|     # Additional heuristic to find MSBUILD_BINARY on Windows | ||||
|     VSWHERE := "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" | ||||
|     VSWHERE_CHECK := $(shell @echo off & $(VSWHERE) 2> nul || echo VSWHERE_NOT_FOUND) | ||||
|     ifneq ($(VSWHERE_CHECK),VSWHERE_NOT_FOUND) | ||||
|       MSBUILD := $(shell @echo off & $(VSWHERE) -latest -prerelease -products * -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe) | ||||
|       VS_INSTALL_PATH := $(shell @echo off & $(VSWHERE) -property installationPath) | ||||
|     endif | ||||
|   endif | ||||
|   DOTNET_binary := dotnet | ||||
|   DOTNET := $(shell $(WHICH) $(DOTNET_binary) 2> nul) | ||||
| else | ||||
|   MSBUILD_binary := msbuild | ||||
|   MSBUILD := $(shell $(WHICH) $(MSBUILD_binary)) | ||||
|   DOTNET_binary := dotnet | ||||
|   DOTNET := $(shell $(WHICH) $(DOTNET_binary)) | ||||
| endif | ||||
|  | ||||
| ifeq ($(MSBUILD),) | ||||
| ifeq ($(DOTNET),) | ||||
|   $(info ) | ||||
|   $(info '$(MSBUILD_binary)' binary could not be found. Check it is in your PATH.) | ||||
|   ifeq ($(detected_OS),Windows) | ||||
|   ifneq ($(VSWHERE_CHECK),VSWHERE_NOT_FOUND) | ||||
|     $(info ) | ||||
|     $(info You may retry after running in the command prompt:) | ||||
|     $(info ) | ||||
|     $(info "$(VS_INSTALL_PATH)\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64) | ||||
|     $(info ) | ||||
|     $(info If this doesn't work, install/find the location of vcvarsall.bat) | ||||
|     $(info or install and add msbuild.exe to your PATH) | ||||
|     $(info ) | ||||
|   endif | ||||
|   endif | ||||
|   $(info '$(DOTNET_binary)' binary could not be found. Check it is in your PATH.) | ||||
|   $(error ) | ||||
| endif | ||||
| $(info MSBUILD: $(MSBUILD)) | ||||
| $(info DOTNET: $(DOTNET)) | ||||
| $(info ) | ||||
|  | ||||
| ifeq ($(ANDROID_SDK_ROOT),) | ||||
| @@ -117,7 +95,7 @@ endif | ||||
| $(info ANDROID_NDK_ROOT: $(ANDROID_NDK_ROOT)) | ||||
|  | ||||
| ifneq ($(Configuration),) | ||||
|   MSBUILD_PARAM = -p:Configuration="$(Configuration)" | ||||
|   DOTNET_PARAM = -p:Configuration="$(Configuration)" | ||||
| else | ||||
|   $(warning Configuration environment variable not set.) | ||||
| endif | ||||
| @@ -127,7 +105,7 @@ CREATE_MANIFEST_LINK := | ||||
|  | ||||
| MANIFEST_FILE :=  | ||||
| ifneq ($(Flavor),) | ||||
|   MSBUILD_PARAM += -p:Flavor="$(Flavor)" | ||||
|   DOTNET_PARAM += -p:Flavor="$(Flavor)" | ||||
|   ifneq ($(Flavor),) | ||||
| 		ifeq ($(Flavor),Debug) | ||||
| 			MANIFEST_FILE := AndroidManifest_debug.xml | ||||
| @@ -152,7 +130,7 @@ else | ||||
| endif | ||||
|  | ||||
| ifneq ($(KeyStore),) | ||||
|   MSBUILD_PARAM += -p:AndroidKeyStore=True -p:AndroidSigningKeyStore="$(KeyStore)" -p:AndroidSigningStorePass=env:MyAndroidSigningStorePass -p:AndroidSigningKeyPass=env:MyAndroidSigningKeyPass -p:AndroidSigningKeyAlias="kp2a" | ||||
|   DOTNET_PARAM += -p:AndroidKeyStore=True -p:AndroidSigningKeyStore="$(KeyStore)" -p:AndroidSigningStorePass=env:MyAndroidSigningStorePass -p:AndroidSigningKeyPass=env:MyAndroidSigningKeyPass -p:AndroidSigningKeyAlias="kp2a" | ||||
| endif | ||||
|  | ||||
| ifeq ($(detected_OS),Windows) | ||||
| @@ -176,7 +154,7 @@ endif | ||||
| # Recursive wildcard: https://stackoverflow.com/a/18258352 | ||||
| rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d)) | ||||
|  | ||||
| $(info MSBUILD_PARAM: $(MSBUILD_PARAM)) | ||||
| $(info DOTNET_PARAM: $(DOTNET_PARAM)) | ||||
| $(info nuget path: $(shell $(WHICH) nuget)) | ||||
| $(info ) | ||||
|  | ||||
| @@ -254,7 +232,7 @@ OUTPUT_PluginQR = src/java/Keepass2AndroidPluginSDK2/app/build/outputs/aar/Keepa | ||||
| .PHONY: native $(NATIVE_COMPONENTS) clean_native $(NATIVE_CLEAN_TARGETS) \ | ||||
| 	java $(JAVA_COMPONENTS) clean_java $(JAVA_CLEAN_TARGETS) \ | ||||
| 	nuget clean_nuget \ | ||||
| 	msbuild clean_msbuild \ | ||||
| 	dotnetbuild clean_dotnet \ | ||||
| 	apk all clean | ||||
|  | ||||
| all: apk | ||||
| @@ -303,7 +281,7 @@ ifeq ($(shell $(WHICH) nuget),) | ||||
| endif | ||||
| 	$(RMFILE) stamp.nuget_* | ||||
| 	nuget restore src/KeePass.sln | ||||
| 	$(MSBUILD) src/KeePass.sln -t:restore $(MSBUILD_PARAM) -p:RestorePackagesConfig=true | ||||
| 	$(DOTNET) restore src/KeePass.sln $(DOTNET_PARAM) -p:RestorePackagesConfig=true | ||||
| 	@echo "" > stamp.nuget_$(Flavor) | ||||
|  | ||||
| manifestlink: | ||||
| @@ -312,20 +290,21 @@ manifestlink: | ||||
| 	$(CREATE_MANIFEST_LINK)	 | ||||
|  | ||||
| ##### | ||||
| src/Kp2aBusinessLogic/Io/DropboxFileStorageKeys.cs: | ||||
| ifeq ($(detected_OS),Windows) | ||||
| 	$(CP) src\Kp2aBusinessLogic\Io\DropboxFileStorageKeysDummy.cs src\Kp2aBusinessLogic\Io\DropboxFileStorageKeys.cs | ||||
| else | ||||
| 	$(CP) src/Kp2aBusinessLogic/Io/DropboxFileStorageKeysDummy.cs $@ | ||||
| endif | ||||
|  | ||||
| msbuild: manifestlink native java nuget src/Kp2aBusinessLogic/Io/DropboxFileStorageKeys.cs | ||||
| 	$(MSBUILD) src/KeePass.sln -target:keepass2android-app -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -p:BuildProjectReferences=true $(MSBUILD_PARAM) -p:Platform="Any CPU" -m | ||||
| dotnetbuild: manifestlink native java nuget  | ||||
| 	$(DOTNET) build src/KeePass.sln -target:keepass2android-app -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -p:BuildProjectReferences=true $(DOTNET_PARAM) -p:Platform="Any CPU" -m | ||||
|  | ||||
| apk: msbuild  | ||||
| 	$(MSBUILD) src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(MSBUILD_PARAM) -p:Platform=AnyCPU -m  | ||||
| apk: manifestlink native java nuget   | ||||
| 	$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m  | ||||
|  | ||||
| build_all: msbuild | ||||
| apk_split: manifestlink native java nuget  | ||||
| 	$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-arm | ||||
| 	$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-arm64 | ||||
| 	$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-x86 | ||||
| 	$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-x64 | ||||
| 	src/build-scripts/rename-output-apks.sh src/keepass2android-app/bin/Release/net8.0-android/ | ||||
|  | ||||
| build_all: dotnetbuild | ||||
|  | ||||
| ##### Cleanup targets | ||||
|  | ||||
| @@ -369,10 +348,10 @@ else | ||||
| endif | ||||
| 	$(RMFILE) stamp.nuget_* | ||||
|  | ||||
| clean_msbuild: | ||||
| 	$(MSBUILD) src/KeePass.sln -target:clean $(MSBUILD_PARAM) | ||||
| clean_dotnet: | ||||
| 	$(DOTNET) clean src/KeePass.sln $(DOTNET_PARAM) | ||||
|  | ||||
| clean: clean_native clean_java clean_nuget clean_msbuild | ||||
| clean: clean_native clean_java clean_nuget clean_dotnet | ||||
|  | ||||
| distclean: clean | ||||
| ifneq ("$(wildcard ./allow_git_clean)","") | ||||
|   | ||||
| @@ -11,10 +11,10 @@ Regular stable releases of Keepass2Android are available on [Google Play](https: | ||||
| Beta-releases can be obtained by opting in to the [Beta testing channel](https://play.google.com/apps/testing/keepass2android.keepass2android) or [Beta testing channel for Keepass2Android Offline](https://play.google.com/apps/testing/keepass2android.keepass2android_nonet). | ||||
|  | ||||
| # How can I contribute? | ||||
| * Help to translate Keepass2Android into your language or improve translations at [our Crowdin page](http://crowdin.net/project/keepass2android) | ||||
| * Help to translate Keepass2Android into your language or improve translations at [our Crowdin page](https://crowdin.net/project/keepass2android) | ||||
| * Add features by [creating a plugin](How-to-create-a-plug-in_.md) or creating a pull request. You might want to contact me before you start working so I can coordinate efforts. | ||||
| * [Become a GitHub sponsor to boost 🚀 development](https://github.com/sponsors/PhilippC) | ||||
| * [Make a donation](http://philipp.crocoll.net/donate.php) | ||||
| * [Make a donation](https://philipp.crocoll.net/donate.php) | ||||
|  | ||||
| # How do I learn more? | ||||
| Please see the [wiki](https://github.com/PhilippC/keepass2android/wiki/Documentation) for further information. | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net8.0-android</TargetFramework> | ||||
|     <TargetFramework>net9.0-android</TargetFramework> | ||||
|     <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net8.0-android</TargetFramework> | ||||
|     <TargetFramework>net9.0-android</TargetFramework> | ||||
|     <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net8.0-android</TargetFramework> | ||||
|     <TargetFramework>net9.0-android</TargetFramework> | ||||
|     <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net8.0-android</TargetFramework> | ||||
|     <TargetFramework>net9.0-android</TargetFramework> | ||||
|     <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| /* | ||||
|   KeePass Password Safe - The Open-Source Password Manager | ||||
|   Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|   Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|  | ||||
|   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 | ||||
| @@ -29,197 +29,226 @@ using KeePassLib.Utility; | ||||
|  | ||||
| namespace KeePassLib.Cryptography | ||||
| { | ||||
| 	/// <summary> | ||||
| 	/// Algorithms supported by <c>CryptoRandomStream</c>. | ||||
| 	/// </summary> | ||||
| 	public enum CrsAlgorithm | ||||
| 	{ | ||||
| 		/// <summary> | ||||
| 		/// Not supported. | ||||
| 		/// </summary> | ||||
| 		Null = 0, | ||||
|     /// <summary> | ||||
|     /// Algorithms supported by <c>CryptoRandomStream</c>. | ||||
|     /// </summary> | ||||
|     public enum CrsAlgorithm | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Not supported. | ||||
|         /// </summary> | ||||
|         Null = 0, | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// A variant of the ARCFour algorithm (RC4 incompatible). | ||||
| 		/// </summary> | ||||
| 		/// </summary> | ||||
| 		ArcFourVariant = 1, | ||||
|         /// <summary> | ||||
|         /// A variant of the ArcFour algorithm (RC4 incompatible). | ||||
|         /// Insecure; for backward compatibility only. | ||||
|         /// </summary> | ||||
|         ArcFourVariant = 1, | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Salsa20 stream cipher algorithm. | ||||
| 		/// </summary> | ||||
| 		Salsa20 = 2, | ||||
|         /// <summary> | ||||
|         /// Salsa20 stream cipher algorithm. | ||||
|         /// </summary> | ||||
|         Salsa20 = 2, | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// ChaCha20 stream cipher algorithm. | ||||
| 		/// </summary> | ||||
| 		ChaCha20 = 3, | ||||
|         /// <summary> | ||||
|         /// ChaCha20 stream cipher algorithm. | ||||
|         /// </summary> | ||||
|         ChaCha20 = 3, | ||||
|  | ||||
| 		Count = 4 | ||||
| 	} | ||||
|         Count = 4 | ||||
|     } | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// 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. | ||||
| 	/// </summary> | ||||
| 	public sealed class CryptoRandomStream : IDisposable | ||||
| 	{ | ||||
| 		private readonly CrsAlgorithm m_crsAlgorithm; | ||||
|     /// <summary> | ||||
|     /// 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. | ||||
|     /// </summary> | ||||
|     public sealed class CryptoRandomStream : IDisposable | ||||
|     { | ||||
|         private readonly CrsAlgorithm m_alg; | ||||
|         private bool m_bDisposed = false; | ||||
|  | ||||
| 		private byte[] m_pbState = null; | ||||
| 		private byte m_i = 0; | ||||
| 		private byte m_j = 0; | ||||
|         private readonly byte[] m_pbKey = null; | ||||
|         private readonly byte[] m_pbIV = null; | ||||
|  | ||||
| 		private Salsa20Cipher m_salsa20 = null; | ||||
| 		private ChaCha20Cipher m_chacha20 = null; | ||||
|         private readonly ChaCha20Cipher m_chacha20 = null; | ||||
|         private readonly Salsa20Cipher m_salsa20 = null; | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Construct a new cryptographically secure random stream object. | ||||
| 		/// </summary> | ||||
| 		/// <param name="genAlgorithm">Algorithm to use.</param> | ||||
| 		/// <param name="pbKey">Initialization key. Must not be <c>null</c> and | ||||
| 		/// must contain at least 1 byte.</param> | ||||
| 		public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey) | ||||
| 		{ | ||||
| 			if(pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); } | ||||
| 		/// <exception cref="System.ArgumentNullException">Thrown if the | ||||
| 			int cbKey = pbKey.Length; | ||||
| 			if(cbKey <= 0) | ||||
| 			{ | ||||
| 				Debug.Assert(false); // Need at least one byte | ||||
| 				throw new ArgumentOutOfRangeException("pbKey"); | ||||
| 			} | ||||
| 		/// <paramref name="pbKey" /> parameter is <c>null</c>.</exception> | ||||
| 			m_crsAlgorithm = a; | ||||
| 		/// <exception cref="System.ArgumentException">Thrown if the | ||||
| 			if(a == CrsAlgorithm.ChaCha20) | ||||
| 			{ | ||||
| 				byte[] pbKey32 = new byte[32]; | ||||
| 				byte[] pbIV12 = new byte[12]; | ||||
| 		/// <paramref name="pbKey" /> 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.</exception> | ||||
| 				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 | ||||
|         private readonly byte[] m_pbState = null; | ||||
|         private byte m_i = 0; | ||||
|         private byte m_j = 0; | ||||
|  | ||||
| 				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; | ||||
|         /// <summary> | ||||
|         /// Construct a new cryptographically secure random stream object. | ||||
|         /// </summary> | ||||
|         /// <param name="a">Algorithm to use.</param> | ||||
|         /// <param name="pbKey">Initialization key. Must not be <c>null</c> | ||||
|         /// and must contain at least 1 byte.</param> | ||||
|         public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey) | ||||
|         { | ||||
|             if (pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); } | ||||
|  | ||||
| 				unchecked | ||||
| 				{ | ||||
| 					byte j = 0, t; | ||||
| 					int inxKey = 0; | ||||
| 					for(int w = 0; w < 256; ++w) // Key setup | ||||
| 					{ | ||||
| 						j += (byte)(m_pbState[w] + pbKey[inxKey]); | ||||
|             int cbKey = pbKey.Length; | ||||
|             if (cbKey <= 0) | ||||
|             { | ||||
|                 Debug.Assert(false); // Need at least one byte | ||||
|                 throw new ArgumentOutOfRangeException("pbKey"); | ||||
|             } | ||||
|  | ||||
| 						t = m_pbState[0]; // Swap entries | ||||
| 						m_pbState[0] = m_pbState[j]; | ||||
| 						m_pbState[j] = t; | ||||
|             m_alg = a; | ||||
|  | ||||
| 						++inxKey; | ||||
| 						if(inxKey >= cbKey) inxKey = 0; | ||||
| 					} | ||||
| 				} | ||||
|             if (a == CrsAlgorithm.ChaCha20) | ||||
|             { | ||||
|                 m_pbKey = new byte[32]; | ||||
|                 m_pbIV = new byte[12]; | ||||
|  | ||||
| 				GetRandomBytes(512); // Increases security, see cryptanalysis | ||||
| 			} | ||||
| 			else // Unknown algorithm | ||||
| 			{ | ||||
| 				Debug.Assert(false); | ||||
| 				throw new ArgumentOutOfRangeException("a"); | ||||
| 			} | ||||
| 		} | ||||
|                 using (SHA512Managed h = new SHA512Managed()) | ||||
|                 { | ||||
|                     byte[] pbHash = h.ComputeHash(pbKey); | ||||
|                     Array.Copy(pbHash, m_pbKey, 32); | ||||
|                     Array.Copy(pbHash, 32, m_pbIV, 0, 12); | ||||
|                     MemUtil.ZeroByteArray(pbHash); | ||||
|                 } | ||||
|  | ||||
| 		public void Dispose() | ||||
| 		{ | ||||
| 			Dispose(true); | ||||
| 			GC.SuppressFinalize(this); | ||||
| 		} | ||||
|                 m_chacha20 = new ChaCha20Cipher(m_pbKey, m_pbIV, true); | ||||
|             } | ||||
|             else if (a == CrsAlgorithm.Salsa20) | ||||
|             { | ||||
|                 m_pbKey = CryptoUtil.HashSha256(pbKey); | ||||
|                 m_pbIV = new byte[8] { 0xE8, 0x30, 0x09, 0x4B, | ||||
|                     0x97, 0x20, 0x5D, 0x2A }; // Unique constant | ||||
|  | ||||
| 		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); } | ||||
| 			} | ||||
| 		} | ||||
|                 m_salsa20 = new Salsa20Cipher(m_pbKey, m_pbIV); | ||||
|             } | ||||
|             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; | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Get <paramref name="uRequestedCount" /> random bytes. | ||||
| 		/// </summary> | ||||
| 		/// <param name="uRequestedCount">Number of random bytes to retrieve.</param> | ||||
| 		/// <returns>Returns <paramref name="uRequestedCount" /> random bytes.</returns> | ||||
| 		public byte[] GetRandomBytes(uint uRequestedCount) | ||||
| 		{ | ||||
| 			if(uRequestedCount == 0) return MemUtil.EmptyByteArray; | ||||
|                 unchecked | ||||
|                 { | ||||
|                     byte j = 0, t; | ||||
|                     int inxKey = 0; | ||||
|                     for (int w = 0; w < 256; ++w) // Key setup | ||||
|                     { | ||||
|                         j += (byte)(m_pbState[w] + pbKey[inxKey]); | ||||
|  | ||||
| 			if(uRequestedCount > (uint)int.MaxValue) | ||||
| 				throw new ArgumentOutOfRangeException("uRequestedCount"); | ||||
| 			int cb = (int)uRequestedCount; | ||||
|                         t = m_pbState[0]; // Swap entries | ||||
|                         m_pbState[0] = m_pbState[j]; | ||||
|                         m_pbState[j] = t; | ||||
|  | ||||
| 			byte[] pbRet = new byte[cb]; | ||||
|                         ++inxKey; | ||||
|                         if (inxKey >= cbKey) inxKey = 0; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
| 			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]; | ||||
|                 GetRandomBytes(512); // Increases security, see cryptanalysis | ||||
|             } | ||||
|             else // Unknown algorithm | ||||
|             { | ||||
|                 Debug.Assert(false); | ||||
|                 throw new ArgumentOutOfRangeException("a"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 						byte t = m_pbState[m_i]; // Swap entries | ||||
| 						m_pbState[m_i] = m_pbState[m_j]; | ||||
| 						m_pbState[m_j] = t; | ||||
|         public void Dispose() | ||||
|         { | ||||
|             Dispose(true); | ||||
|             GC.SuppressFinalize(this); | ||||
|         } | ||||
|  | ||||
| 						t = (byte)(m_pbState[m_i] + m_pbState[m_j]); | ||||
| 						pbRet[w] = m_pbState[t]; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			else { Debug.Assert(false); } | ||||
|         private void Dispose(bool disposing) | ||||
|         { | ||||
|             if (disposing) | ||||
|             { | ||||
|                 if (m_alg == CrsAlgorithm.ChaCha20) | ||||
|                     m_chacha20.Dispose(); | ||||
|                 else if (m_alg == CrsAlgorithm.Salsa20) | ||||
|                     m_salsa20.Dispose(); | ||||
|                 else if (m_alg == CrsAlgorithm.ArcFourVariant) | ||||
|                 { | ||||
|                     MemUtil.ZeroByteArray(m_pbState); | ||||
|                     m_i = 0; | ||||
|                     m_j = 0; | ||||
|                 } | ||||
|                 else { Debug.Assert(false); } | ||||
|  | ||||
| 			return pbRet; | ||||
| 		} | ||||
|                 if (m_pbKey != null) MemUtil.ZeroByteArray(m_pbKey); | ||||
|                 if (m_pbIV != null) MemUtil.ZeroByteArray(m_pbIV); | ||||
|  | ||||
| 		public ulong GetRandomUInt64() | ||||
| 		{ | ||||
| 			byte[] pb = GetRandomBytes(8); | ||||
| 			return MemUtil.BytesToUInt64(pb); | ||||
| 			} | ||||
|                 m_bDisposed = true; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Get <paramref name="uRequestedCount" /> random bytes. | ||||
|         /// </summary> | ||||
|         /// <param name="uRequestedCount">Number of random bytes to retrieve.</param> | ||||
|         /// <returns>Returns <paramref name="uRequestedCount" /> random bytes.</returns> | ||||
|         public byte[] GetRandomBytes(uint uRequestedCount) | ||||
|         { | ||||
|             if (m_bDisposed) throw new ObjectDisposedException(null); | ||||
|  | ||||
|             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_alg == CrsAlgorithm.ChaCha20) | ||||
|                 m_chacha20.Encrypt(pbRet, 0, cb); | ||||
|             else if (m_alg == CrsAlgorithm.Salsa20) | ||||
|                 m_salsa20.Encrypt(pbRet, 0, cb); | ||||
|             else if (m_alg == 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); | ||||
|         } | ||||
|  | ||||
|         internal ulong GetRandomUInt64(ulong uMaxExcl) | ||||
|         { | ||||
|             if (uMaxExcl == 0) { Debug.Assert(false); throw new ArgumentOutOfRangeException("uMaxExcl"); } | ||||
|  | ||||
|             ulong uGen, uRem; | ||||
|             do | ||||
|             { | ||||
|                 uGen = GetRandomUInt64(); | ||||
|                 uRem = uGen % uMaxExcl; | ||||
|             } | ||||
|             while ((uGen - uRem) > (ulong.MaxValue - (uMaxExcl - 1UL))); | ||||
|             // This ensures that the last number of the block (i.e. | ||||
|             // (uGen - uRem) + (uMaxExcl - 1)) is generatable; | ||||
|             // for signed longs, overflow to negative number: | ||||
|             // while((uGen - uRem) + (uMaxExcl - 1) < 0); | ||||
|  | ||||
|             return uRem; | ||||
|         } | ||||
|  | ||||
| #if CRSBENCHMARK | ||||
| 		public static string Benchmark() | ||||
| @@ -237,22 +266,21 @@ namespace KeePassLib.Cryptography | ||||
| 			return str; | ||||
| 		} | ||||
|  | ||||
| 		private static int BenchTime(CrsAlgorithm cra, int nRounds, int nDataSize) | ||||
| 		private static int BenchTime(CrsAlgorithm a, int nRounds, int cbData) | ||||
| 		{ | ||||
| 			byte[] pbKey = new byte[4] { 0x00, 0x01, 0x02, 0x03 }; | ||||
|  | ||||
| 			int nStart = Environment.TickCount; | ||||
| 			int tStart = Environment.TickCount; | ||||
| 			for(int i = 0; i < nRounds; ++i) | ||||
| 			{ | ||||
| 				using(CryptoRandomStream c = new CryptoRandomStream(cra, pbKey)) | ||||
| 				using(CryptoRandomStream crs = new CryptoRandomStream(a, pbKey)) | ||||
| 				{ | ||||
| 					c.GetRandomBytes((uint)nDataSize); | ||||
| 					crs.GetRandomBytes((uint)cbData); | ||||
| 				} | ||||
| 			} | ||||
| 			int nEnd = Environment.TickCount; | ||||
|  | ||||
| 			return (nEnd - nStart); | ||||
| 			return (Environment.TickCount - tStart); | ||||
| 		} | ||||
| #endif | ||||
| 	} | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,65 +0,0 @@ | ||||
| /* | ||||
|   KeePass Password Safe - The Open-Source Password Manager | ||||
|   Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|  | ||||
|   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<char>(vGenerated); | ||||
| 					return PwgError.TooFewCharacters; | ||||
| 				} | ||||
|  | ||||
| 				vGenerated[nIndex] = ch; | ||||
| 			} | ||||
|  | ||||
| 			byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vGenerated); | ||||
| 			psOut = new ProtectedString(true, pbUtf8); | ||||
| 			MemUtil.ZeroByteArray(pbUtf8); | ||||
| 			MemUtil.ZeroArray<char>(vGenerated); | ||||
|  | ||||
| 			return PwgError.Success; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,173 +0,0 @@ | ||||
| /* | ||||
|   KeePass Password Safe - The Open-Source Password Manager | ||||
|   Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|  | ||||
|   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<char> vGenerated = new LinkedList<char>(); | ||||
| 			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<char>(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; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| /* | ||||
|   KeePass Password Safe - The Open-Source Password Manager | ||||
|   Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|   Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|  | ||||
|   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 | ||||
| @@ -19,333 +19,311 @@ | ||||
|  | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
| using System.Diagnostics; | ||||
| using System.Text; | ||||
|  | ||||
| using KeePassLib.Utility; | ||||
|  | ||||
| namespace KeePassLib.Cryptography.PasswordGenerator | ||||
| { | ||||
| 	public sealed class PwCharSet | ||||
| 	{ | ||||
| 		public const string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; | ||||
| 		public const string LowerCase = "abcdefghijklmnopqrstuvwxyz"; | ||||
| 		public const string Digits = "0123456789"; | ||||
|     public sealed class PwCharSet : IEquatable<PwCharSet> | ||||
|     { | ||||
|         public static readonly string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; | ||||
|         public static readonly string LowerCase = "abcdefghijklmnopqrstuvwxyz"; | ||||
|         public static readonly string Digits = "0123456789"; | ||||
|  | ||||
| 		public const string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ"; | ||||
| 		public const string LowerConsonants = "bcdfghjklmnpqrstvwxyz"; | ||||
| 		public const string UpperVowels = "AEIOU"; | ||||
| 		public const string LowerVowels = "aeiou"; | ||||
|         public static readonly string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ"; | ||||
|         public static readonly string LowerConsonants = "bcdfghjklmnpqrstvwxyz"; | ||||
|         public static readonly string UpperVowels = "AEIOU"; | ||||
|         public static readonly string LowerVowels = "aeiou"; | ||||
|  | ||||
| 		public const string Punctuation = @",.;:"; | ||||
| 		public const string Brackets = @"[]{}()<>"; | ||||
|         public static readonly string Punctuation = ",.;:"; | ||||
|         public static readonly string Brackets = @"[]{}()<>"; | ||||
|  | ||||
| 		public const string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; | ||||
|         public static readonly string Special = "!\"#$%&'*+,./:;=?@\\^`|~"; | ||||
|         public static readonly string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; | ||||
|  | ||||
| 		public const string UpperHex = "0123456789ABCDEF"; | ||||
| 		public const string LowerHex = "0123456789abcdef"; | ||||
|         public static readonly string UpperHex = "0123456789ABCDEF"; | ||||
|         public static readonly string LowerHex = "0123456789abcdef"; | ||||
|  | ||||
| 		public const string Invalid = "\t\r\n"; | ||||
| 		public const string LookAlike = @"O0l1I|"; | ||||
|         public static readonly string LookAlike = "O0Il1|"; | ||||
|  | ||||
| 		internal const string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits; | ||||
|         /// <summary> | ||||
|         /// Latin-1 Supplement except U+00A0 (NBSP) and U+00AD (SHY). | ||||
|         /// </summary> | ||||
|         public static readonly string Latin1S = | ||||
|             "\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7" + | ||||
|             "\u00A8\u00A9\u00AA\u00AB\u00AC\u00AE\u00AF" + | ||||
|             "\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7" + | ||||
|             "\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF" + | ||||
|             "\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7" + | ||||
|             "\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF" + | ||||
|             "\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7" + | ||||
|             "\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF" + | ||||
|             "\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7" + | ||||
|             "\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF" + | ||||
|             "\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7" + | ||||
|             "\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF"; | ||||
|  | ||||
| 		private const int CharTabSize = (0x10000 / 8); | ||||
|         // internal static readonly string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits; | ||||
|  | ||||
| 		private List<char> m_vChars = new List<char>(); | ||||
| 		private byte[] m_vTab = new byte[CharTabSize]; | ||||
|         [Obsolete] | ||||
|         public static string SpecialChars { get { return PwCharSet.Special; } } | ||||
|         [Obsolete] | ||||
|         public static string HighAnsiChars { get { return PwCharSet.Latin1S; } } | ||||
|  | ||||
| 		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 readonly List<char> m_lChars = new List<char>(); | ||||
|         private readonly byte[] m_vTab = new byte[0x10000 / 8]; | ||||
|  | ||||
| 		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; | ||||
| 			} | ||||
| 		} | ||||
|         /// <summary> | ||||
|         /// Create a new, empty character set. | ||||
|         /// </summary> | ||||
|         public PwCharSet() | ||||
|         { | ||||
|             Debug.Assert(PwCharSet.Latin1S.Length == (16 * 6 - 2)); | ||||
|         } | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Create a new, empty character set collection object. | ||||
| 		/// </summary> | ||||
| 		public PwCharSet() | ||||
| 		{ | ||||
| 			Initialize(true); | ||||
| 		} | ||||
|         public PwCharSet(string strCharSet) | ||||
|         { | ||||
|             Add(strCharSet); | ||||
|         } | ||||
|  | ||||
| 		public PwCharSet(string strCharSet) | ||||
| 		{ | ||||
| 			Initialize(true); | ||||
| 			Add(strCharSet); | ||||
| 		} | ||||
|         /// <summary> | ||||
|         /// Number of characters in this set. | ||||
|         /// </summary> | ||||
|         public uint Size | ||||
|         { | ||||
|             get { return (uint)m_lChars.Count; } | ||||
|         } | ||||
|  | ||||
| 		private PwCharSet(bool bFullInitialize) | ||||
| 		{ | ||||
| 			Initialize(bFullInitialize); | ||||
| 		} | ||||
|         /// <summary> | ||||
|         /// Get a character of the set using an index. | ||||
|         /// </summary> | ||||
|         /// <param name="uPos">Index of the character to get.</param> | ||||
|         /// <returns>Character at the specified position. If the index is invalid, | ||||
|         /// an <c>ArgumentOutOfRangeException</c> is thrown.</returns> | ||||
|         public char this[uint uPos] | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if (uPos >= (uint)m_lChars.Count) | ||||
|                     throw new ArgumentOutOfRangeException("uPos"); | ||||
|  | ||||
| 		private void Initialize(bool bFullInitialize) | ||||
| 		{ | ||||
| 			Clear(); | ||||
|                 return m_lChars[(int)uPos]; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 			if(!bFullInitialize) return; | ||||
|         public bool Equals(PwCharSet other) | ||||
|         { | ||||
|             if (object.ReferenceEquals(other, this)) return true; | ||||
|             if (object.ReferenceEquals(other, null)) return false; | ||||
|  | ||||
| 			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'); | ||||
|             if (m_lChars.Count != other.m_lChars.Count) return false; | ||||
|  | ||||
| 				m_strHighAnsi = sbHighAnsi.ToString(); | ||||
| 			} | ||||
|             return MemUtil.ArraysEqual(m_vTab, other.m_vTab); | ||||
|         } | ||||
|  | ||||
| 			if(m_strSpecial == null) | ||||
| 			{ | ||||
| 				PwCharSet pcs = new PwCharSet(false); | ||||
| 				pcs.AddRange('!', '/'); | ||||
| 				pcs.AddRange(':', '@'); | ||||
| 				pcs.AddRange('[', '`'); | ||||
| 				pcs.Add(@"|~"); | ||||
| 				pcs.Remove(@"-_ "); | ||||
| 				pcs.Remove(PwCharSet.Brackets); | ||||
|         public override bool Equals(object obj) | ||||
|         { | ||||
|             return Equals(obj as PwCharSet); | ||||
|         } | ||||
|  | ||||
| 				m_strSpecial = pcs.ToString(); | ||||
| 			} | ||||
| 		} | ||||
|         public override int GetHashCode() | ||||
|         { | ||||
|             return (int)MemUtil.Hash32(m_vTab, 0, m_vTab.Length); | ||||
|         } | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Number of characters in this set. | ||||
| 		/// </summary> | ||||
| 		public uint Size | ||||
| 		{ | ||||
| 			get { return (uint)m_vChars.Count; } | ||||
| 		} | ||||
|         /// <summary> | ||||
|         /// Remove all characters from this set. | ||||
|         /// </summary> | ||||
|         public void Clear() | ||||
|         { | ||||
|             m_lChars.Clear(); | ||||
|             Array.Clear(m_vTab, 0, m_vTab.Length); | ||||
|         } | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Get a character of the set using an index. | ||||
| 		/// </summary> | ||||
| 		/// <param name="uPos">Index of the character to get.</param> | ||||
| 		/// <returns>Character at the specified position. If the index is invalid, | ||||
| 		/// an <c>ArgumentOutOfRangeException</c> is thrown.</returns> | ||||
| 		public char this[uint uPos] | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				if(uPos >= (uint)m_vChars.Count) | ||||
| 					throw new ArgumentOutOfRangeException("uPos"); | ||||
|         public bool Contains(char ch) | ||||
|         { | ||||
|             return (((m_vTab[ch / 8] >> (ch % 8)) & 1) != char.MinValue); | ||||
|         } | ||||
|  | ||||
| 				return m_vChars[(int)uPos]; | ||||
| 			} | ||||
| 		} | ||||
|         public bool Contains(string strCharacters) | ||||
|         { | ||||
|             Debug.Assert(strCharacters != null); | ||||
|             if (strCharacters == null) throw new ArgumentNullException("strCharacters"); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Remove all characters from this set. | ||||
| 		/// </summary> | ||||
| 		public void Clear() | ||||
| 		{ | ||||
| 			m_vChars.Clear(); | ||||
| 			Array.Clear(m_vTab, 0, m_vTab.Length); | ||||
| 		} | ||||
|             foreach (char ch in strCharacters) | ||||
|             { | ||||
|                 if (!Contains(ch)) return false; | ||||
|             } | ||||
|  | ||||
| 		public bool Contains(char ch) | ||||
| 		{ | ||||
| 			return (((m_vTab[ch / 8] >> (ch % 8)) & 1) != char.MinValue); | ||||
| 		} | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
| 		public bool Contains(string strCharacters) | ||||
| 		{ | ||||
| 			Debug.Assert(strCharacters != null); | ||||
| 			if(strCharacters == null) throw new ArgumentNullException("strCharacters"); | ||||
|         /// <summary> | ||||
|         /// Add characters to the set. | ||||
|         /// </summary> | ||||
|         /// <param name="ch">Character to add.</param> | ||||
|         public void Add(char ch) | ||||
|         { | ||||
|             if (ch == char.MinValue) { Debug.Assert(false); return; } | ||||
|  | ||||
| 			foreach(char ch in strCharacters) | ||||
| 			{ | ||||
| 				if(!Contains(ch)) return false; | ||||
| 			} | ||||
|             if (!Contains(ch)) | ||||
|             { | ||||
|                 m_lChars.Add(ch); | ||||
|                 m_vTab[ch / 8] |= (byte)(1 << (ch % 8)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 			return true; | ||||
| 		} | ||||
|         /// <summary> | ||||
|         /// Add characters to the set. | ||||
|         /// </summary> | ||||
|         /// <param name="strCharSet">String containing characters to add.</param> | ||||
|         public void Add(string strCharSet) | ||||
|         { | ||||
|             Debug.Assert(strCharSet != null); | ||||
|             if (strCharSet == null) throw new ArgumentNullException("strCharSet"); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Add characters to the set. | ||||
| 		/// </summary> | ||||
| 		/// <param name="ch">Character to add.</param> | ||||
| 		public void Add(char ch) | ||||
| 		{ | ||||
| 			if(ch == char.MinValue) { Debug.Assert(false); return; } | ||||
|             foreach (char ch in strCharSet) | ||||
|                 Add(ch); | ||||
|         } | ||||
|  | ||||
| 			if(!Contains(ch)) | ||||
| 			{ | ||||
| 				m_vChars.Add(ch); | ||||
| 				m_vTab[ch / 8] |= (byte)(1 << (ch % 8)); | ||||
| 			} | ||||
| 		} | ||||
|         public void Add(string strCharSet1, string strCharSet2) | ||||
|         { | ||||
|             Add(strCharSet1); | ||||
|             Add(strCharSet2); | ||||
|         } | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Add characters to the set. | ||||
| 		/// </summary> | ||||
| 		/// <param name="strCharSet">String containing characters to add.</param> | ||||
| 		public void Add(string strCharSet) | ||||
| 		{ | ||||
| 			Debug.Assert(strCharSet != null); | ||||
| 			if(strCharSet == null) throw new ArgumentNullException("strCharSet"); | ||||
|         public void Add(string strCharSet1, string strCharSet2, string strCharSet3) | ||||
|         { | ||||
|             Add(strCharSet1); | ||||
|             Add(strCharSet2); | ||||
|             Add(strCharSet3); | ||||
|         } | ||||
|  | ||||
| 			m_vChars.Capacity = m_vChars.Count + strCharSet.Length; | ||||
|         public void AddRange(char chMin, char chMax) | ||||
|         { | ||||
|             for (char ch = chMin; ch < chMax; ++ch) | ||||
|                 Add(ch); | ||||
|  | ||||
| 			foreach(char ch in strCharSet) | ||||
| 				Add(ch); | ||||
| 		} | ||||
|             Add(chMax); | ||||
|         } | ||||
|  | ||||
| 		public void Add(string strCharSet1, string strCharSet2) | ||||
| 		{ | ||||
| 			Add(strCharSet1); | ||||
| 			Add(strCharSet2); | ||||
| 		} | ||||
|         public bool AddCharSet(char chCharSetIdentifier) | ||||
|         { | ||||
|             bool bResult = true; | ||||
|  | ||||
| 		public void Add(string strCharSet1, string strCharSet2, string strCharSet3) | ||||
| 		{ | ||||
| 			Add(strCharSet1); | ||||
| 			Add(strCharSet2); | ||||
| 			Add(strCharSet3); | ||||
| 		} | ||||
|             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(PwCharSet.Latin1S); break; | ||||
|                 default: bResult = false; break; | ||||
|             } | ||||
|  | ||||
| 		public void AddRange(char chMin, char chMax) | ||||
| 		{ | ||||
| 			m_vChars.Capacity = m_vChars.Count + (chMax - chMin) + 1; | ||||
|             return bResult; | ||||
|         } | ||||
|  | ||||
| 			for(char ch = chMin; ch < chMax; ++ch) | ||||
| 				Add(ch); | ||||
|         public bool Remove(char ch) | ||||
|         { | ||||
|             m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8))); | ||||
|             return m_lChars.Remove(ch); | ||||
|         } | ||||
|  | ||||
| 			Add(chMax); | ||||
| 		} | ||||
|         public bool Remove(string strCharacters) | ||||
|         { | ||||
|             Debug.Assert(strCharacters != null); | ||||
|             if (strCharacters == null) throw new ArgumentNullException("strCharacters"); | ||||
|  | ||||
| 		public bool AddCharSet(char chCharSetIdentifier) | ||||
| 		{ | ||||
| 			bool bResult = true; | ||||
|             bool bResult = true; | ||||
|             foreach (char ch in strCharacters) | ||||
|             { | ||||
|                 if (!Remove(ch)) bResult = false; | ||||
|             } | ||||
|  | ||||
| 			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; | ||||
|         } | ||||
|  | ||||
| 			return bResult; | ||||
| 		} | ||||
|         public bool RemoveIfAllExist(string strCharacters) | ||||
|         { | ||||
|             Debug.Assert(strCharacters != null); | ||||
|             if (strCharacters == null) throw new ArgumentNullException("strCharacters"); | ||||
|  | ||||
| 		public bool Remove(char ch) | ||||
| 		{ | ||||
| 			m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8))); | ||||
| 			return m_vChars.Remove(ch); | ||||
| 		} | ||||
|             if (!Contains(strCharacters)) | ||||
|                 return false; | ||||
|  | ||||
| 		public bool Remove(string strCharacters) | ||||
| 		{ | ||||
| 			Debug.Assert(strCharacters != null); | ||||
| 			if(strCharacters == null) throw new ArgumentNullException("strCharacters"); | ||||
|             return Remove(strCharacters); | ||||
|         } | ||||
|  | ||||
| 			bool bResult = true; | ||||
| 			foreach(char ch in strCharacters) | ||||
| 			{ | ||||
| 				if(!Remove(ch)) bResult = false; | ||||
| 			} | ||||
|         /// <summary> | ||||
|         /// Convert the character set to a string containing all its characters. | ||||
|         /// </summary> | ||||
|         /// <returns>String containing all character set characters.</returns> | ||||
|         public override string ToString() | ||||
|         { | ||||
|             StringBuilder sb = new StringBuilder(m_lChars.Count); | ||||
|             foreach (char ch in m_lChars) | ||||
|                 sb.Append(ch); | ||||
|  | ||||
| 			return bResult; | ||||
| 		} | ||||
|             return sb.ToString(); | ||||
|         } | ||||
|  | ||||
| 		public bool RemoveIfAllExist(string strCharacters) | ||||
| 		{ | ||||
| 			Debug.Assert(strCharacters != null); | ||||
| 			if(strCharacters == null) throw new ArgumentNullException("strCharacters"); | ||||
|         public string PackAndRemoveCharRanges() | ||||
|         { | ||||
|             StringBuilder sb = new StringBuilder(); | ||||
|  | ||||
| 			if(!Contains(strCharacters)) | ||||
| 				return false; | ||||
|             sb.Append(RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_'); | ||||
|             sb.Append(RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_'); | ||||
|             sb.Append(RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_'); | ||||
|             sb.Append(RemoveIfAllExist(PwCharSet.Special) ? '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(PwCharSet.Latin1S) ? 'H' : '_'); | ||||
|  | ||||
| 			return Remove(strCharacters); | ||||
| 		} | ||||
|             return sb.ToString(); | ||||
|         } | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Convert the character set to a string containing all its characters. | ||||
| 		/// </summary> | ||||
| 		/// <returns>String containing all character set characters.</returns> | ||||
| 		public override string ToString() | ||||
| 		{ | ||||
| 			StringBuilder sb = new StringBuilder(); | ||||
| 			foreach(char ch in m_vChars) | ||||
| 				sb.Append(ch); | ||||
|         public void UnpackCharRanges(string strRanges) | ||||
|         { | ||||
|             if (strRanges == null) { Debug.Assert(false); return; } | ||||
|             if (strRanges.Length < 10) { Debug.Assert(false); return; } | ||||
|  | ||||
| 			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); | ||||
| 		} | ||||
| 	} | ||||
|             if (strRanges[0] != '_') Add(PwCharSet.UpperCase); | ||||
|             if (strRanges[1] != '_') Add(PwCharSet.LowerCase); | ||||
|             if (strRanges[2] != '_') Add(PwCharSet.Digits); | ||||
|             if (strRanges[3] != '_') Add(PwCharSet.Special); | ||||
|             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(PwCharSet.Latin1S); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| /* | ||||
|   KeePass Password Safe - The Open-Source Password Manager | ||||
|   Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|   Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|  | ||||
|   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 | ||||
| @@ -20,133 +20,172 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Diagnostics; | ||||
| using System.Security.Cryptography; | ||||
| using System.Text; | ||||
|  | ||||
| #if !KeePassUAP | ||||
| using System.Security.Cryptography; | ||||
| #endif | ||||
|  | ||||
| using KeePassLib.Resources; | ||||
| using KeePassLib.Security; | ||||
| using KeePassLib.Utility; | ||||
|  | ||||
| namespace KeePassLib.Cryptography.PasswordGenerator | ||||
| { | ||||
| 	public enum PwgError | ||||
| 	{ | ||||
| 		Success = 0, | ||||
| 		Unknown = 1, | ||||
| 		TooFewCharacters = 2, | ||||
| 		UnknownAlgorithm = 3 | ||||
| 	} | ||||
|     public enum PwgError | ||||
|     { | ||||
|         Success = 0, | ||||
|         Unknown = 1, | ||||
|         TooFewCharacters = 2, | ||||
|         UnknownAlgorithm = 3, | ||||
|         InvalidCharSet = 4, | ||||
|         InvalidPattern = 5 | ||||
|     } | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Utility functions for generating random passwords. | ||||
| 	/// </summary> | ||||
| 	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"); | ||||
|     /// <summary> | ||||
|     /// Password generator. | ||||
|     /// </summary> | ||||
|     public static class PwGenerator | ||||
|     { | ||||
|  | ||||
| 			CryptoRandomStream crs = CreateCryptoStream(pbUserEntropy); | ||||
| 			PwgError e = PwgError.Unknown; | ||||
|         private static CryptoRandomStream CreateRandomStream(byte[] pbAdditionalEntropy, | ||||
|             out byte[] pbKey) | ||||
|         { | ||||
|             pbKey = CryptoRandom.Instance.GetRandomBytes(128); | ||||
|  | ||||
| 			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; } | ||||
|             // 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); | ||||
|                     MemUtil.ZeroByteArray(pbHash); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| 			return e; | ||||
| 		} | ||||
|             return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey); | ||||
|         } | ||||
|  | ||||
| 		private static CryptoRandomStream CreateCryptoStream(byte[] pbAdditionalEntropy) | ||||
| 		{ | ||||
| 			byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(128); | ||||
|         internal static char GenerateCharacter(PwCharSet pwCharSet, | ||||
|             CryptoRandomStream crsRandomSource) | ||||
|         { | ||||
|             uint cc = pwCharSet.Size; | ||||
|             if (cc == 0) return char.MinValue; | ||||
|  | ||||
| 			// 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); | ||||
| 				} | ||||
| 			} | ||||
|             uint i = (uint)crsRandomSource.GetRandomUInt64(cc); | ||||
|             return pwCharSet[i]; | ||||
|         } | ||||
|  | ||||
| 			return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey); | ||||
| 		} | ||||
|         internal static bool PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile) | ||||
|         { | ||||
|             uint cc = pwCharSet.Size; | ||||
|             for (uint i = 0; i < cc; ++i) | ||||
|             { | ||||
|                 char ch = pwCharSet[i]; | ||||
|                 if ((ch == char.MinValue) || (ch == '\t') || (ch == '\r') || | ||||
|                     (ch == '\n') || char.IsSurrogate(ch)) | ||||
|                     return false; | ||||
|             } | ||||
|  | ||||
| 		internal static char GenerateCharacter(PwProfile pwProfile, | ||||
| 			PwCharSet pwCharSet, CryptoRandomStream crsRandomSource) | ||||
| 		{ | ||||
| 			if (pwCharSet.Size == 0) return char.MinValue; | ||||
|             if (pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike); | ||||
|  | ||||
| 			ulong uIndex = crsRandomSource.GetRandomUInt64(); | ||||
| 			uIndex %= (ulong)pwCharSet.Size; | ||||
|             if (!string.IsNullOrEmpty(pwProfile.ExcludeCharacters)) | ||||
|                 pwCharSet.Remove(pwProfile.ExcludeCharacters); | ||||
|  | ||||
| 			char ch = pwCharSet[(uint)uIndex]; | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
| 			if (pwProfile.NoRepeatingCharacters) | ||||
| 				pwCharSet.Remove(ch); | ||||
|         internal static void Shuffle(char[] v, CryptoRandomStream crsRandomSource) | ||||
|         { | ||||
|             if (v == null) { Debug.Assert(false); return; } | ||||
|             if (crsRandomSource == null) { Debug.Assert(false); return; } | ||||
|  | ||||
| 			return ch; | ||||
| 		} | ||||
|             for (int i = v.Length - 1; i >= 1; --i) | ||||
|             { | ||||
|                 int j = (int)crsRandomSource.GetRandomUInt64((ulong)(i + 1)); | ||||
|  | ||||
| 		internal static void PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile) | ||||
| 		{ | ||||
| 			pwCharSet.Remove(PwCharSet.Invalid); | ||||
|                 char t = v[i]; | ||||
|                 v[i] = v[j]; | ||||
|                 v[j] = t; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 			if (pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike); | ||||
|         private static PwgError GenerateCustom(out ProtectedString psOut, | ||||
|             PwProfile pwProfile, CryptoRandomStream crs, | ||||
|             CustomPwGeneratorPool pwAlgorithmPool) | ||||
|         { | ||||
|             psOut = ProtectedString.Empty; | ||||
|  | ||||
| 			if (pwProfile.ExcludeCharacters.Length > 0) | ||||
| 				pwCharSet.Remove(pwProfile.ExcludeCharacters); | ||||
| 		} | ||||
|             Debug.Assert(pwProfile.GeneratorType == PasswordGeneratorType.Custom); | ||||
|             if (pwAlgorithmPool == null) return PwgError.UnknownAlgorithm; | ||||
|  | ||||
| 		internal static void ShufflePassword(char[] pPassword, | ||||
| 			CryptoRandomStream crsRandomSource) | ||||
| 		{ | ||||
| 			Debug.Assert(pPassword != null); if (pPassword == null) return; | ||||
| 			Debug.Assert(crsRandomSource != null); if (crsRandomSource == null) return; | ||||
|             string strID = pwProfile.CustomAlgorithmUuid; | ||||
|             if (string.IsNullOrEmpty(strID)) return PwgError.UnknownAlgorithm; | ||||
|  | ||||
| 			if (pPassword.Length <= 1) return; // Nothing to shuffle | ||||
|             byte[] pbUuid = Convert.FromBase64String(strID); | ||||
|             PwUuid uuid = new PwUuid(pbUuid); | ||||
|             CustomPwGenerator pwg = pwAlgorithmPool.Find(uuid); | ||||
|             if (pwg == null) { Debug.Assert(false); return PwgError.UnknownAlgorithm; } | ||||
|  | ||||
| 			for (int nSelect = 0; nSelect < pPassword.Length; ++nSelect) | ||||
| 			{ | ||||
| 				ulong uRandomIndex = crsRandomSource.GetRandomUInt64(); | ||||
| 				uRandomIndex %= (ulong)(pPassword.Length - nSelect); | ||||
|             ProtectedString pwd = pwg.Generate(pwProfile.CloneDeep(), crs); | ||||
|             if (pwd == null) return PwgError.Unknown; | ||||
|  | ||||
| 				char chTemp = pPassword[nSelect]; | ||||
| 				pPassword[nSelect] = pPassword[nSelect + (int)uRandomIndex]; | ||||
| 				pPassword[nSelect + (int)uRandomIndex] = chTemp; | ||||
| 			} | ||||
| 		} | ||||
|             psOut = pwd; | ||||
|             return PwgError.Success; | ||||
|         } | ||||
|  | ||||
| 		private static PwgError GenerateCustom(out ProtectedString psOut, | ||||
| 			PwProfile pwProfile, CryptoRandomStream crs, | ||||
| 			CustomPwGeneratorPool pwAlgorithmPool) | ||||
| 		{ | ||||
| 			psOut = ProtectedString.Empty; | ||||
|         internal static string ErrorToString(PwgError e, bool bHeader) | ||||
|         { | ||||
|             if (e == PwgError.Success) { Debug.Assert(false); return string.Empty; } | ||||
|             if ((e == PwgError.Unknown) && bHeader) return KLRes.PwGenFailed; | ||||
|  | ||||
| 			Debug.Assert(pwProfile.GeneratorType == PasswordGeneratorType.Custom); | ||||
| 			if (pwAlgorithmPool == null) return PwgError.UnknownAlgorithm; | ||||
|             string str = KLRes.UnknownError; | ||||
|             switch (e) | ||||
|             { | ||||
|                 // case PwgError.Success: | ||||
|                 //	break; | ||||
|  | ||||
| 			string strID = pwProfile.CustomAlgorithmUuid; | ||||
| 			if (string.IsNullOrEmpty(strID)) { Debug.Assert(false); return PwgError.UnknownAlgorithm; } | ||||
|                 case PwgError.Unknown: | ||||
|                     break; | ||||
|  | ||||
| 			byte[] pbUuid = Convert.FromBase64String(strID); | ||||
| 			PwUuid uuid = new PwUuid(pbUuid); | ||||
| 			CustomPwGenerator pwg = pwAlgorithmPool.Find(uuid); | ||||
| 			if (pwg == null) { Debug.Assert(false); return PwgError.UnknownAlgorithm; } | ||||
|                 case PwgError.TooFewCharacters: | ||||
|                     str = KLRes.CharSetTooFewChars; | ||||
|                     break; | ||||
|  | ||||
| 			ProtectedString pwd = pwg.Generate(pwProfile.CloneDeep(), crs); | ||||
| 			if (pwd == null) return PwgError.Unknown; | ||||
|                 case PwgError.UnknownAlgorithm: | ||||
|                     str = KLRes.AlgorithmUnknown; | ||||
|                     break; | ||||
|  | ||||
| 			psOut = pwd; | ||||
| 			return PwgError.Success; | ||||
| 		} | ||||
| 	} | ||||
|                 case PwgError.InvalidCharSet: | ||||
|                     str = KLRes.CharSetInvalid; | ||||
|                     break; | ||||
|  | ||||
|                 case PwgError.InvalidPattern: | ||||
|                     str = KLRes.PatternInvalid; | ||||
|                     break; | ||||
|  | ||||
|                 default: | ||||
|                     Debug.Assert(false); | ||||
|                     break; | ||||
|             } | ||||
|  | ||||
|             if (bHeader) | ||||
|                 str = KLRes.PwGenFailed + MessageService.NewParagraph + str; | ||||
|  | ||||
|             return str; | ||||
|         } | ||||
|  | ||||
|         internal static string ErrorToString(Exception ex, bool bHeader) | ||||
|         { | ||||
|             string str = ((ex == null) ? KLRes.UnknownError : | ||||
|                 StrUtil.FormatException(ex)); | ||||
|  | ||||
|             if (bHeader) | ||||
|                 str = KLRes.PwGenFailed + MessageService.NewParagraph + str; | ||||
|  | ||||
|             return str; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| /* | ||||
|   KeePass Password Safe - The Open-Source Password Manager | ||||
|   Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|   Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|  | ||||
|   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 | ||||
| @@ -19,114 +19,115 @@ | ||||
|  | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
| using System.Diagnostics; | ||||
| using System.Text; | ||||
|  | ||||
| using KeePassLib.Utility; | ||||
|  | ||||
| namespace KeePassLib.Cryptography | ||||
| { | ||||
| 	public static class PopularPasswords | ||||
| 	{ | ||||
| 		private static Dictionary<int, Dictionary<string, bool>> m_dicts = | ||||
| 			new Dictionary<int, Dictionary<string, bool>>(); | ||||
|     public static class PopularPasswords | ||||
|     { | ||||
|         private static readonly Dictionary<int, Dictionary<char[], bool>> g_dicts = | ||||
|             new Dictionary<int, Dictionary<char[], bool>>(); | ||||
|  | ||||
| 		internal static int MaxLength | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				int iMaxLen = 0; | ||||
| 				foreach(int iLen in m_dicts.Keys) | ||||
| 				{ | ||||
| 					if(iLen > iMaxLen) iMaxLen = iLen; | ||||
| 				} | ||||
|         internal static int MaxLength | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 Debug.Assert(g_dicts.Count > 0); // Should be initialized | ||||
|  | ||||
| 				return iMaxLen; | ||||
| 			} | ||||
| 		} | ||||
|                 int iMaxLen = 0; | ||||
|                 foreach (int iLen in g_dicts.Keys) | ||||
|                 { | ||||
|                     if (iLen > iMaxLen) iMaxLen = iLen; | ||||
|                 } | ||||
|  | ||||
| 		internal static bool ContainsLength(int nLength) | ||||
| 		{ | ||||
| 			Dictionary<string, bool> dDummy; | ||||
| 			return m_dicts.TryGetValue(nLength, out dDummy); | ||||
| 		} | ||||
|                 return iMaxLen; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 		public static bool IsPopularPassword(char[] vPassword) | ||||
| 		{ | ||||
| 			ulong uDummy; | ||||
| 			return IsPopularPassword(vPassword, out uDummy); | ||||
| 		} | ||||
|         internal static bool ContainsLength(int nLength) | ||||
|         { | ||||
|             Dictionary<char[], bool> dDummy; | ||||
|             return g_dicts.TryGetValue(nLength, out dDummy); | ||||
|         } | ||||
|  | ||||
| 		public static bool IsPopularPassword(char[] vPassword, out ulong uDictSize) | ||||
| 		{ | ||||
| 			if(vPassword == null) throw new ArgumentNullException("vPassword"); | ||||
| 			if(vPassword.Length == 0) { uDictSize = 0; return false; } | ||||
|         public static bool IsPopularPassword(char[] vPassword) | ||||
|         { | ||||
|             ulong uDummy; | ||||
|             return IsPopularPassword(vPassword, out uDummy); | ||||
|         } | ||||
|  | ||||
| 			string str = new string(vPassword); | ||||
|         public static bool IsPopularPassword(char[] vPassword, out ulong uDictSize) | ||||
|         { | ||||
|             if (vPassword == null) throw new ArgumentNullException("vPassword"); | ||||
|             if (vPassword.Length == 0) { uDictSize = 0; return false; } | ||||
|  | ||||
| 			try { return IsPopularPasswordPriv(str, out uDictSize); } | ||||
| 			catch(Exception) { Debug.Assert(false); } | ||||
| #if DEBUG | ||||
|             Array.ForEach(vPassword, ch => Debug.Assert(ch == char.ToLower(ch))); | ||||
| #endif | ||||
|  | ||||
| 			uDictSize = 0; | ||||
| 			return false; | ||||
| 		} | ||||
|             try { return IsPopularPasswordPriv(vPassword, out uDictSize); } | ||||
|             catch (Exception) { Debug.Assert(false); } | ||||
|  | ||||
| 		private static bool IsPopularPasswordPriv(string str, out ulong uDictSize) | ||||
| 		{ | ||||
| 			Debug.Assert(m_dicts.Count > 0); // Should be initialized with data | ||||
|             uDictSize = 0; | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
| 			Dictionary<string, bool> d; | ||||
| 			if(!m_dicts.TryGetValue(str.Length, out d)) | ||||
| 			{ | ||||
| 				uDictSize = 0; | ||||
| 				return false; | ||||
| 			} | ||||
|         private static bool IsPopularPasswordPriv(char[] vPassword, out ulong uDictSize) | ||||
|         { | ||||
|             Debug.Assert(g_dicts.Count > 0); // Should be initialized with data | ||||
|  | ||||
| 			uDictSize = (ulong)d.Count; | ||||
| 			return d.ContainsKey(str); | ||||
| 		} | ||||
|             Dictionary<char[], bool> d; | ||||
|             if (!g_dicts.TryGetValue(vPassword.Length, out d)) | ||||
|             { | ||||
|                 uDictSize = 0; | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
| 		public static void Add(byte[] pbData, bool bGZipped) | ||||
| 		{ | ||||
| 			try | ||||
| 			{ | ||||
| 				if(bGZipped) | ||||
| 					pbData = MemUtil.Decompress(pbData); | ||||
|             uDictSize = (ulong)d.Count; | ||||
|             return d.ContainsKey(vPassword); | ||||
|         } | ||||
|  | ||||
| 				string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length); | ||||
| 				if(string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; } | ||||
|         public static void Add(byte[] pbData, bool bGZipped) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 if (bGZipped) | ||||
|                     pbData = MemUtil.Decompress(pbData); | ||||
|  | ||||
| 				if(!char.IsWhiteSpace(strData[strData.Length - 1])) | ||||
| 					strData += "\n"; | ||||
|                 string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length); | ||||
|                 if (string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; } | ||||
|  | ||||
| 				StringBuilder sb = new StringBuilder(); | ||||
| 				for(int i = 0; i < strData.Length; ++i) | ||||
| 				{ | ||||
| 					char ch = strData[i]; | ||||
|                 StringBuilder sb = new StringBuilder(); | ||||
|                 for (int i = 0; i <= strData.Length; ++i) | ||||
|                 { | ||||
|                     char ch = ((i == strData.Length) ? ' ' : strData[i]); | ||||
|  | ||||
| 					if(char.IsWhiteSpace(ch)) | ||||
| 					{ | ||||
| 						int cc = sb.Length; | ||||
| 						if(cc > 0) | ||||
| 						{ | ||||
| 							string strWord = sb.ToString(); | ||||
| 							Debug.Assert(strWord.Length == cc); | ||||
|                     if (char.IsWhiteSpace(ch)) | ||||
|                     { | ||||
|                         int cc = sb.Length; | ||||
|                         if (cc > 0) | ||||
|                         { | ||||
|                             char[] vWord = new char[cc]; | ||||
|                             sb.CopyTo(0, vWord, 0, cc); | ||||
|  | ||||
| 							Dictionary<string, bool> d; | ||||
| 							if(!m_dicts.TryGetValue(cc, out d)) | ||||
| 							{ | ||||
| 								d = new Dictionary<string, bool>(); | ||||
| 								m_dicts[cc] = d; | ||||
| 							} | ||||
|                             Dictionary<char[], bool> d; | ||||
|                             if (!g_dicts.TryGetValue(cc, out d)) | ||||
|                             { | ||||
|                                 d = new Dictionary<char[], bool>(MemUtil.ArrayHelperExOfChar); | ||||
|                                 g_dicts[cc] = d; | ||||
|                             } | ||||
|  | ||||
| 							d[strWord] = true; | ||||
| 							sb.Remove(0, cc); | ||||
| 						} | ||||
| 					} | ||||
| 					else sb.Append(char.ToLower(ch)); | ||||
| 				} | ||||
| 			} | ||||
| 			catch(Exception) { Debug.Assert(false); } | ||||
| 		} | ||||
| 	} | ||||
|                             d[vWord] = true; | ||||
|                             sb.Remove(0, cc); | ||||
|                         } | ||||
|                     } | ||||
|                     else sb.Append(char.ToLower(ch)); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception) { Debug.Assert(false); } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -46,4 +46,12 @@ namespace KeePassLib.Delegates | ||||
| 	public delegate void VoidDelegate(); | ||||
|  | ||||
| 	public delegate string StrPwEntryDelegate(string str, PwEntry pe); | ||||
|  | ||||
|     public delegate TResult GFunc<TResult>(); | ||||
|     public delegate TResult GFunc<T, TResult>(T o); | ||||
|     public delegate TResult GFunc<T1, T2, TResult>(T1 o1, T2 o2); | ||||
|     public delegate TResult GFunc<T1, T2, T3, TResult>(T1 o1, T2 o2, T3 o3); | ||||
|     public delegate TResult GFunc<T1, T2, T3, T4, TResult>(T1 o1, T2 o2, T3 o3, T4 o4); | ||||
|     public delegate TResult GFunc<T1, T2, T3, T4, T5, TResult>(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5); | ||||
|     public delegate TResult GFunc<T1, T2, T3, T4, T5, T6, TResult>(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5, T6 o6); | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net8.0-android</TargetFramework> | ||||
|     <TargetFramework>net9.0-android</TargetFramework> | ||||
|     <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|   | ||||
| @@ -116,12 +116,23 @@ namespace keepass2android | ||||
| 				 | ||||
| 			Intent sendIntent = new Intent(); | ||||
| 			sendIntent.SetAction(Intent.ActionSend); | ||||
| 			sendIntent.PutExtra(Intent.ExtraText, File.ReadAllText(LogFilename)); | ||||
|             string logText = File.ReadAllText(LogFilename); | ||||
|  | ||||
|             sendIntent.PutExtra(Intent.ExtraText, logText); | ||||
| 			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...")); | ||||
| 		} | ||||
|             try | ||||
|             { | ||||
|                 ctx.StartActivity(Intent.CreateChooser(sendIntent, "Send log to...")); | ||||
|             } | ||||
|             catch (Exception e) | ||||
|             { | ||||
| 				Toast.MakeText(ctx, $"Error sending log of length {logText.Length} bytes: " + e.Message, ToastLength.Long)?.Show(); | ||||
|  | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         public static void LogTask(object task, string activityName) | ||||
|         { | ||||
|   | ||||
| @@ -83,6 +83,7 @@ namespace KeePassLib.Serialization | ||||
| 			if (m_bUsedOnce) | ||||
| 				throw new InvalidOperationException("Do not reuse KdbxFile objects!"); | ||||
| 			m_bUsedOnce = true; | ||||
| 			Kp2aLog.Log("Starting to load KDBX file..."); | ||||
|  | ||||
| #if KDBX_BENCHMARK | ||||
| 			Stopwatch swTime = Stopwatch.StartNew(); | ||||
| @@ -257,7 +258,8 @@ namespace KeePassLib.Serialization | ||||
| 			MessageService.ShowInfo("Loading KDBX took " + | ||||
| 				swTime.ElapsedMilliseconds.ToString() + " ms."); | ||||
| #endif | ||||
| 		} | ||||
|             Kp2aLog.Log("Finished loading KDBX file."); | ||||
|         } | ||||
|  | ||||
| 		private void CommonCleanUpRead(List<Stream> lStreams, HashingStreamEx sHashing) | ||||
| 		{ | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -95,7 +95,7 @@ namespace Kp2aAutofillParserTest | ||||
|             StructureParserBase<TestInputField> parser = | ||||
|                 new StructureParserBase<TestInputField>(new TestLogger(), new TestDalSourceTrustAll()); | ||||
|  | ||||
|             var result = parser.ParseForFill(false, autofillView); | ||||
|             var result = parser.ParseForFill(autofillView); | ||||
|             if (expectedPackageName != null) | ||||
|                 Assert.Equal(expectedPackageName, result.PackageName); | ||||
|             if (expectedWebDomain != null) | ||||
|   | ||||
| @@ -58,7 +58,8 @@ | ||||
|       "IsFocused": false, | ||||
|       "InputType": 97, | ||||
|       "HtmlInfoTag": null, | ||||
|       "HtmlInfoTypeAttribute": null | ||||
|       "HtmlInfoTypeAttribute": null, | ||||
|       "ExpectedAssignedHints": [ "username" ] | ||||
|     }, | ||||
|     { | ||||
|       "IdEntry": "password_text_input_layout", | ||||
| @@ -81,6 +82,7 @@ | ||||
|       "InputType": 129, | ||||
|       "HtmlInfoTag": null, | ||||
|       "HtmlInfoTypeAttribute": null, | ||||
|       "ExpectedAssignedHints": [ "password" ] | ||||
|  | ||||
|     }, | ||||
|     { | ||||
|   | ||||
| @@ -476,8 +476,16 @@ namespace Kp2aAutofillParser | ||||
|  | ||||
|             foreach (var field in autofillFields.HintMap.Values.Distinct()) | ||||
|             { | ||||
|                 if (field == null || field.AutofillHints == null) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|                 foreach (var hint in field.AutofillHints) | ||||
|                 { | ||||
|                     if (hint == null) | ||||
|                     { | ||||
|                         continue; | ||||
|                     } | ||||
|                     if (GetPartitionIndex(hint) == partitionIndex) | ||||
|                     { | ||||
|                         filteredCollection.Add(field); | ||||
| @@ -793,14 +801,14 @@ namespace Kp2aAutofillParser | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public AutofillTargetId ParseForFill(bool isManual, AutofillView<FieldT> autofillView) | ||||
|         public AutofillTargetId ParseForFill(AutofillView<FieldT> autofillView) | ||||
|         { | ||||
|             return Parse(true, isManual, autofillView); | ||||
|             return Parse(true, autofillView); | ||||
|         } | ||||
|  | ||||
|         public AutofillTargetId ParseForSave(AutofillView<FieldT> autofillView) | ||||
|         { | ||||
|             return Parse(false, true, autofillView); | ||||
|             return Parse(false, autofillView); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
| @@ -808,8 +816,7 @@ namespace Kp2aAutofillParser | ||||
|         /// </summary> | ||||
|         /// <returns>The parse.</returns> | ||||
|         /// <param name="forFill">If set to <c>true</c> for fill.</param> | ||||
|         /// <param name="isManualRequest"></param> | ||||
|         protected virtual AutofillTargetId Parse(bool forFill, bool isManualRequest, AutofillView<FieldT> autofillView) | ||||
|         protected virtual AutofillTargetId Parse(bool forFill, AutofillView<FieldT> autofillView) | ||||
|         { | ||||
|             AutofillTargetId result = new AutofillTargetId() | ||||
|             { | ||||
| @@ -876,8 +883,9 @@ namespace Kp2aAutofillParser | ||||
|                      | ||||
|                 } | ||||
|  | ||||
|                 //for "heuristic determination" we demand that one of the filled fields is focused: | ||||
|                 if (passwordFields.Concat(usernameFields).Any(f => f.IsFocused)) | ||||
|                 //for "heuristic determination" we demand that there is a password field or one of the username fields is focused: | ||||
|                 //Note that "IsFocused" might be false even when tapping the field. It might require long-press to autofill. | ||||
|                 if (passwordFields.Any() || usernameFields.Any(f => f.IsFocused)) | ||||
|                 { | ||||
|                     foreach (var uf in usernameFields) | ||||
|                         AddFieldToHintMap(uf, new string[] { AutofillHintsHelper.AutofillHintUsername }); | ||||
|   | ||||
| @@ -29,6 +29,14 @@ namespace keepass2android | ||||
|  | ||||
| 	} | ||||
|  | ||||
|  | ||||
|     public enum MessageSeverity | ||||
|     { | ||||
|         Info, | ||||
|         Warning, | ||||
|         Error | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| 	/// Interface through which Activities and the logic layer can access some app specific functionalities and Application static data | ||||
| 	/// </summary> | ||||
| @@ -102,10 +110,13 @@ namespace keepass2android | ||||
| 		                    Context ctx, | ||||
| 		                    string messageSuffix = ""); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Returns a Handler object which can run tasks on the UI thread | ||||
| 		/// </summary> | ||||
| 		Handler UiThreadHandler { get; } | ||||
|         void ShowMessage(Context ctx, int resourceId, MessageSeverity severity); | ||||
|         void ShowMessage(Context ctx, string text, MessageSeverity severity); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Returns a Handler object which can run tasks on the UI thread | ||||
|         /// </summary> | ||||
|         Handler UiThreadHandler { get; } | ||||
|  | ||||
| 		IProgressDialog CreateProgressDialog(Context ctx); | ||||
|  | ||||
| @@ -129,6 +140,10 @@ namespace keepass2android | ||||
|  | ||||
|  | ||||
| #endif | ||||
|         int WebDavChunkedUploadSize | ||||
|         { | ||||
|             get; | ||||
|         } | ||||
| 	     | ||||
| 	} | ||||
| } | ||||
| @@ -13,7 +13,7 @@ using Android.Content.PM; | ||||
| using Android.OS; | ||||
| using Android.Preferences; | ||||
| using Java.IO; | ||||
|  | ||||
| using KeePass.Util; | ||||
| using KeePassLib.Serialization; | ||||
| using KeePassLib.Utility; | ||||
| using File = System.IO.File; | ||||
| @@ -121,7 +121,7 @@ namespace keepass2android.Io | ||||
| 			var response = ex.Response as HttpWebResponse; | ||||
| 			if ((response != null) && (response.StatusCode == HttpStatusCode.NotFound)) | ||||
| 			{ | ||||
| 				throw new FileNotFoundException(ex.Message, ioc.Path, ex); | ||||
| 				throw new FileNotFoundException(ExceptionUtil.GetErrorMessage(ex), ioc.Path, ex); | ||||
| 			} | ||||
| 			if (ex.Status == WebExceptionStatus.TrustFailure) | ||||
| 			{ | ||||
|   | ||||
| @@ -15,7 +15,9 @@ namespace keepass2android.Io | ||||
| 	    { | ||||
| 	        get { return false; } | ||||
| 	    } | ||||
| 	} | ||||
|  | ||||
|         static public bool IsConfigured => !string.IsNullOrEmpty(AppKey) && !string.IsNullOrEmpty(AppSecret); | ||||
|     } | ||||
|  | ||||
| 	public partial class DropboxAppFolderFileStorage: JavaFileStorage | ||||
| 	{ | ||||
| @@ -29,6 +31,7 @@ namespace keepass2android.Io | ||||
| 	        get { return false; } | ||||
| 	    } | ||||
|  | ||||
|         static public bool IsConfigured => !string.IsNullOrEmpty(AppKey) && !string.IsNullOrEmpty(AppSecret); | ||||
|     } | ||||
| 	 | ||||
| } | ||||
|   | ||||
| @@ -1,13 +0,0 @@ | ||||
| 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"; | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/Kp2aBusinessLogic/Io/GenerateSecrets.targets
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/Kp2aBusinessLogic/Io/GenerateSecrets.targets
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| <Project> | ||||
|   <Target Name="GenerateDropboxSecrets" BeforeTargets="BeforeCompile" | ||||
|   Inputs="@(DropboxSecretLines)" | ||||
|   Outputs="DropboxFileStorage.g.cs"> | ||||
|  | ||||
|     <WriteLinesToFile | ||||
|     File="Io/DropboxFileStorage.g.cs" | ||||
|       Lines="@(DropboxSecretLines->'%(Text)')" | ||||
|       Overwrite="true" | ||||
|       /> | ||||
|   </Target> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <DropboxSecretLines Include="GeneratedDropboxSecrets"> | ||||
|       <Text>namespace keepass2android.Io { | ||||
|     public partial class DropboxFileStorage { | ||||
|       private const string AppKey = "$(DropboxAppKey)"; | ||||
|       private const string AppSecret = "$(DropboxAppSecret)"; | ||||
|     } | ||||
|     public partial class DropboxAppFolderFileStorage { | ||||
|       private const string AppKey = "$(DropboxAppFolderAppKey)"; | ||||
|       private const string AppSecret = "$(DropboxAppFolderAppSecret)"; | ||||
|     } | ||||
|   }</Text> | ||||
|     </DropboxSecretLines> | ||||
|   </ItemGroup> | ||||
| </Project> | ||||
| @@ -13,6 +13,7 @@ using Keepass2android.Javafilestorage; | ||||
| #endif | ||||
| using Exception = System.Exception; | ||||
| using FileNotFoundException = Java.IO.FileNotFoundException; | ||||
| using KeePass.Util; | ||||
|  | ||||
| namespace keepass2android.Io | ||||
| { | ||||
| @@ -42,7 +43,7 @@ namespace keepass2android.Io | ||||
| 			} | ||||
| 			catch (FileNotFoundException e) | ||||
| 			{ | ||||
| 				throw new System.IO.FileNotFoundException(e.Message, e); | ||||
| 				throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e); | ||||
| 			} | ||||
| 			catch (Java.Lang.Exception e) | ||||
| 			{ | ||||
| @@ -122,7 +123,7 @@ namespace keepass2android.Io | ||||
|  | ||||
| 		} | ||||
|  | ||||
| 		public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) | ||||
| 		public virtual IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) | ||||
| 		{ | ||||
| 			return new JavaFileStorageWriteTransaction(IocToPath(ioc), useFileTransaction, this); | ||||
| 		} | ||||
| @@ -195,7 +196,7 @@ namespace keepass2android.Io | ||||
| 			} | ||||
| 			catch (FileNotFoundException e) | ||||
| 			{ | ||||
| 				throw new System.IO.FileNotFoundException(e.Message, e); | ||||
| 				throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e); | ||||
| 			} | ||||
| 			catch (Java.Lang.Exception e) | ||||
| 			{ | ||||
| @@ -214,7 +215,7 @@ namespace keepass2android.Io | ||||
| 			} | ||||
| 			catch (FileNotFoundException e) | ||||
| 			{ | ||||
| 				throw new System.IO.FileNotFoundException(e.Message, e); | ||||
| 				throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e); | ||||
| 			} | ||||
| 			catch (Java.Lang.Exception e) | ||||
| 			{ | ||||
| @@ -244,7 +245,7 @@ namespace keepass2android.Io | ||||
| 			} | ||||
| 			catch (FileNotFoundException e) | ||||
| 			{ | ||||
| 				throw new System.IO.FileNotFoundException(e.Message, e); | ||||
| 				throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e); | ||||
| 			} | ||||
| 			catch (Java.Lang.Exception e) | ||||
| 			{ | ||||
|   | ||||
| @@ -8,6 +8,8 @@ using Android.Content; | ||||
| using Android.OS; | ||||
| using FluentFTP; | ||||
| using FluentFTP.Exceptions; | ||||
| using FluentFTP.GnuTLS; | ||||
| using KeePass.Util; | ||||
| using KeePassLib; | ||||
| using KeePassLib.Serialization; | ||||
| using KeePassLib.Utility; | ||||
| @@ -127,7 +129,7 @@ namespace keepass2android.Io | ||||
| 				var ftpEx = (FtpCommandException) exception; | ||||
|  | ||||
| 				if (ftpEx.CompletionCode == "550") | ||||
| 					throw new FileNotFoundException(exception.Message, exception); | ||||
| 					throw new FileNotFoundException(ExceptionUtil.GetErrorMessage(exception), exception); | ||||
| 			} | ||||
|  | ||||
| 			return exception; | ||||
| @@ -139,6 +141,7 @@ namespace keepass2android.Io | ||||
| 			var settings = ConnectionSettings.FromIoc(ioc); | ||||
| 			 | ||||
| 			FtpClient client = new FtpClient(); | ||||
|             client.Config.CustomStream = typeof(GnuTlsStream); | ||||
| 			client.Config.RetryAttempts = 3; | ||||
| 			if ((settings.Username.Length > 0) || (settings.Password.Length > 0)) | ||||
| 				client.Credentials = new NetworkCredential(settings.Username, settings.Password); | ||||
|   | ||||
| @@ -3,6 +3,7 @@ using System.Reflection; | ||||
| using System.Text; | ||||
| using Android.Content; | ||||
| using Android.Util; | ||||
| using KeePass.Util; | ||||
| using keepass2android.Io.ItemLocation; | ||||
| using KeePassLib.Serialization; | ||||
| using KeePassLib.Utility; | ||||
| @@ -522,10 +523,10 @@ namespace keepass2android.Io | ||||
|         { | ||||
|  | ||||
|             if (e.IsMatch(GraphErrorCode.ItemNotFound.ToString())) | ||||
|                 return new FileNotFoundException(e.Message); | ||||
|                 return new FileNotFoundException(ExceptionUtil.GetErrorMessage(e)); | ||||
|             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 new FileNotFoundException(ExceptionUtil.GetErrorMessage(e)); | ||||
|             return e; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -16,20 +16,32 @@ namespace keepass2android.Io | ||||
|     /// </summary> | ||||
| 	public class OneDriveFileStorage: IFileStorage | ||||
| 	{ | ||||
| 		 | ||||
| 		public IEnumerable<string> SupportedProtocols | ||||
|         public OneDriveFileStorage(IKp2aApp app) | ||||
|         { | ||||
|             _app = app; | ||||
|         } | ||||
|  | ||||
|         private readonly IKp2aApp _app; | ||||
|  | ||||
|         public IEnumerable<string> SupportedProtocols | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				yield return "skydrive"; | ||||
| 				yield return "onedrive"; | ||||
| 			} | ||||
|             } | ||||
| 		} | ||||
|  | ||||
|         private Exception GetDeprecatedMessage() | ||||
|         string GetDeprecatedMessage() | ||||
|         { | ||||
|             return | ||||
|                 "You have opened your file through a deprecated Microsoft API. Please select Change database, Open Database and then select OneDrive again."; | ||||
|         } | ||||
|  | ||||
|         private Exception GetDeprecatedException() | ||||
|         { | ||||
|             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."); | ||||
|                 GetDeprecatedMessage()); | ||||
|         } | ||||
|  | ||||
| 	    public bool UserShouldBackup | ||||
| @@ -39,133 +51,132 @@ namespace keepass2android.Io | ||||
|  | ||||
|         public void Delete(IOConnectionInfo ioc) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             throw GetDeprecatedException(); | ||||
|         } | ||||
|  | ||||
|         public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             throw GetDeprecatedException(); | ||||
|         } | ||||
|  | ||||
|         public string GetCurrentFileVersionFast(IOConnectionInfo ioc) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             throw GetDeprecatedException(); | ||||
|         } | ||||
|  | ||||
|         public Stream OpenFileForRead(IOConnectionInfo ioc) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             throw GetDeprecatedException(); | ||||
|         } | ||||
|  | ||||
|         public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             throw GetDeprecatedException(); | ||||
|         } | ||||
|  | ||||
|         public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             throw GetDeprecatedException(); | ||||
|         } | ||||
|  | ||||
|         public string GetFileExtension(IOConnectionInfo ioc) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             throw GetDeprecatedException(); | ||||
|         } | ||||
|  | ||||
|         public bool RequiresCredentials(IOConnectionInfo ioc) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         public void CreateDirectory(IOConnectionInfo ioc, string newDirName) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             throw GetDeprecatedException(); | ||||
|         } | ||||
|  | ||||
|         public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             throw GetDeprecatedException(); | ||||
|         } | ||||
|  | ||||
|         public FileDescription GetFileDescription(IOConnectionInfo ioc) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             throw GetDeprecatedException(); | ||||
|         } | ||||
|  | ||||
|         public bool RequiresSetup(IOConnectionInfo ioConnection) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         public string IocToPath(IOConnectionInfo ioc) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             throw GetDeprecatedException(); | ||||
|         } | ||||
|  | ||||
|         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(); | ||||
|             _app.ShowMessage(activity.Activity, GetDeprecatedMessage(), MessageSeverity.Error); | ||||
|  | ||||
|         } | ||||
|  | ||||
|         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(); | ||||
|             return "File using deprecated Microsoft API. Please update."; | ||||
|         } | ||||
|  | ||||
|         public string CreateFilePath(string parent, string newFilename) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             throw GetDeprecatedException(); | ||||
|         } | ||||
|  | ||||
|         public IOConnectionInfo GetParentPath(IOConnectionInfo ioc) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             throw GetDeprecatedException(); | ||||
|         } | ||||
|  | ||||
|         public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             throw GetDeprecatedException(); | ||||
|         } | ||||
|  | ||||
|         public bool IsPermanentLocation(IOConnectionInfo ioc) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             throw GetDeprecatedException(); | ||||
|         } | ||||
|  | ||||
|         public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut<UiStringKey> reason = null) | ||||
|         { | ||||
|             throw GetDeprecatedMessage(); | ||||
|             throw GetDeprecatedException(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										617
									
								
								src/Kp2aBusinessLogic/Io/SmbFileStorage.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										617
									
								
								src/Kp2aBusinessLogic/Io/SmbFileStorage.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,617 @@ | ||||
| #if !NoNet | ||||
| using System.Net; | ||||
| using Android.Content; | ||||
| using keepass2android; | ||||
| using keepass2android.Io; | ||||
| using KeePassLib.Serialization; | ||||
| using SMBLibrary.Client; | ||||
| using SMBLibrary; | ||||
| using FileAttributes = SMBLibrary.FileAttributes; | ||||
| using KeePassLib.Utility; | ||||
| using Java.Nio.FileNio; | ||||
|  | ||||
| namespace Kp2aBusinessLogic.Io | ||||
| { | ||||
|     public class SmbFileStorage : IFileStorage | ||||
|     { | ||||
|         public IEnumerable<string> SupportedProtocols | ||||
|         { | ||||
|             get { yield return "smb"; } | ||||
|         } | ||||
|  | ||||
|         public bool UserShouldBackup | ||||
|         { | ||||
|             get { return false; } | ||||
|         } | ||||
|  | ||||
|         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 struct SmbConnectionInfo | ||||
|         { | ||||
|             public string Host; | ||||
|             public string Username; | ||||
|             public string Password; | ||||
|             public string? Domain; | ||||
|             public string? Share; | ||||
|             public string? LocalPath; | ||||
|  | ||||
|             public static SmbConnectionInfo FromUrlAndCredentials(string url, string username, string password, string? domain) | ||||
|             { | ||||
|                 string userDomain = username; | ||||
|                 if (domain != null) | ||||
|                 { | ||||
|                     userDomain = domain + "\\" + username; | ||||
|                 } | ||||
|                 if (url.StartsWith("smb://")) | ||||
|                 { | ||||
|                     url = url.Substring(6); | ||||
|                 } | ||||
|  | ||||
|                 if (url.StartsWith("\\\\")) | ||||
|                 { | ||||
|                     url = url.Substring(2); | ||||
|                 } | ||||
|  | ||||
|                 url = url.Replace("\\", "/"); | ||||
|              | ||||
|                 string fullPath = "smb://" + WebUtility.UrlEncode(userDomain) + ":" + WebUtility.UrlEncode(password) + "@" + url; | ||||
|                 return new SmbConnectionInfo(new IOConnectionInfo() { Path = fullPath} ); | ||||
|             } | ||||
|  | ||||
|  | ||||
|             public SmbConnectionInfo(IOConnectionInfo ioc) | ||||
|             { | ||||
|                 string fullpath = ioc.Path; | ||||
|                 if (!fullpath.StartsWith("smb://")) | ||||
|                 { | ||||
|                     throw new Exception("Invalid smb path!"); | ||||
|                 } | ||||
|  | ||||
|                 fullpath = fullpath.Substring(6); | ||||
|                 string[] authAndPath = fullpath.Split('@'); | ||||
|                 if (authAndPath.Length != 2) | ||||
|                 { | ||||
|                     throw new Exception("Invalid smb path!"); | ||||
|                 } | ||||
|  | ||||
|                 string[] userAndPwd = authAndPath[0].Split(':'); | ||||
|                 if (userAndPwd.Length != 2) | ||||
|                 { | ||||
|                     throw new Exception("Invalid smb path!"); | ||||
|                 } | ||||
|  | ||||
|                 string[] pathParts = authAndPath[1].Split('/'); | ||||
|                 if (pathParts.Length < 1) | ||||
|                 { | ||||
|                     throw new Exception("Invalid smb path!"); | ||||
|                 } | ||||
|  | ||||
|                 Host = pathParts[0]; | ||||
|                 if (pathParts.Length > 1) | ||||
|                 { | ||||
|                     Share = pathParts[1]; | ||||
|                 } | ||||
|                 LocalPath = String.Join("/", pathParts.Skip(2)); | ||||
|                 if (LocalPath.EndsWith("/")) | ||||
|                 { | ||||
|                     LocalPath = LocalPath.Substring(0, LocalPath.Length - 1); | ||||
|                 } | ||||
|              | ||||
|                 Username = WebUtility.UrlDecode(userAndPwd[0]); | ||||
|                 if (Username.Contains("\\")) | ||||
|                 { | ||||
|                     string[] domainAndUser = Username.Split('\\'); | ||||
|                     Domain = domainAndUser[0]; | ||||
|                     Username = domainAndUser[1]; | ||||
|                 } | ||||
|                 else Domain = null; | ||||
|  | ||||
|                 Password = WebUtility.UrlDecode(userAndPwd[1]); | ||||
|             } | ||||
|  | ||||
|             public string ToPath() | ||||
|             { | ||||
|                 string domainUser = Username; | ||||
|                 if (Domain != null) | ||||
|                 { | ||||
|                     domainUser = Domain + "\\" + Username; | ||||
|                 } | ||||
|  | ||||
|                 return "smb://" + WebUtility.UrlEncode(domainUser) + ":" + WebUtility.UrlEncode(Password) + "@" + Host + | ||||
|                        "/" + Share + "/" + LocalPath; | ||||
|             } | ||||
|  | ||||
|             public string GetPathWithoutCredentials() | ||||
|             { | ||||
|                 return "smb://" + Host + "/" + Share + "/" + LocalPath; | ||||
|             } | ||||
|  | ||||
|             public string GetLocalSmbPath() | ||||
|             { | ||||
|                 return LocalPath?.Replace("/", "\\") ?? ""; | ||||
|             } | ||||
|  | ||||
|             public SmbConnectionInfo GetParent() | ||||
|             { | ||||
|                 SmbConnectionInfo parent = new SmbConnectionInfo | ||||
|                 { | ||||
|                     Host = Host, | ||||
|                     Username = Username, | ||||
|                     Password = Password, | ||||
|                     Domain = Domain, | ||||
|                     Share = Share | ||||
|                 }; | ||||
|                 string[] pathParts = LocalPath?.Split('/') ?? []; | ||||
|                 if (pathParts.Length > 0) | ||||
|                 { | ||||
|                     parent.LocalPath = string.Join("/", pathParts.Take(pathParts.Length - 1)); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     parent.LocalPath = ""; | ||||
|                     parent.Share = ""; | ||||
|                 } | ||||
|  | ||||
|                 return parent; | ||||
|             } | ||||
|  | ||||
|             public string Stem() | ||||
|             { | ||||
|                 return LocalPath?.Split('/').Last() ?? ""; | ||||
|             } | ||||
|  | ||||
|  | ||||
|             public SmbConnectionInfo GetChild(string childName) | ||||
|             { | ||||
|                 SmbConnectionInfo child = new SmbConnectionInfo(); | ||||
|                 child.Host = Host; | ||||
|                 child.Username = Username; | ||||
|                 child.Password = Password; | ||||
|                 child.Domain = Domain; | ||||
|                 if (string.IsNullOrEmpty(Share)) | ||||
|                 { | ||||
|                     child.Share = childName; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|  | ||||
|                     child.Share = Share; | ||||
|                     var pathPartsList = LocalPath?.Split('/').Where(p => !string.IsNullOrEmpty(p)).ToList() ?? []; | ||||
|                     pathPartsList.Add(childName); | ||||
|                     child.LocalPath = string.Join("/", pathPartsList); | ||||
|                 } | ||||
|  | ||||
|                 return child; | ||||
|             } | ||||
|  | ||||
|             public string ToDisplayString() | ||||
|             { | ||||
|                 return "smb://" + Host + "/" + Share + "/" + LocalPath; | ||||
|  | ||||
|             } | ||||
|         } | ||||
|  | ||||
|  | ||||
|         class SmbConnection: IDisposable | ||||
|         { | ||||
|             public SmbConnection(SmbConnectionInfo info) | ||||
|             { | ||||
|                 _isLoggedIn = false; | ||||
|                 var isConnected = Client.Connect(info.Host, SMBTransportType.DirectTCPTransport); | ||||
|                 if (!isConnected) | ||||
|                 { | ||||
|                     throw new Exception($"Failed to connect to SMB server {info.Host}"); | ||||
|                 } | ||||
|  | ||||
|                 var status = Client.Login(info.Domain ?? string.Empty, info.Username, info.Password); | ||||
|                 if (status != NTStatus.STATUS_SUCCESS) | ||||
|                 { | ||||
|                     throw new Exception($"Failed to login to SMB as {info.Username}"); | ||||
|                 } | ||||
|  | ||||
|                 _isLoggedIn = true; | ||||
|  | ||||
|                 if (!string.IsNullOrEmpty(info.Share)) | ||||
|                 { | ||||
|                     FileStore = Client.TreeConnect(info.Share, out status); | ||||
|                 } | ||||
|                  | ||||
|             } | ||||
|  | ||||
|  | ||||
|  | ||||
|             public readonly SMB2Client Client = new SMB2Client(); | ||||
|  | ||||
|  | ||||
|             public readonly ISMBFileStore? FileStore; | ||||
|             private readonly bool _isLoggedIn; | ||||
|  | ||||
|             public void Dispose() | ||||
|             { | ||||
|                 FileStore?.Disconnect(); | ||||
|                  | ||||
|                 if (_isLoggedIn) | ||||
|                     Client.Logoff(); | ||||
|  | ||||
|                 if (!Client.IsConnected) return; | ||||
|                 Client.Disconnect(); | ||||
|  | ||||
|  | ||||
|             } | ||||
|         } | ||||
|      | ||||
|  | ||||
|  | ||||
|     public Stream OpenFileForRead(IOConnectionInfo ioc) | ||||
|         { | ||||
|  | ||||
|             SmbConnectionInfo info = new SmbConnectionInfo(ioc); | ||||
|             using SmbConnection conn = new SmbConnection(info); | ||||
|  | ||||
|             if (conn.FileStore == null) | ||||
|             { | ||||
|                 throw new Exception($"Failed to read to {info.GetPathWithoutCredentials()}"); | ||||
|             } | ||||
|  | ||||
|  | ||||
|             NTStatus status = conn.FileStore.CreateFile(out var fileHandle, out _, info.GetLocalSmbPath(), | ||||
|                 AccessMask.GENERIC_READ | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.Read, | ||||
|                 CreateDisposition.FILE_OPEN, | ||||
|                 CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null); | ||||
|  | ||||
|             if (status != NTStatus.STATUS_SUCCESS) | ||||
|             { | ||||
|                 throw new Exception($"Failed to open file {info.LocalPath}"); | ||||
|             } | ||||
|  | ||||
|             var stream = new MemoryStream(); | ||||
|             long bytesRead = 0; | ||||
|             while (true) | ||||
|             { | ||||
|                 status = conn.FileStore.ReadFile(out var data, fileHandle, bytesRead, (int)conn.Client.MaxReadSize); | ||||
|                 if (status != NTStatus.STATUS_SUCCESS && status != NTStatus.STATUS_END_OF_FILE) | ||||
|                 { | ||||
|                     throw new Exception("Failed to read from file"); | ||||
|                 } | ||||
|  | ||||
|                 if (status == NTStatus.STATUS_END_OF_FILE || data.Length == 0) | ||||
|                 { | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 bytesRead += data.Length; | ||||
|                 stream.Write(data, 0, data.Length); | ||||
|             } | ||||
|  | ||||
|             stream.Seek(0, SeekOrigin.Begin); | ||||
|             return stream; | ||||
|         } | ||||
|  | ||||
|  | ||||
|         class SmbFileStorageWriteTransaction : IWriteTransaction | ||||
|         { | ||||
|             private bool UseFileTransaction { get; } | ||||
|             private readonly string _path; | ||||
|             private readonly string _uploadPath; | ||||
|             private readonly SmbFileStorage _fileStorage; | ||||
|             private MemoryStream? _memoryStream; | ||||
|  | ||||
|             public SmbFileStorageWriteTransaction(string path, SmbFileStorage fileStorage, bool useFileTransaction) | ||||
|             { | ||||
|                 UseFileTransaction = useFileTransaction; | ||||
|                 _path = path; | ||||
|                 if (useFileTransaction) | ||||
|                 { | ||||
|                     _uploadPath = _path + Guid.NewGuid().ToString().Substring(0, 8) + ".tmp"; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     _uploadPath = _path; | ||||
|                 } | ||||
|                  | ||||
|  | ||||
|                 _fileStorage = fileStorage; | ||||
|                 _memoryStream = null; | ||||
|             } | ||||
|  | ||||
|             public void Dispose() | ||||
|             { | ||||
|                 _memoryStream?.Dispose(); | ||||
|             } | ||||
|  | ||||
|             public Stream OpenFile() | ||||
|             { | ||||
|                 _memoryStream = new MemoryStream(); | ||||
|                 return _memoryStream; | ||||
|             } | ||||
|  | ||||
|             public void CommitWrite() | ||||
|             { | ||||
|                 _fileStorage.UploadData(new MemoryStream(_memoryStream!.ToArray()), new SmbConnectionInfo(new IOConnectionInfo() { Path = _uploadPath})); | ||||
|                 if (UseFileTransaction) | ||||
|                 { | ||||
|                     SmbConnectionInfo uploadPath = new SmbConnectionInfo(new IOConnectionInfo() { Path = _uploadPath }); | ||||
|                     SmbConnectionInfo finalPath = new SmbConnectionInfo(new IOConnectionInfo() { Path = _path }); | ||||
|                     _fileStorage.RenameFile(uploadPath, finalPath); | ||||
|                 } | ||||
|  | ||||
|  | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void RenameFile(SmbConnectionInfo fromPath, SmbConnectionInfo toPath) | ||||
|         { | ||||
|             using var connection = new SmbConnection(fromPath); | ||||
|  | ||||
|             // Open existing file | ||||
|             var status = connection.FileStore!.CreateFile(out var handle, out _, fromPath.GetLocalSmbPath(), AccessMask.MAXIMUM_ALLOWED, 0, ShareAccess.Read, CreateDisposition.FILE_OPEN, CreateOptions.FILE_NON_DIRECTORY_FILE, null); | ||||
|             if (status != NTStatus.STATUS_SUCCESS) | ||||
|                 throw new Exception($"Failed to open {fromPath.LocalPath} for renaming!"); | ||||
|          | ||||
|             FileRenameInformationType2 renameInfo = new FileRenameInformationType2 | ||||
|             { | ||||
|                 FileName = toPath.GetLocalSmbPath(), | ||||
|                 ReplaceIfExists = true | ||||
|             }; | ||||
|             connection.FileStore.SetFileInformation(handle, renameInfo); | ||||
|             connection.FileStore.CloseFile(handle); | ||||
|              | ||||
|         } | ||||
|  | ||||
|         private void UploadData(Stream data, SmbConnectionInfo uploadPath) | ||||
|         { | ||||
|             using var connection = new SmbConnection(uploadPath); | ||||
|             var status = connection.FileStore!.CreateFile(out var fileHandle, out _, uploadPath.GetLocalSmbPath(), AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.None, CreateDisposition.FILE_CREATE, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null); | ||||
|             if (status == NTStatus.STATUS_OBJECT_NAME_COLLISION) | ||||
|                 status = connection.FileStore!.CreateFile(out fileHandle, out _, uploadPath.GetLocalSmbPath(), AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.None, CreateDisposition.FILE_OVERWRITE, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null); | ||||
|             if (status != NTStatus.STATUS_SUCCESS) | ||||
|             { | ||||
|                 throw new Exception($"Failed to open {uploadPath.LocalPath} for writing!"); | ||||
|             } | ||||
|  | ||||
|             long writeOffset = 0; | ||||
|             while (data.Position < data.Length) | ||||
|             { | ||||
|                 byte[] buffer = new byte[(int)connection.Client.MaxWriteSize]; | ||||
|                 int bytesRead = data.Read(buffer, 0, buffer.Length); | ||||
|                 if (bytesRead < (int)connection.Client.MaxWriteSize) | ||||
|                 { | ||||
|                     Array.Resize(ref buffer, bytesRead); | ||||
|                 } | ||||
|  | ||||
|                 status = connection.FileStore.WriteFile(out _, fileHandle, writeOffset, buffer); | ||||
|                 if (status != NTStatus.STATUS_SUCCESS) | ||||
|                 { | ||||
|                     throw new Exception("Failed to write to file"); | ||||
|                 } | ||||
|                 writeOffset += bytesRead; | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) | ||||
|         { | ||||
|             return new SmbFileStorageWriteTransaction(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 bool RequiresCredentials(IOConnectionInfo ioc) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         public void CreateDirectory(IOConnectionInfo ioc, string newDirName) | ||||
|         { | ||||
|             throw new NotImplementedException(); | ||||
|         } | ||||
|  | ||||
|         private static IEnumerable<FileDescription> ListShares(SmbConnection conn, SmbConnectionInfo parent) | ||||
|         { | ||||
|             foreach (string share in conn.Client.ListShares(out _)) | ||||
|             { | ||||
|                 yield return new FileDescription() | ||||
|                 { | ||||
|                     CanRead = true, | ||||
|                     CanWrite = true, | ||||
|                     DisplayName = share, | ||||
|                     IsDirectory = true, | ||||
|                     Path = parent.GetChild(share).ToPath() | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc) | ||||
|         { | ||||
|             List<FileDescription> result = []; | ||||
|             SmbConnectionInfo info = new SmbConnectionInfo(ioc); | ||||
|             using SmbConnection conn = new SmbConnection(info); | ||||
|             if (string.IsNullOrEmpty(info.Share)) | ||||
|             { | ||||
|                 var shares = ListShares(conn, info).ToList(); | ||||
|                 return shares; | ||||
|             } | ||||
|  | ||||
|             NTStatus status = conn.FileStore!.CreateFile(out var directoryHandle, out _, info.GetLocalSmbPath(), AccessMask.GENERIC_READ, FileAttributes.Directory, ShareAccess.Read | ShareAccess.Write, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null); | ||||
|             if (status == NTStatus.STATUS_SUCCESS) | ||||
|             { | ||||
|                 conn.FileStore.QueryDirectory(out List<QueryDirectoryFileInformation> fileList, directoryHandle, "*", FileInformationClass.FileDirectoryInformation); | ||||
|                 foreach (var fi in fileList) | ||||
|                 { | ||||
|                     var fileDirectoryInformation = fi as FileDirectoryInformation; | ||||
|                     if (fileDirectoryInformation == null) | ||||
|                         continue; | ||||
|  | ||||
|                     if (fileDirectoryInformation.FileName is "." or "..") | ||||
|                         continue; | ||||
|  | ||||
|                     var fileDescription = FileDescriptionConvert(ioc, fileDirectoryInformation); | ||||
|  | ||||
|                     result.Add(fileDescription); | ||||
|                 } | ||||
|                 conn.FileStore.CloseFile(directoryHandle); | ||||
|             } | ||||
|  | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|         private FileDescription FileDescriptionConvert(IOConnectionInfo parentIoc, | ||||
|             FileDirectoryInformation fileDirectoryInformation) | ||||
|         { | ||||
|             FileDescription fileDescription = new FileDescription | ||||
|             { | ||||
|                 CanRead = true, | ||||
|                 CanWrite = true, | ||||
|                 IsDirectory = (fileDirectoryInformation.FileAttributes & FileAttributes.Directory) != 0, | ||||
|                 DisplayName = fileDirectoryInformation.FileName | ||||
|             }; | ||||
|             fileDescription.Path = CreateFilePath(parentIoc.Path, fileDescription.DisplayName); | ||||
|             fileDescription.LastModified = fileDirectoryInformation.LastWriteTime; | ||||
|                          | ||||
|             fileDescription.SizeInBytes = fileDirectoryInformation.EndOfFile; | ||||
|             return fileDescription; | ||||
|         } | ||||
|  | ||||
|         public FileDescription GetFileDescription(IOConnectionInfo ioc) | ||||
|         { | ||||
|             SmbConnectionInfo info = new SmbConnectionInfo(ioc); | ||||
|  | ||||
|             if (string.IsNullOrEmpty(info.Share)) | ||||
|             { | ||||
|                 return new FileDescription | ||||
|                 { | ||||
|                     CanRead = true, CanWrite = true, | ||||
|                     DisplayName = info.Host, | ||||
|                     IsDirectory = true, | ||||
|                     Path = info.ToPath() | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             using SmbConnection conn = new SmbConnection(info); | ||||
|             NTStatus status = conn.FileStore!.CreateFile(out var directoryHandle, out _, info.GetParent().GetLocalSmbPath(), AccessMask.GENERIC_READ, FileAttributes.Directory, ShareAccess.Read | ShareAccess.Write, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null); | ||||
|             if (status != NTStatus.STATUS_SUCCESS) throw new Exception($"Failed to query details for {info.LocalPath}"); | ||||
|             conn.FileStore.QueryDirectory(out List<QueryDirectoryFileInformation> fileList, directoryHandle, info.Stem(), FileInformationClass.FileDirectoryInformation); | ||||
|             foreach (var fi in fileList) | ||||
|             { | ||||
|                 var fileDirectoryInformation = fi as FileDirectoryInformation; | ||||
|                 if (fileDirectoryInformation == null) | ||||
|                     continue; | ||||
|  | ||||
|                 if (fileDirectoryInformation.FileName is "." or "..") | ||||
|                     continue; | ||||
|  | ||||
|                 return FileDescriptionConvert(ioc, fileDirectoryInformation); | ||||
|  | ||||
|                          | ||||
|             } | ||||
|             conn.FileStore.CloseFile(directoryHandle); | ||||
|  | ||||
|             throw new Exception($"Failed to query details for {info.LocalPath}"); | ||||
|         } | ||||
|  | ||||
|         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, protocolId); | ||||
|  | ||||
|         } | ||||
|  | ||||
|         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) | ||||
|         { | ||||
|             return new SmbConnectionInfo(ioc).ToDisplayString(); | ||||
|         } | ||||
|  | ||||
|         public string CreateFilePath(string parent, string newFilename) | ||||
|         { | ||||
|             return new SmbConnectionInfo(new IOConnectionInfo() { Path = parent}).GetChild(newFilename).ToPath(); | ||||
|         } | ||||
|  | ||||
|         public IOConnectionInfo GetParentPath(IOConnectionInfo ioc) | ||||
|         { | ||||
|             SmbConnectionInfo connectionInfo = new SmbConnectionInfo(ioc); | ||||
|             return new IOConnectionInfo() { Path = connectionInfo.GetParent().ToPath() }; | ||||
|         } | ||||
|  | ||||
|         public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename) | ||||
|         { | ||||
|             return new IOConnectionInfo() { Path = CreateFilePath(folderPath.Path, filename)}; | ||||
|         } | ||||
|  | ||||
|         public bool IsPermanentLocation(IOConnectionInfo ioc) | ||||
|         { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut<UiStringKey> reason = null) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| #endif | ||||
| @@ -6,10 +6,12 @@ using System.Text; | ||||
| using Android.App; | ||||
| using Android.Content; | ||||
| using Android.OS; | ||||
| using Android.Preferences; | ||||
| using Android.Runtime; | ||||
| using Android.Views; | ||||
| using Android.Widget; | ||||
| #if !NoNet && !EXCLUDE_JAVAFILESTORAGE | ||||
|  | ||||
| using Keepass2android.Javafilestorage; | ||||
| #endif | ||||
| using KeePassLib.Serialization; | ||||
| @@ -19,9 +21,15 @@ namespace keepass2android.Io | ||||
| #if !NoNet && !EXCLUDE_JAVAFILESTORAGE | ||||
| 	public class WebDavFileStorage: JavaFileStorage | ||||
| 	{ | ||||
| 		public WebDavFileStorage(IKp2aApp app) : base(new Keepass2android.Javafilestorage.WebDavStorage(app.CertificateErrorHandler), app) | ||||
| 		{ | ||||
| 		} | ||||
|         private readonly IKp2aApp _app; | ||||
|         private readonly WebDavStorage baseWebdavStorage; | ||||
|  | ||||
|         public WebDavFileStorage(IKp2aApp app, int chunkSize) : base(new Keepass2android.Javafilestorage.WebDavStorage(app.CertificateErrorHandler, chunkSize), app) | ||||
|         { | ||||
|             _app = app; | ||||
|             baseWebdavStorage = (WebDavStorage)Jfs; | ||||
|  | ||||
|         } | ||||
|  | ||||
| 		public override IEnumerable<string> SupportedProtocols | ||||
| 		{ | ||||
| @@ -75,6 +83,15 @@ namespace keepass2android.Io | ||||
| 			} | ||||
| 			return base.IocToPath(ioc); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|  | ||||
|         public override IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) | ||||
|         { | ||||
|             baseWebdavStorage.SetUploadChunkSize(_app.WebDavChunkedUploadSize); | ||||
|             return base.OpenWriteTransaction(ioc, useFileTransaction); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
| #endif | ||||
| } | ||||
| @@ -1,33 +1,48 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net8.0-android</TargetFramework> | ||||
|     <TargetFramework>net9.0-android</TargetFramework> | ||||
|     <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|     <DefineConstants Condition="'$(Flavor)'=='NoNet'">NO_QR_SCANNER;EXCLUDE_JAVAFILESTORAGE;NoNet</DefineConstants> | ||||
|   </PropertyGroup> | ||||
|   <ItemGroup> | ||||
|     <Folder Include="Resources\" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="FluentFTP" Version="51.1.0" /> | ||||
|     <PackageReference Include="MegaApiClient" Version="1.10.4" /> | ||||
|     <PackageReference Include="Microsoft.Graph" Version="5.68.0" /> | ||||
|     <PackageReference Include="Microsoft.Identity.Client" Version="4.67.1" /> | ||||
|     <PackageReference Include="FluentFTP" Version="52.1.0" Condition="'$(Flavor)'!='NoNet'"/> | ||||
|     <PackageReference Include="FluentFTP.GnuTLS" Version="1.0.37" Condition="'$(Flavor)'!='NoNet'"/> | ||||
|     <PackageReference Include="MegaApiClient" Version="1.10.4"  Condition="'$(Flavor)'!='NoNet'"/> | ||||
|     <PackageReference Include="Microsoft.Graph" Version="5.68.0" Condition="'$(Flavor)'!='NoNet'"/> | ||||
|     <PackageReference Include="Microsoft.Identity.Client" Version="4.67.1" Condition="'$(Flavor)'!='NoNet'"/> | ||||
|     <PackageReference Include="SMBLibrary" Version="1.5.4" Condition="'$(Flavor)'!='NoNet'"/> | ||||
|     <PackageReference Include="Xamarin.AndroidX.Browser" Version="1.8.0" /> | ||||
|     <PackageReference Include="Xamarin.AndroidX.Core" Version="1.13.1.5" /> | ||||
|     <PackageReference Include="Xamarin.Google.Android.Material" Version="1.11.0.3" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\AndroidFileChooserBinding\AndroidFileChooserBinding.csproj" /> | ||||
|     <ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj" /> | ||||
|     <ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj" Condition="'$(Flavor)'!='NoNet'"  /> | ||||
|     <ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj" /> | ||||
|     <ProjectReference Include="..\KP2AKdbLibraryBinding\KP2AKdbLibraryBinding.csproj" /> | ||||
|     <ProjectReference Include="..\TwofishCipher\TwofishCipher.csproj" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|   <None Remove="Io/DropboxFileStorageKeysDummy.cs" /> | ||||
|   <Compile Remove="Io/DropboxFileStorageKeysDummy.cs" /> | ||||
|   <Content Remove="Io/DropboxFileStorageKeysDummy.cs" /> | ||||
|  | ||||
| </ItemGroup> | ||||
| <ItemGroup  Condition="'$(Flavor)'=='NoNet'"> | ||||
|   <None Remove="Io/OneDrive2FileStorage.cs" /> | ||||
|   <Compile Remove="Io/OneDrive2FileStorage.cs" /> | ||||
|   <Content Remove="Io/OneDrive2FileStorage.cs" /> | ||||
|   <None Remove="Io/MegaFileStorage.cs" /> | ||||
|   <Compile Remove="Io/MegaFileStorage.cs" /> | ||||
|   <Content Remove="Io/MegaFileStorage.cs" /> | ||||
| </ItemGroup> | ||||
|  | ||||
| <Import Project="Io/GenerateSecrets.targets" /> | ||||
| <ItemGroup> | ||||
| <Compile Include="Io/DropboxFileStorage.g.cs" /> | ||||
| </ItemGroup> | ||||
|  | ||||
|    | ||||
| </Project> | ||||
| @@ -4,6 +4,7 @@ using Android.Content; | ||||
| using Android.OS; | ||||
| using Android.Widget; | ||||
| using Java.Net; | ||||
| using KeePass.Util; | ||||
| using KeePassLib.Serialization; | ||||
| using keepass2android.Io; | ||||
|  | ||||
| @@ -94,15 +95,12 @@ namespace keepass2android | ||||
| 				} | ||||
| 				if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE"))) | ||||
| 				{ | ||||
| 					ShowToast(data.GetStringExtra("EXTRA_ERROR_MESSAGE")); | ||||
| 					ShowErrorToast(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://")) | ||||
| @@ -150,7 +148,7 @@ namespace keepass2android | ||||
|  | ||||
| 		protected abstract void StartFileChooser(string path, int requestCode, bool isForSave); | ||||
|  | ||||
| 		protected abstract void ShowToast(string text); | ||||
| 		protected abstract void ShowErrorToast(string text); | ||||
|  | ||||
| 		protected abstract void ShowInvalidSchemeMessage(string dataString); | ||||
|  | ||||
| @@ -208,7 +206,7 @@ namespace keepass2android | ||||
| 					{ | ||||
| 						return () => | ||||
| 							{ | ||||
| 								ShowToast(_app.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message); | ||||
| 								ShowErrorToast(_app.GetResourceString(UiStringKey.ErrorOcurred) + " " + ExceptionUtil.GetErrorMessage(e)); | ||||
| 								ReturnCancel(); | ||||
| 							}; | ||||
| 					} | ||||
|   | ||||
							
								
								
									
										32
									
								
								src/Kp2aBusinessLogic/Utils/ExceptionUtil.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/Kp2aBusinessLogic/Utils/ExceptionUtil.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
|  | ||||
| namespace KeePass.Util | ||||
| { | ||||
|     public class ExceptionUtil | ||||
|     { | ||||
|  | ||||
|         public static string GetErrorMessage(Exception e) | ||||
|         { | ||||
|             string errorMessage = e.Message; | ||||
|             if (e is Java.Lang.Exception javaException) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     errorMessage = javaException.LocalizedMessage ?? javaException.Message ?? errorMessage; | ||||
|                 } | ||||
|                 finally | ||||
|                 { | ||||
|  | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return errorMessage; | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -5,6 +5,7 @@ using System.Security.Cryptography; | ||||
| using System.Text; | ||||
| using Android.App; | ||||
| using Android.Content; | ||||
| using KeePass.Util; | ||||
| using KeePassLib.Cryptography; | ||||
| using KeePassLib.Serialization; | ||||
| using KeePassLib.Utility; | ||||
| @@ -65,7 +66,7 @@ namespace keepass2android | ||||
| 			} | ||||
| 			catch (Exception e) | ||||
| 			{ | ||||
| 				Finish(false, e.Message); | ||||
| 				Finish(false, ExceptionUtil.GetErrorMessage(e)); | ||||
| 			} | ||||
|  | ||||
| 		} | ||||
|   | ||||
| @@ -90,9 +90,12 @@ namespace keepass2android | ||||
| 			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); | ||||
|             Kp2aLog.Log("LoadData: Retrieving stream"); | ||||
|             Stream s = databaseData ?? fileStorage.OpenFileForRead(iocInfo); | ||||
|             Kp2aLog.Log("LoadData: GetCurrentFileVersion"); | ||||
|             var fileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo); | ||||
|             Kp2aLog.Log("LoadData: PopulateDatabaseFromStream"); | ||||
|             PopulateDatabaseFromStream(pwDatabase, s, iocInfo, compositeKey, status, databaseFormat); | ||||
| 		    LastFileVersion = fileVersion; | ||||
|  | ||||
| 		    status.UpdateSubMessage(""); | ||||
|   | ||||
| @@ -10,6 +10,7 @@ using Com.Keepassdroid.Database.Exception; | ||||
| #endif | ||||
| using Com.Keepassdroid.Database.Save; | ||||
| using Java.Util; | ||||
| using KeePass.Util; | ||||
| using KeePassLib; | ||||
| using KeePassLib.Cryptography; | ||||
| using KeePassLib.Cryptography.Cipher; | ||||
| @@ -82,15 +83,14 @@ namespace keepass2android | ||||
| 			catch (Java.IO.FileNotFoundException e) | ||||
| 			{ | ||||
| 				throw new FileNotFoundException( | ||||
| 					e.Message, e); | ||||
|                     ExceptionUtil.GetErrorMessage(e), 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); | ||||
| 				throw new Exception(ExceptionUtil.GetErrorMessage(e) ?? | ||||
|                                                                   e.GetType().Name, e); | ||||
| 			} | ||||
| 			 | ||||
| 			HashOfLastStream = hashingStream.Hash; | ||||
|   | ||||
| @@ -6,6 +6,7 @@ using Android.App; | ||||
| using Android.Content; | ||||
| using KeePassLib.Serialization; | ||||
| using keepass2android.Io; | ||||
| using KeePass.Util; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| @@ -109,7 +110,7 @@ namespace keepass2android | ||||
| 			catch (Exception e) | ||||
| 			{ | ||||
|                 Kp2aLog.LogUnexpectedError(e); | ||||
| 				Finish(false, e.Message); | ||||
| 				Finish(false, ExceptionUtil.GetErrorMessage(e)); | ||||
| 			} | ||||
| 			 | ||||
| 		} | ||||
|   | ||||
| @@ -21,6 +21,7 @@ using System.Linq; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| using Android.App; | ||||
| using KeePass.Util; | ||||
| using keepass2android.database.edit; | ||||
| using KeePassLib; | ||||
| using KeePassLib.Keys; | ||||
| @@ -103,10 +104,10 @@ namespace keepass2android | ||||
| 			} | ||||
| 			catch (AggregateException e) | ||||
| 			{ | ||||
| 				string message = e.Message; | ||||
| 				string message = ExceptionUtil.GetErrorMessage(e); | ||||
| 				foreach (var innerException in e.InnerExceptions) | ||||
| 				{ | ||||
| 					message = innerException.Message; | ||||
| 					message = ExceptionUtil.GetErrorMessage(innerException); | ||||
| 					// Override the message shown with the last (hopefully most recent) inner exception | ||||
| 					Kp2aLog.LogUnexpectedError(innerException); | ||||
| 				} | ||||
| @@ -116,14 +117,14 @@ namespace keepass2android | ||||
| 			catch (DuplicateUuidsException e) | ||||
| 			{ | ||||
| 				Kp2aLog.Log(e.ToString()); | ||||
| 				Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + e.Message + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), false, Exception); | ||||
| 				Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + ExceptionUtil.GetErrorMessage(e) + _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); | ||||
| 				Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + (ExceptionUtil.GetErrorMessage(e) ?? (e is FileNotFoundException ? _app.GetResourceString(UiStringKey.FileNotFound) :  "")), false, Exception); | ||||
| 				return; | ||||
| 			} | ||||
| 			 | ||||
| @@ -137,6 +138,7 @@ namespace keepass2android | ||||
|  | ||||
| 		Database TryLoad(MemoryStream databaseStream) | ||||
| 		{ | ||||
| 			Kp2aLog.Log("LoadDb: Copying database in memory"); | ||||
| 			//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. | ||||
| @@ -145,8 +147,9 @@ namespace keepass2android | ||||
| 			workingCopy.Seek(0, SeekOrigin.Begin); | ||||
| 			//reset stream if we need to reuse it later: | ||||
| 			databaseStream.Seek(0, SeekOrigin.Begin); | ||||
| 			//now let's go: | ||||
| 			try | ||||
|             Kp2aLog.Log("LoadDb: Ready to start loading"); | ||||
|             //now let's go: | ||||
|             try | ||||
| 			{ | ||||
|                 Database newDb = _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent); | ||||
| 				Kp2aLog.Log("LoadDB OK"); | ||||
|   | ||||
| @@ -130,24 +130,24 @@ namespace keepass2android | ||||
| 			if ( !String.IsNullOrEmpty(message) ) { | ||||
| 			    Kp2aLog.Log("OnFinish message: " + message); | ||||
|                 if (makeDialog && ctx != null) | ||||
| 			    { | ||||
| 			        try | ||||
| 			        { | ||||
| 			            MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx); | ||||
| 			             | ||||
| 			            builder.SetMessage(message) | ||||
| 			                .SetPositiveButton(Android.Resource.String.Ok, (sender, args) => ((Dialog)sender).Dismiss()) | ||||
| 			                .Show(); | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx); | ||||
|  | ||||
|                         builder.SetMessage(message) | ||||
|                             .SetPositiveButton(Android.Resource.String.Ok, (sender, args) => ((Dialog)sender).Dismiss()) | ||||
|                             .Show(); | ||||
|  | ||||
|                     } | ||||
|                     catch (Exception) | ||||
| 			        { | ||||
| 			            Toast.MakeText(ctx, message, ToastLength.Long).Show(); | ||||
| 			        } | ||||
| 			    } | ||||
|                     { | ||||
|                         Toast.MakeText(ctx, message, ToastLength.Long).Show(); | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                     Toast.MakeText(ctx ?? Application.Context, message, ToastLength.Long).Show(); | ||||
| 			} | ||||
|             } | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -29,6 +29,7 @@ using KeePassLib.Utility; | ||||
| using keepass2android.Io; | ||||
| using Debug = System.Diagnostics.Debug; | ||||
| using Exception = System.Exception; | ||||
| using KeePass.Util; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| @@ -187,7 +188,7 @@ namespace keepass2android | ||||
| 			} | ||||
| */ | ||||
| 					Kp2aLog.LogUnexpectedError(e); | ||||
| 					Finish(false, e.Message); | ||||
| 					Finish(false, ExceptionUtil.GetErrorMessage(e)); | ||||
| 					return; | ||||
| 				} | ||||
| 			} | ||||
| @@ -222,8 +223,8 @@ namespace keepass2android | ||||
| 						catch (Exception e) | ||||
| 						{ | ||||
| 							Kp2aLog.LogUnexpectedError(e); | ||||
| 							Kp2aLog.Log("Error in worker thread of SaveDb: " + e); | ||||
| 							Finish(false, e.Message); | ||||
| 							Kp2aLog.Log("Error in worker thread of SaveDb: " + ExceptionUtil.GetErrorMessage(e)); | ||||
| 							Finish(false, ExceptionUtil.GetErrorMessage(e)); | ||||
| 						} | ||||
| 						 | ||||
| 					}); | ||||
| @@ -233,7 +234,7 @@ namespace keepass2android | ||||
| 			{ | ||||
| 				Kp2aLog.LogUnexpectedError(e); | ||||
| 				Kp2aLog.Log("Error starting worker thread of SaveDb: "+e); | ||||
| 				Finish(false, e.Message); | ||||
| 				Finish(false, ExceptionUtil.GetErrorMessage(e)); | ||||
| 			} | ||||
| 			 | ||||
| 		} | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net8.0-android</TargetFramework> | ||||
|     <TargetFramework>net9.0-android</TargetFramework> | ||||
|     <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net8.0-android</TargetFramework> | ||||
|     <TargetFramework>net9.0-android</TargetFramework> | ||||
|     <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net8.0-android</TargetFramework> | ||||
|     <TargetFramework>net9.0-android</TargetFramework> | ||||
|     <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net8.0-android</TargetFramework> | ||||
|     <TargetFramework>net9.0-android</TargetFramework> | ||||
|     <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net8.0-android</TargetFramework> | ||||
|     <TargetFramework>net9.0-android</TargetFramework> | ||||
|     <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|   | ||||
							
								
								
									
										25
									
								
								src/build-scripts/build-java.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										25
									
								
								src/build-scripts/build-java.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| set -e | ||||
|  | ||||
| #unset ANDROID_NDK_HOME ANDROID_NDK | ||||
|  | ||||
| pushd ../java/ | ||||
|  | ||||
| pushd JavaFileStorageTest-AS | ||||
| ./gradlew assemble | ||||
| popd | ||||
|  | ||||
| pushd KP2ASoftkeyboard_AS | ||||
| ./gradlew assemble | ||||
| popd | ||||
|  | ||||
| pushd Keepass2AndroidPluginSDK2 | ||||
| ./gradlew assemble | ||||
| popd | ||||
|  | ||||
| pushd KP2AKdbLibrary | ||||
| ./gradlew assemble | ||||
| popd | ||||
|  | ||||
| popd | ||||
							
								
								
									
										6
									
								
								src/build-scripts/build-native.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										6
									
								
								src/build-scripts/build-native.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| #!/bin/bash | ||||
| set -e | ||||
|  | ||||
| pushd ../java/argon2 | ||||
| ndk-build | ||||
| popd | ||||
							
								
								
									
										28
									
								
								src/build-scripts/linux-build.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/build-scripts/linux-build.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| ## Setup build environment | ||||
| * install Android SDK | ||||
| * install Android NDK | ||||
| * install dotnet8 | ||||
|  | ||||
| ``` | ||||
|  | ||||
| #from https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/sdk-8.0.407-linux-x64-binaries | ||||
| wget https://download.visualstudio.microsoft.com/download/pr/9d07577e-f7bc-4d60-838d-f79c50b5c11a/459ef339396783db369e0432d6dc3d7e/dotnet-sdk-8.0.407-linux-x64.tar.gz | ||||
| mkdir -p $HOME/dotnet && tar zxf dotnet-sdk-8.0.407-linux-x64.tar.gz -C $HOME/dotnet | ||||
| export DOTNET_ROOT=$HOME/dotnet | ||||
| export PATH=$PATH:$HOME/dotnet | ||||
|  | ||||
| ``` | ||||
|  | ||||
| ## Build Keepass2Android | ||||
|  | ||||
| ``` | ||||
| git clone --recurse-submodules https://github.com/PhilippC/keepass2android.git | ||||
| cd keepass2android/src/build-scripts | ||||
| ./build-java.sh && ./build-native.sh | ||||
| cd .. | ||||
| cd keepass2android-app | ||||
| ln -s Manifests/AndroidManifest_debug.xml AndroidManifest.xml | ||||
| dotnet workload restore | ||||
| dotnet restore | ||||
| dotnet build | ||||
| ``` | ||||
							
								
								
									
										22
									
								
								src/build-scripts/rename-output-apks.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/build-scripts/rename-output-apks.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| #!/usr/bin/env bash | ||||
|  | ||||
| BASE_DIR="${1}" | ||||
|  | ||||
| for arch_dir in "$BASE_DIR"/android-*/; do | ||||
|   arch=$(basename "$arch_dir") | ||||
|   arch=${arch#android-} | ||||
|   APK_DIR="${arch_dir}publish" | ||||
|   if [[ -d "$APK_DIR" ]]; then | ||||
|     apk_path=$(find "$APK_DIR" -maxdepth 1 -type f -name "*.apk" | head -n1) | ||||
|     if [[ -n "$apk_path" ]]; then | ||||
|       base=$(basename "$apk_path" .apk) | ||||
|       new_path="$APK_DIR/${base}-${arch}.apk" | ||||
|       mv "$apk_path" "$new_path" | ||||
|       echo "Renamed $apk_path to $new_path" | ||||
|     else | ||||
|       echo "No APK found in $APK_DIR" | ||||
|     fi | ||||
|   else | ||||
|     echo "Directory $APK_DIR does not exist" | ||||
|   fi | ||||
| done | ||||
| @@ -1,6 +1,7 @@ | ||||
| package keepass2android.javafilestorage; | ||||
|  | ||||
| import android.content.Context; | ||||
| import java.math.BigInteger; | ||||
| import android.content.Intent; | ||||
|  | ||||
| import android.net.Uri; | ||||
| @@ -15,7 +16,10 @@ import com.burgstaller.okhttp.basic.BasicAuthenticator; | ||||
| import com.burgstaller.okhttp.digest.CachingAuthenticator; | ||||
| import com.burgstaller.okhttp.digest.DigestAuthenticator; | ||||
|  | ||||
|  | ||||
| import java.io.ByteArrayInputStream; | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.StringReader; | ||||
| import java.io.UnsupportedEncodingException; | ||||
| @@ -24,6 +28,7 @@ import java.security.KeyManagementException; | ||||
| import java.security.KeyStore; | ||||
| import java.security.KeyStoreException; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
| import java.security.SecureRandom; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.Date; | ||||
| @@ -44,23 +49,33 @@ import keepass2android.javafilestorage.webdav.DecoratedTrustManager; | ||||
| import keepass2android.javafilestorage.webdav.PropfindXmlParser; | ||||
| import keepass2android.javafilestorage.webdav.WebDavUtil; | ||||
| import okhttp3.MediaType; | ||||
| import okhttp3.MultipartBody; | ||||
| import okhttp3.OkHttpClient; | ||||
| import okhttp3.Request; | ||||
| import okhttp3.RequestBody; | ||||
| import okhttp3.Response; | ||||
| import okhttp3.internal.tls.OkHostnameVerifier; | ||||
| import okio.BufferedSink; | ||||
|  | ||||
| public class WebDavStorage extends JavaFileStorageBase { | ||||
|  | ||||
|     private final ICertificateErrorHandler mCertificateErrorHandler; | ||||
|     private Context appContext; | ||||
|  | ||||
|     public WebDavStorage(ICertificateErrorHandler certificateErrorHandler) | ||||
|     int chunkSize; | ||||
|  | ||||
|     public WebDavStorage(ICertificateErrorHandler certificateErrorHandler, int chunkSize) | ||||
|     { | ||||
|         this.chunkSize = chunkSize; | ||||
|  | ||||
|         mCertificateErrorHandler = certificateErrorHandler; | ||||
|     } | ||||
|  | ||||
|     public void setUploadChunkSize(int chunkSize) | ||||
|     { | ||||
|         this.chunkSize = chunkSize; | ||||
|     } | ||||
|  | ||||
|     public String buildFullPath(String url, String username, String password) throws UnsupportedEncodingException { | ||||
|         String scheme = url.substring(0, url.indexOf("://")); | ||||
|         url = url.substring(scheme.length() + 3); | ||||
| @@ -181,21 +196,119 @@ public class WebDavStorage extends JavaFileStorageBase { | ||||
|         return client; | ||||
|     } | ||||
|  | ||||
|     public void renameOrMoveWebDavResource(String sourcePath, String destinationPath, boolean overwrite) throws Exception { | ||||
|  | ||||
|         ConnectionInfo sourceCi = splitStringToConnectionInfo(sourcePath); | ||||
|         ConnectionInfo destinationCi = splitStringToConnectionInfo(destinationPath); | ||||
|  | ||||
|         Request.Builder requestBuilder = new Request.Builder() | ||||
|                 .url(new URL(sourceCi.URL)) | ||||
|                 .method("MOVE", null) // "MOVE" is the HTTP method | ||||
|                 .header("Destination", destinationCi.URL); // New URI for the resource | ||||
|  | ||||
|         // Add Overwrite header | ||||
|         if (overwrite) { | ||||
|             requestBuilder.header("Overwrite", "T"); // 'T' for true | ||||
|         } else { | ||||
|             requestBuilder.header("Overwrite", "F"); // 'F' for false | ||||
|         } | ||||
|  | ||||
|         Request request = requestBuilder.build(); | ||||
|  | ||||
|         Response response = getClient(sourceCi).newCall(request).execute(); | ||||
|  | ||||
|         // Check the status code | ||||
|         if (response.isSuccessful()) { | ||||
|             // WebDAV MOVE can return 201 (Created) if a new resource was created at dest, | ||||
|             // or 204 (No Content) if moved to a pre-existing destination (e.g., just renamed). | ||||
|             // A 200 OK might also be returned by some servers, though 201/204 are more common. | ||||
|  | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             throw new Exception("Rename/Move failed for " + sourceCi.URL + " to " + destinationCi.URL + ": " + response.code() + " " + response.message()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static String generateRandomHexString(int length) { | ||||
|         SecureRandom secureRandom = new SecureRandom(); | ||||
|         // Generate enough bytes to ensure we can get the desired number of hex characters. | ||||
|         // Each byte converts to two hex characters. | ||||
|         // For 8 hex characters, we need 4 bytes. | ||||
|         int numBytes = (int) Math.ceil(length / 2.0); | ||||
|         byte[] randomBytes = new byte[numBytes]; | ||||
|         secureRandom.nextBytes(randomBytes); | ||||
|  | ||||
|         // Convert the byte array to a hexadecimal string | ||||
|         // BigInteger(1, randomBytes) treats the byte array as a positive number. | ||||
|         // toString(16) converts it to a hexadecimal string. | ||||
|         String hexString = new BigInteger(1, randomBytes).toString(16); | ||||
|  | ||||
|         // Pad with leading zeros if necessary (e.g., if the generated number is small) | ||||
|         // and then take the first 'length' characters. | ||||
|         // Using String.format to ensure leading zeros if the hexString is shorter. | ||||
|         return String.format("%0" + length + "d", new BigInteger(hexString, 16)).substring(0, length); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void uploadFile(String path, byte[] data, boolean writeTransactional) | ||||
|             throws Exception { | ||||
|  | ||||
|         if (writeTransactional) | ||||
|         { | ||||
|             String randomSuffix = ".tmp." + generateRandomHexString(8); | ||||
|             uploadFile(path + randomSuffix, data, false); | ||||
|             renameOrMoveWebDavResource(path+randomSuffix, path, true); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|  | ||||
|         try { | ||||
|             ConnectionInfo ci = splitStringToConnectionInfo(path); | ||||
|  | ||||
|  | ||||
|             RequestBody requestBody; | ||||
|             if (chunkSize > 0) | ||||
|             { | ||||
|                 // use chunked upload | ||||
|                 requestBody = new RequestBody() { | ||||
|                     @Override | ||||
|                     public MediaType contentType() { | ||||
|                         return MediaType.parse("application/binary"); | ||||
|                     } | ||||
|  | ||||
|                     @Override | ||||
|                     public void writeTo(BufferedSink sink) throws IOException { | ||||
|                         try (InputStream in = new ByteArrayInputStream(data)) { | ||||
|                             byte[] buffer = new byte[chunkSize]; | ||||
|                             int read; | ||||
|                             while ((read = in.read(buffer)) != -1) { | ||||
|                                 sink.write(buffer, 0, read); | ||||
|                                 sink.flush(); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     @Override | ||||
|                     public long contentLength() { | ||||
|                         return -1; // use chunked upload | ||||
|                     } | ||||
|                 }; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 requestBody = new MultipartBody.Builder() | ||||
|                     .addPart(RequestBody.create(data, MediaType.parse("application/binary"))) | ||||
|                     .build(); | ||||
|             }             | ||||
|  | ||||
|  | ||||
|             Request request = new Request.Builder() | ||||
|                     .url(new URL(ci.URL)) | ||||
|                     .put(RequestBody.create(MediaType.parse("application/binary"), data)) | ||||
|                     .put(requestBody) | ||||
|                     .build(); | ||||
|  | ||||
|             //TODO consider writeTransactional | ||||
|             //TODO check for error | ||||
|  | ||||
|  | ||||
|             Response response = getClient(ci).newCall(request).execute(); | ||||
|             checkStatus(response); | ||||
| @@ -290,7 +403,10 @@ public class WebDavStorage extends JavaFileStorageBase { | ||||
|                             e.sizeInBytes = -1; | ||||
|                         } | ||||
|                     } | ||||
|                     e.isDirectory = r.href.endsWith("/"); | ||||
|  | ||||
|                     e.isDirectory = r.href.endsWith("/") || okprop.IsCollection; | ||||
|  | ||||
|  | ||||
|  | ||||
|                     e.displayName = okprop.DisplayName; | ||||
|                     if (e.displayName == null) | ||||
| @@ -519,3 +635,4 @@ public class WebDavStorage extends JavaFileStorageBase { | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -57,6 +57,8 @@ public class PropfindXmlParser | ||||
|                 public String DisplayName; | ||||
|                 public String LastModified; | ||||
|                 public String ContentLength; | ||||
|  | ||||
|                 public boolean IsCollection; | ||||
|             } | ||||
|             public String status; | ||||
|             public Prop prop; | ||||
| @@ -191,6 +193,8 @@ public class PropfindXmlParser | ||||
|                 continue; | ||||
|             } | ||||
|             String name = parser.getName(); | ||||
|             String namespace = parser.getNamespace(); | ||||
|  | ||||
|  | ||||
|             android.util.Log.d("PARSE", "4name = " + name); | ||||
|             if (name.equals("getcontentlength")) | ||||
| @@ -200,6 +204,9 @@ public class PropfindXmlParser | ||||
|                 prop.LastModified = readText(parser); | ||||
|             } else if (name.equals("displayname")) { | ||||
|                 prop.DisplayName = readText(parser); | ||||
|             } else if (name.equals("resourcetype") && namespace.equals(ns)) { | ||||
|                 // We found the <d:resourcetype> tag | ||||
|                 prop.IsCollection = readResourceType(parser); | ||||
|             } else { | ||||
|                 skip(parser); | ||||
|             } | ||||
| @@ -208,6 +215,37 @@ public class PropfindXmlParser | ||||
|         return  prop; | ||||
|     } | ||||
|  | ||||
|     private boolean readResourceType(XmlPullParser parser) throws IOException, XmlPullParserException { | ||||
|         boolean isCollection = false; | ||||
|         parser.require(XmlPullParser.START_TAG, ns, "resourcetype"); | ||||
|  | ||||
|         while (parser.next() != XmlPullParser.END_TAG) { | ||||
|  | ||||
|             if (parser.getEventType() != XmlPullParser.START_TAG) { | ||||
|                 continue; | ||||
|             } | ||||
|             String name = parser.getName(); | ||||
|             String namespace = parser.getNamespace(); | ||||
|  | ||||
|             if (name.equals("collection") && namespace.equals(ns)) { | ||||
|                 // We found <d:collection/>, so it's a folder | ||||
|                 isCollection = true; | ||||
|                 // Since <d:collection/> is usually an empty tag, just consume it. | ||||
|                 // It might contain text if there's whitespace, so consume text then end tag. | ||||
|                 if (parser.next() == XmlPullParser.TEXT) { | ||||
|                     parser.nextTag(); // Move to the end tag | ||||
|                 } | ||||
|                 parser.require(XmlPullParser.END_TAG, ns, "collection"); | ||||
|             } else { | ||||
|                 // Skip any other unexpected tags within <d:resourcetype> | ||||
|                 skip(parser); | ||||
|             } | ||||
|         } | ||||
|         // After reading all children of <d:resourcetype>, ensure we are at its END_TAG | ||||
|         parser.require(XmlPullParser.END_TAG, ns, "resourcetype"); | ||||
|         return isCollection; | ||||
|     } | ||||
|  | ||||
|     private void skip(XmlPullParser parser) throws XmlPullParserException, IOException { | ||||
|         android.util.Log.d("PARSE", "skipping " + parser.getName()); | ||||
|  | ||||
|   | ||||
| @@ -6,8 +6,7 @@ | ||||
|         android:allowBackup="true" | ||||
|         android:icon="@mipmap/ic_launcher" | ||||
|         android:label="@string/app_name" | ||||
|         android:supportsRtl="true" | ||||
|         android:theme="@style/AppTheme"> | ||||
|         android:supportsRtl="true"> | ||||
|         <activity android:name="com.crocoapps.javafilestoragetest2.MainActivity" | ||||
|                   android:exported="true"> | ||||
|             <intent-filter> | ||||
|   | ||||
| @@ -548,7 +548,7 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag | ||||
|  | ||||
|  | ||||
| 		//storageToTest = new GoogleDriveAppDataFileStorage(); | ||||
| 		/*storageToTest = new WebDavStorage(new ICertificateErrorHandler() { | ||||
| 		storageToTest = new WebDavStorage(new ICertificateErrorHandler() { | ||||
| 			@Override | ||||
| 			public boolean onValidationError(String error) { | ||||
| 				return false; | ||||
| @@ -558,12 +558,12 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag | ||||
| 			public boolean alwaysFailOnValidationError() { | ||||
| 				return false; | ||||
| 			} | ||||
| 		}); | ||||
| */ | ||||
| 		}, 64*1024); | ||||
|  | ||||
| 		//storageToTest =  new DropboxV2Storage(ctx,"4ybka4p4a1027n6", "1z5lv528un9nre8", !simulateRestart); | ||||
| 		//storageToTest =  new DropboxFileStorage(ctx,"4ybka4p4a1027n6", "1z5lv528un9nre8", !simulateRestart); | ||||
| 		//storageToTest = new DropboxAppFolderFileStorage(ctx,"ax0268uydp1ya57", "3s86datjhkihwyc", true); | ||||
| 		storageToTest = new GoogleDriveFullFileStorage(); | ||||
| 		// storageToTest = new GoogleDriveFullFileStorage(); | ||||
|  | ||||
|  | ||||
| 		return storageToTest; | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="fill_parent"> | ||||
| <LinearLayout | ||||
|     android:orientation="vertical" | ||||
|     android:layout_width="fill_parent" | ||||
|     android:layout_height="fill_parent" | ||||
| @@ -216,4 +219,5 @@ | ||||
|             android:layout_marginLeft="5dp" | ||||
|             android:text="Resolve" /> | ||||
|     </LinearLayout> | ||||
| </LinearLayout> | ||||
| </LinearLayout> | ||||
| </ScrollView> | ||||
| @@ -1,4 +1,7 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="fill_parent"> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:orientation="vertical" | ||||
|     android:layout_width="fill_parent" | ||||
| @@ -36,4 +39,5 @@ | ||||
|         android:hint="@string/hint_pass" | ||||
|         android:importantForAccessibility="no" /> | ||||
|  | ||||
| </LinearLayout> | ||||
| </LinearLayout> | ||||
| </ScrollView> | ||||
| @@ -56,15 +56,15 @@ | ||||
|     <string name="afc_title_sort_by">Ordina per…</string> | ||||
|     <string name="afc_yesterday">Ieri</string> | ||||
|     <plurals name="afc_title_choose_directories"> | ||||
|         <item quantity="one">Scegli la cartella…</item> | ||||
|         <item quantity="other">Scegli le cartelle…</item> | ||||
|         <item quantity="one">Scegli cartella...</item> | ||||
|         <item quantity="other">Scegli le cartelle...</item> | ||||
|     </plurals> | ||||
|     <plurals name="afc_title_choose_files"> | ||||
|         <item quantity="one">Scegli il file…</item> | ||||
|         <item quantity="other">Scegli i file…</item> | ||||
|         <item quantity="one">Scegli il file</item> | ||||
|         <item quantity="other">Scegli i file</item> | ||||
|     </plurals> | ||||
|     <plurals name="afc_title_choose_files_directories"> | ||||
|         <item quantity="one">Scegli file/ cartella…</item> | ||||
|         <item quantity="other">Scegli file/ cartelle…</item> | ||||
|         <item quantity="one">Scegli file/cartella</item> | ||||
|         <item quantity="other">Scegli file/cartelle</item> | ||||
|     </plurals> | ||||
| </resources> | ||||
|   | ||||
| @@ -55,4 +55,19 @@ | ||||
|     <string name="afc_title_size">Dimensiune</string> | ||||
|     <string name="afc_title_sort_by">Sortează după…</string> | ||||
|     <string name="afc_yesterday">Ieri</string> | ||||
|     <plurals name="afc_title_choose_directories"> | ||||
|         <item quantity="one">Alege dosarul…</item> | ||||
|         <item quantity="few">Alege dosarele…</item> | ||||
|         <item quantity="other">Alege dosarele…</item> | ||||
|     </plurals> | ||||
|     <plurals name="afc_title_choose_files"> | ||||
|         <item quantity="one">Alege fișierul…</item> | ||||
|         <item quantity="few">Alege fișierele…</item> | ||||
|         <item quantity="other">Alege fișierele…</item> | ||||
|     </plurals> | ||||
|     <plurals name="afc_title_choose_files_directories"> | ||||
|         <item quantity="one">Alege fișierul/dosarul…</item> | ||||
|         <item quantity="few">Alege fișierele/dosarele…</item> | ||||
|         <item quantity="other">Alege fișierele/dosarele…</item> | ||||
|     </plurals> | ||||
| </resources> | ||||
|   | ||||
| @@ -59,7 +59,7 @@ namespace keepass2android | ||||
| 					} | ||||
| 					catch (ActivityNotFoundException) | ||||
| 					{ | ||||
| 						Toast.MakeText(Context, Resource.String.no_url_handler, ToastLength.Long).Show(); | ||||
|                         App.Kp2a.ShowMessage(Context, Resource.String.no_url_handler, MessageSeverity.Error); | ||||
| 					} | ||||
|  | ||||
| 				}; | ||||
| @@ -71,7 +71,7 @@ namespace keepass2android | ||||
| 				} | ||||
| 				catch (ActivityNotFoundException) | ||||
| 				{ | ||||
| 					Toast.MakeText(Context, Resource.String.no_url_handler, ToastLength.Long).Show(); | ||||
| 					App.Kp2a.ShowMessage(Context, Resource.String.no_url_handler,  MessageSeverity.Error); | ||||
| 				} | ||||
| 			}; | ||||
| 			FindViewById(Resource.Id.translate).Click += delegate | ||||
| @@ -82,7 +82,7 @@ namespace keepass2android | ||||
| 				} | ||||
| 				catch (ActivityNotFoundException) | ||||
| 				{ | ||||
| 					Toast.MakeText(Context, Resource.String.no_url_handler, ToastLength.Long).Show(); | ||||
| 					App.Kp2a.ShowMessage(Context, Resource.String.no_url_handler,  MessageSeverity.Error); | ||||
| 				} | ||||
| 			}; FindViewById(Resource.Id.donate).Click += delegate | ||||
| 			{ | ||||
|   | ||||
| @@ -14,7 +14,7 @@ using keepass2android; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| 	[Activity(Label = AppNames.AppName)] | ||||
| 	[Activity(Label = AppNames.AppName, Theme = "@style/Kp2aTheme_BlueNoActionBar")] | ||||
| 	public class AppKilledInfo : Activity, IDialogInterfaceOnDismissListener | ||||
| 	{ | ||||
| 		protected override void OnCreate(Bundle bundle) | ||||
|   | ||||
							
								
								
									
										10183
									
								
								src/keepass2android-app/Assets/MostPopularPasswords.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10183
									
								
								src/keepass2android-app/Assets/MostPopularPasswords.txt
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -31,6 +31,12 @@ namespace keepass2android | ||||
| 			MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx); | ||||
| 			builder.SetTitle(ctx.GetString(Resource.String.ChangeLog_title)); | ||||
| 			List<string> changeLog = new List<string>{ | ||||
| 				#if !NoNet | ||||
| 				BuildChangelogString(ctx, new List<int>{Resource.Array.ChangeLog_1_14_net}, "1.14"), | ||||
| 				#endif | ||||
|  | ||||
|                 BuildChangelogString(ctx, new List<int>{Resource.Array.ChangeLog_1_13}, "1.13"), | ||||
|  | ||||
|                 BuildChangelogString(ctx, new List<int>{Resource.Array.ChangeLog_1_12 | ||||
| #if !NoNet | ||||
| 					,Resource.Array.ChangeLog_1_12_net | ||||
|   | ||||
| @@ -183,7 +183,7 @@ namespace keepass2android | ||||
| 			// Verify that a password or keyfile is set | ||||
| 			if (password.Length == 0 && !keyfileCheckbox.Checked) | ||||
| 			{ | ||||
| 				Toast.MakeText(this, Resource.String.error_nopass, ToastLength.Long).Show(); | ||||
| 				App.Kp2a.ShowMessage(this, Resource.String.error_nopass,  MessageSeverity.Error); | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| @@ -207,7 +207,7 @@ namespace keepass2android | ||||
| 				} | ||||
| 				catch (Exception) | ||||
| 				{ | ||||
| 					Toast.MakeText(this, Resource.String.error_adding_keyfile, ToastLength.Long).Show(); | ||||
| 					App.Kp2a.ShowMessage(this, Resource.String.error_adding_keyfile,  MessageSeverity.Error); | ||||
| 					return; | ||||
| 				} | ||||
| 			} | ||||
| @@ -235,7 +235,7 @@ namespace keepass2android | ||||
| 			if (! pass.Equals(confpass)) | ||||
| 			{ | ||||
| 				// Passwords do not match | ||||
| 				Toast.MakeText(this, Resource.String.error_pass_match, ToastLength.Long).Show(); | ||||
| 				App.Kp2a.ShowMessage(this, Resource.String.error_pass_match,  MessageSeverity.Error); | ||||
| 				return false; | ||||
| 			} | ||||
| 			return true; | ||||
|   | ||||
| @@ -28,7 +28,7 @@ namespace keepass2android | ||||
| 			} | ||||
| 			catch (Exception e) | ||||
| 			{ | ||||
| 				Finish(false, e.Message); | ||||
| 				Finish(false, Util.GetErrorMessage(e)); | ||||
| 			} | ||||
|  | ||||
| 		} | ||||
|   | ||||
| @@ -19,7 +19,7 @@ using keepass2android.services.AutofillBase; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
|     [Activity(Label = "DisableAutofillForQueryActivity")] | ||||
|     [Activity(Label = "DisableAutofillForQueryActivity", Theme = "@style/Kp2aTheme_ActionBar")] | ||||
|     public class DisableAutofillForQueryActivity : Activity | ||||
|     { | ||||
|         public IAutofillIntentBuilder IntentBuilder = new Kp2aAutofillIntentBuilder(); | ||||
| @@ -41,7 +41,7 @@ namespace keepass2android | ||||
|             string requestedUrl = Intent.GetStringExtra(ChooseForAutofillActivityBase.ExtraQueryString); | ||||
|             if (requestedUrl == null) | ||||
|             { | ||||
|                 Toast.MakeText(this, "Cannot execute query for null.", ToastLength.Long).Show(); | ||||
|                 App.Kp2a.ShowMessage(this, "Cannot execute query for null.",  MessageSeverity.Error); | ||||
|                 RestartApp(); | ||||
|                 return; | ||||
|             } | ||||
| @@ -63,8 +63,6 @@ namespace keepass2android | ||||
|  | ||||
|             prefs.Edit().PutStringSet("AutoFillDisabledQueries", disabledValues).Commit(); | ||||
|  | ||||
|             bool isManual = Intent.GetBooleanExtra(ChooseForAutofillActivityBase.ExtraIsManualRequest, false); | ||||
|              | ||||
|             Intent reply = new Intent(); | ||||
|             FillResponse.Builder builder = new FillResponse.Builder(); | ||||
|             AssistStructure structure = (AssistStructure)Intent.GetParcelableExtra(AutofillManager.ExtraAssistStructure); | ||||
| @@ -77,7 +75,7 @@ namespace keepass2android | ||||
|             StructureParser parser = new StructureParser(this, structure); | ||||
|             try | ||||
|             { | ||||
|                 parser.ParseForFill(isManual); | ||||
|                 parser.ParseForFill(); | ||||
|  | ||||
|             } | ||||
|             catch (Java.Lang.SecurityException e) | ||||
|   | ||||
| @@ -78,7 +78,7 @@ namespace keepass2android | ||||
|             var task = new EntryActivity.WriteBinaryTask(_activity, App.Kp2a, new ActionOnFinish(_activity, (success, message, activity) => | ||||
|                 { | ||||
|                     if (!success) | ||||
|                         Toast.MakeText(activity, message, ToastLength.Long).Show(); | ||||
|                         App.Kp2a.ShowMessage(activity, message,  MessageSeverity.Error); | ||||
|                 } | ||||
|             ), ((EntryActivity)_activity).Entry.Binaries.Get(_binaryToSave), ioc); | ||||
|             ProgressTask pt = new ProgressTask(App.Kp2a, _activity, task); | ||||
| @@ -107,8 +107,8 @@ namespace keepass2android | ||||
|  | ||||
| 	    public const int requestCodeBinaryFilename = 42376; | ||||
|         public const int requestCodeSelFileStorageForWriteAttachment = 42377; | ||||
| 	     | ||||
|  | ||||
|         protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content); | ||||
|  | ||||
|         public static void Launch(Activity act, PwEntry pw, int pos, AppTask appTask, ActivityFlags? flags = null, int historyIndex=-1) | ||||
| 		{ | ||||
| @@ -767,9 +767,9 @@ namespace keepass2android | ||||
|  | ||||
| 	            if (parent == null || (parent.Exists() && !parent.IsDirectory)) | ||||
| 	            { | ||||
| 	                Toast.MakeText(this, | ||||
| 	                App.Kp2a.ShowMessage(this, | ||||
| 	                    Resource.String.error_invalid_path, | ||||
| 	                    ToastLength.Long).Show(); | ||||
| 	                     MessageSeverity.Error); | ||||
| 	                return null; | ||||
| 	            } | ||||
|  | ||||
| @@ -778,9 +778,9 @@ namespace keepass2android | ||||
| 	                // Create parent directory | ||||
| 	                if (!parent.Mkdirs()) | ||||
| 	                { | ||||
| 	                    Toast.MakeText(this, | ||||
| 	                    App.Kp2a.ShowMessage(this, | ||||
| 	                        Resource.String.error_could_not_create_parent, | ||||
| 	                        ToastLength.Long).Show(); | ||||
| 	                         MessageSeverity.Error); | ||||
| 	                    return null; | ||||
|  | ||||
| 	                } | ||||
| @@ -794,18 +794,18 @@ namespace keepass2android | ||||
| 	            } | ||||
| 	            catch (Exception exWrite) | ||||
| 	            { | ||||
| 	                Toast.MakeText(this, | ||||
| 	                App.Kp2a.ShowMessage(this, | ||||
| 	                    GetString(Resource.String.SaveAttachment_Failed, new Java.Lang.Object[] {filename}) | ||||
| 	                    + exWrite.Message, ToastLength.Long).Show(); | ||||
| 	                    + Util.GetErrorMessage(exWrite),  MessageSeverity.Error); | ||||
| 	                return null; | ||||
| 	            } | ||||
| 	            finally | ||||
| 	            { | ||||
| 	                MemUtil.ZeroByteArray(pbData); | ||||
| 	            } | ||||
| 	            Toast.MakeText(this, | ||||
| 	            App.Kp2a.ShowMessage(this, | ||||
| 	                GetString(Resource.String.SaveAttachment_doneMessage, new Java.Lang.Object[] {filename}), | ||||
| 	                ToastLength.Short).Show(); | ||||
| 	                 MessageSeverity.Info); | ||||
| 	            return Uri.Parse("content://" + AttachmentContentProvider.Authority + "/" | ||||
| 	                             + filename); | ||||
| 	        } | ||||
| @@ -838,7 +838,7 @@ namespace keepass2android | ||||
| 				catch (ActivityNotFoundException) | ||||
| 				{ | ||||
| 					//ignore | ||||
| 					Toast.MakeText(this, "Couldn't open file", ToastLength.Short).Show(); | ||||
| 					App.Kp2a.ShowMessage(this, "Couldn't open file",  MessageSeverity.Error); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| @@ -1305,7 +1305,7 @@ namespace keepass2android | ||||
| 	            } | ||||
| 	            catch (Exception ex) | ||||
| 	            { | ||||
| 	                Finish(false, ex.Message); | ||||
| 	                Finish(false, Util.GetErrorMessage(ex)); | ||||
| 	            } | ||||
|  | ||||
|  | ||||
| @@ -1546,10 +1546,10 @@ namespace keepass2android | ||||
|             string url = _stringViews[urlFieldKey].Text; | ||||
| 			if (url == null) return false; | ||||
|  | ||||
| 			// Default http:// if no protocol specified | ||||
| 			// Default https:// if no protocol specified | ||||
| 			if ((!url.Contains(":") || (url.StartsWith("www.")))) | ||||
| 			{ | ||||
| 				url = "http://" + url; | ||||
| 				url = "https://" + url; | ||||
| 			} | ||||
|  | ||||
| 			try | ||||
| @@ -1558,7 +1558,7 @@ namespace keepass2android | ||||
| 			} | ||||
| 			catch (ActivityNotFoundException) | ||||
| 			{ | ||||
| 				Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show(); | ||||
| 				App.Kp2a.ShowMessage(this, Resource.String.no_url_handler,  MessageSeverity.Error); | ||||
| 			} | ||||
| 			return true; | ||||
| 		} | ||||
|   | ||||
| @@ -37,8 +37,10 @@ using System.Net; | ||||
| using System.Text; | ||||
| using Android.Content.Res; | ||||
| using Android.Database; | ||||
| #if !NO_QR_SCANNER | ||||
| using Android.Gms.Common; | ||||
| using Android.Gms.Tasks; | ||||
| #endif | ||||
| using Android.Graphics; | ||||
| using Android.Graphics.Drawables; | ||||
| using Android.Runtime; | ||||
| @@ -55,19 +57,20 @@ using Object = Java.Lang.Object; | ||||
| using Uri = Android.Net.Uri; | ||||
| using Resource = keepass2android.Resource; | ||||
| using Google.Android.Material.TextField; | ||||
| #if !NO_QR_SCANNER | ||||
| using Xamarin.Google.MLKit.Vision.Barcode.Common; | ||||
| using Xamarin.Google.MLKit.Vision.CodeScanner; | ||||
| #endif | ||||
| using Console = System.Console; | ||||
| using Task = Android.Gms.Tasks.Task; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| 	[Activity(Label = "@string/app_name", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden, Theme = "@style/Kp2aTheme_ActionBar")]			 | ||||
| 	public class EntryEditActivity : LockCloseActivity { | ||||
| 	     | ||||
|  | ||||
| 	     | ||||
| 		public const String KeyEntry = "entry"; | ||||
|         protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content); | ||||
|  | ||||
|         public const String KeyEntry = "entry"; | ||||
| 		public const String KeyParent = "parent"; | ||||
| 		public const String KeyTemplateUuid = "KeyTemplateUuid"; | ||||
| 		 | ||||
| @@ -331,13 +334,18 @@ namespace keepass2android | ||||
|  | ||||
|         } | ||||
|  | ||||
| 	    protected override void OnStart() | ||||
|         private bool hasRequestedKeyboardActivation = false; | ||||
|         protected override void OnStart() | ||||
| 	    { | ||||
| 	        base.OnStart(); | ||||
| 	        if (PreferenceManager.GetDefaultSharedPreferences(this) | ||||
| 	            .GetBoolean(GetString(Resource.String.UseKp2aKeyboardInKp2a_key), false)) | ||||
| 	            .GetBoolean(GetString(Resource.String.UseKp2aKeyboardInKp2a_key), false) | ||||
|                 && (!hasRequestedKeyboardActivation)) | ||||
| 	        { | ||||
| 	            CopyToClipboardService.ActivateKeyboard(this); | ||||
|                 //only try this once. if the user clicks cancel, we don't want to ask again | ||||
| 				// (it may happen that the activity is restarted because of the NewTask flag immediately after the dialog) | ||||
|                 hasRequestedKeyboardActivation = true; | ||||
|                 CopyToClipboardService.ActivateKeyboard(this); | ||||
| 	        } | ||||
|         } | ||||
|  | ||||
| @@ -715,7 +723,7 @@ namespace keepass2android | ||||
| 			} | ||||
| 			catch(Exception exAttach) | ||||
| 			{ | ||||
| 				Toast.MakeText(this, GetString(Resource.String.AttachFailed)+" "+exAttach.Message, ToastLength.Long).Show(); | ||||
| 				App.Kp2a.ShowMessage(this, GetString(Resource.String.AttachFailed)+" "+ Util.GetErrorMessage(exAttach),  MessageSeverity.Error); | ||||
| 			} | ||||
| 			State.EntryModified = true; | ||||
| 			PopulateBinaries(); | ||||
| @@ -833,7 +841,7 @@ namespace keepass2android | ||||
| 						string s = Util.GetFilenameFromInternalFileChooser(data, this); | ||||
| 						if (s == null) | ||||
| 						{ | ||||
| 							Toast.MakeText(this, "No URI retrieved.", ToastLength.Short).Show(); | ||||
| 							App.Kp2a.ShowMessage(this, "No URI retrieved.",  MessageSeverity.Error); | ||||
| 							return; | ||||
| 						} | ||||
| 						uri = Uri.Parse(s); | ||||
| @@ -1139,7 +1147,7 @@ namespace keepass2android | ||||
| 				} | ||||
|                 else | ||||
|                 { | ||||
| 					Toast.MakeText(this, "did not find target field", ToastLength.Long).Show(); | ||||
| 					App.Kp2a.ShowMessage(this, "did not find target field",  MessageSeverity.Error); | ||||
|                 } | ||||
|                  | ||||
| 				 | ||||
| @@ -1153,12 +1161,15 @@ namespace keepass2android | ||||
|             { | ||||
|                 dlgView.FindViewById(Resource.Id.totp_custom_settings_group).Visibility = args.IsChecked ? ViewStates.Visible : ViewStates.Gone; | ||||
|             }; | ||||
|  | ||||
|             dlgView.FindViewById<Button>(Resource.Id.totp_scan).Click += async (object o, EventArgs args) => | ||||
| #if NO_QR_SCANNER | ||||
|             dlgView.FindViewById<Button>(Resource.Id.totp_scan).Visibility = ViewStates.Gone; | ||||
| #else | ||||
| 			dlgView.FindViewById<Button>(Resource.Id.totp_scan).Click += async (object o, EventArgs args) => | ||||
|             { | ||||
|                 if (GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(this) != ConnectionResult.Success) | ||||
|                 { | ||||
|                     Toast.MakeText(this, Resource.String.qr_scanning_error_no_google_play_services, ToastLength.Long); | ||||
|                     App.Kp2a.ShowMessage(this, Resource.String.qr_scanning_error_no_google_play_services, | ||||
|                         MessageSeverity.Error); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
| @@ -1178,16 +1189,17 @@ namespace keepass2android | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             Toast.MakeText(this, "Scanned code should contain an otpauth:// text.", ToastLength.Long).Show(); | ||||
|                             App.Kp2a.ShowMessage(this, "Scanned code should contain an otpauth:// text.",  MessageSeverity.Warning); | ||||
|                         } | ||||
|                     })) | ||||
|                     .AddOnFailureListener(new FailureListener((e) => | ||||
|                     { | ||||
|                         Console.WriteLine($"Scan failed: {e.Message}"); | ||||
|                         Console.WriteLine($"Scan failed: {Util.GetErrorMessage(e)}"); | ||||
|                     })); | ||||
|  | ||||
|  | ||||
|             }; | ||||
| #endif | ||||
|  | ||||
| 			//copy values from entry into dialog | ||||
| 			View ees = (View)sender.Parent; | ||||
| @@ -1503,7 +1515,7 @@ namespace keepass2android | ||||
| 			// Require title | ||||
| 			String title = Util.GetEditText(this, Resource.Id.entry_title); | ||||
| 			if ( title.Length == 0 ) { | ||||
| 				Toast.MakeText(this, Resource.String.error_title_required, ToastLength.Long).Show(); | ||||
| 				App.Kp2a.ShowMessage(this, Resource.String.error_title_required,  MessageSeverity.Error); | ||||
| 				return false; | ||||
| 			} | ||||
| 			 | ||||
| @@ -1513,7 +1525,7 @@ namespace keepass2android | ||||
| 			DateTime newExpiry = new DateTime(); | ||||
| 			if ((State.Entry.Expires) && (!DateTime.TryParse( Util.GetEditText(this,Resource.Id.entry_expires), out newExpiry))) | ||||
| 		    { | ||||
| 				Toast.MakeText(this, Resource.String.error_invalid_expiry_date, ToastLength.Long).Show(); | ||||
| 				App.Kp2a.ShowMessage(this, Resource.String.error_invalid_expiry_date,  MessageSeverity.Error); | ||||
| 				return false; | ||||
| 			} | ||||
| 			State.Entry.ExpiryTime = newExpiry.ToUniversalTime(); | ||||
| @@ -1527,13 +1539,13 @@ namespace keepass2android | ||||
| 				string key = keyView.Text; | ||||
| 				 | ||||
| 				if (String.IsNullOrEmpty(key)) { | ||||
| 					Toast.MakeText(this, Resource.String.error_string_key, ToastLength.Long).Show(); | ||||
| 					App.Kp2a.ShowMessage(this, Resource.String.error_string_key,  MessageSeverity.Error); | ||||
| 					return false; | ||||
| 				} | ||||
|  | ||||
| 				if (allKeys.Contains(key)) | ||||
| 				{ | ||||
| 					Toast.MakeText(this, GetString(Resource.String.error_string_duplicate_key, new Object[]{key}), ToastLength.Long).Show(); | ||||
| 					App.Kp2a.ShowMessage(this, GetString(Resource.String.error_string_duplicate_key, new Object[]{key}),  MessageSeverity.Error); | ||||
| 					return false; | ||||
| 				} | ||||
|  | ||||
| @@ -1566,6 +1578,7 @@ namespace keepass2android | ||||
|  | ||||
| 		} | ||||
| 	} | ||||
| #if !NO_QR_SCANNER | ||||
|     public class SuccessListener : Object, IOnSuccessListener | ||||
|     { | ||||
|         private readonly Action<Barcode> _onSuccess; | ||||
| @@ -1595,8 +1608,9 @@ namespace keepass2android | ||||
|             _onFailure?.Invoke(e); | ||||
|         } | ||||
|     } | ||||
| #endif | ||||
|  | ||||
|     public class DefaultEdit : EditModeBase | ||||
| 	public class DefaultEdit : EditModeBase | ||||
| 	{ | ||||
| 		 | ||||
| 	} | ||||
|   | ||||
| @@ -29,9 +29,9 @@ namespace keepass2android | ||||
|             var exportDb = new ExportDatabaseActivity.ExportDb(_activity, App.Kp2a, new ActionOnFinish(_activity, (success, message, activity) => | ||||
|                 { | ||||
|                     if (!success) | ||||
|                         Toast.MakeText(activity, message, ToastLength.Long).Show(); | ||||
|                         App.Kp2a.ShowMessage(activity, message,  MessageSeverity.Error); | ||||
|                     else | ||||
|                         Toast.MakeText(activity, _activity.GetString(Resource.String.export_database_successful), ToastLength.Long).Show(); | ||||
|                         App.Kp2a.ShowMessage(activity, _activity.GetString(Resource.String.export_database_successful),  MessageSeverity.Info); | ||||
|                     activity.Finish(); | ||||
|                 } | ||||
|             ), _ffp, ioc); | ||||
| @@ -140,7 +140,7 @@ namespace keepass2android | ||||
| 				} | ||||
| 				catch (Exception ex) | ||||
| 				{ | ||||
| 					Finish(false, ex.Message); | ||||
| 					Finish(false, Util.GetErrorMessage(ex)); | ||||
| 				} | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -107,7 +107,7 @@ namespace keepass2android | ||||
|                             { | ||||
|                                 if (!success) | ||||
|                                 { | ||||
|                                     Toast.MakeText(activity, messageOrFilename, ToastLength.Long).Show(); | ||||
|                                     App.Kp2a.ShowMessage(activity, messageOrFilename,  MessageSeverity.Error); | ||||
|                                     return; | ||||
|                                 } | ||||
|                                 SaveFile(new IOConnectionInfo { Path = FileSelectHelper.ConvertFilenameToIocPath(messageOrFilename) }); | ||||
|   | ||||
| @@ -4,12 +4,15 @@ using System.Linq; | ||||
| using System.Net; | ||||
| #if !NoNet | ||||
| using FluentFTP; | ||||
| using static Kp2aBusinessLogic.Io.SmbFileStorage; | ||||
| #endif | ||||
| using System.Text; | ||||
|  | ||||
| using Android.App; | ||||
| using Android.Content; | ||||
| using Android.Content.Res; | ||||
| using Android.OS; | ||||
| using Android.Preferences; | ||||
| using Android.Runtime; | ||||
| using Android.Views; | ||||
| using Android.Widget; | ||||
| @@ -23,6 +26,7 @@ using Keepass2android.Javafilestorage; | ||||
| using KeePassLib.Serialization; | ||||
| using KeePassLib.Utility; | ||||
|  | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| 	public class FileSelectHelper | ||||
| @@ -115,6 +119,7 @@ namespace keepass2android | ||||
| 				string keyContent = keyContentTxt.Text; | ||||
|  | ||||
| 				string toastMsg = null; | ||||
| 				MessageSeverity severity = MessageSeverity.Info; | ||||
|                 if (!string.IsNullOrEmpty(keyName) && !string.IsNullOrEmpty(keyContent)) | ||||
| 				{ | ||||
| 					try | ||||
| @@ -127,8 +132,10 @@ namespace keepass2android | ||||
| 					catch (Exception e) | ||||
| 					{ | ||||
| 						toastMsg = ctx.GetString(Resource.String.private_key_save_failed, | ||||
| 							new Java.Lang.Object[] { e.Message }); | ||||
| 					} | ||||
| 							new Java.Lang.Object[] { Util.GetErrorMessage(e)}); | ||||
|                         severity = MessageSeverity.Error; | ||||
|  | ||||
|                     } | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| @@ -136,7 +143,7 @@ namespace keepass2android | ||||
| 				} | ||||
|  | ||||
| 				if (toastMsg!= null) { | ||||
|                     Toast.MakeText(_activity, toastMsg, ToastLength.Long).Show(); | ||||
|                     App.Kp2a.ShowMessage(_activity, toastMsg,  severity); | ||||
|                 } | ||||
|  | ||||
| 				UpdatePrivateKeyNames(keyNamesAdapter, fileStorage, ctx); | ||||
| @@ -153,7 +160,7 @@ namespace keepass2android | ||||
|  | ||||
| 					int msgId = deleted ? Resource.String.private_key_delete : Resource.String.private_key_delete_failed; | ||||
| 					string msg = ctx.GetString(msgId, new Java.Lang.Object[] { keyName }); | ||||
|                     Toast.MakeText(_activity, msg, ToastLength.Long).Show(); | ||||
|                     App.Kp2a.ShowMessage(_activity, msg,  deleted ? MessageSeverity.Info :MessageSeverity.Error); | ||||
|  | ||||
| 					UpdatePrivateKeyNames(keyNamesAdapter, fileStorage, ctx); | ||||
| 					keySpinner.SetSelection(SftpKeySpinnerCreateNewIdx); | ||||
| @@ -271,7 +278,8 @@ namespace keepass2android | ||||
|  | ||||
| 			builder.SetNegativeButton(Android.Resource.String.Cancel, evtH); | ||||
| 			builder.SetTitle(activity.GetString(Resource.String.enter_sftp_login_title)); | ||||
| 			Dialog dialog = builder.Create(); | ||||
|             builder.SetCancelable(false); | ||||
|             Dialog dialog = builder.Create(); | ||||
|  | ||||
| 			dialog.Show(); | ||||
| #endif | ||||
| @@ -316,7 +324,7 @@ namespace keepass2android | ||||
| 			View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.httpcredentials, null); | ||||
| 		    if (!defaultPath.EndsWith(_schemeSeparator)) | ||||
| 		    { | ||||
| 		        var webdavStorage = new Keepass2android.Javafilestorage.WebDavStorage(App.Kp2a.CertificateErrorHandler); | ||||
| 		        var webdavStorage = CreateWebdavStorage(activity); | ||||
| 		        var connInfo = webdavStorage.SplitStringToConnectionInfo(defaultPath); | ||||
| 		        dlgContents.FindViewById<EditText>(Resource.Id.http_url).Text = connInfo.Url; | ||||
| 		        dlgContents.FindViewById<EditText>(Resource.Id.http_user).Text = connInfo.Username; | ||||
| @@ -336,7 +344,7 @@ namespace keepass2android | ||||
| 										  string scheme = defaultPath.Substring(0, defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal)); | ||||
| 										  if (host.Contains(_schemeSeparator) == false) | ||||
| 											  host = scheme + _schemeSeparator + host; | ||||
| 										  string httpPath = new Keepass2android.Javafilestorage.WebDavStorage(null).BuildFullPath(host, user, | ||||
| 										  string httpPath = CreateWebdavStorage(activity).BuildFullPath(host, user, | ||||
| 																										  password); | ||||
| 										  onStartBrowse(httpPath); | ||||
| 									  }); | ||||
| @@ -344,13 +352,61 @@ namespace keepass2android | ||||
|  | ||||
| 			builder.SetNegativeButton(Android.Resource.String.Cancel, evtH); | ||||
| 			builder.SetTitle(activity.GetString(Resource.String.enter_http_login_title)); | ||||
| 			Dialog dialog = builder.Create(); | ||||
|             builder.SetCancelable(false); | ||||
|             Dialog dialog = builder.Create(); | ||||
|  | ||||
| 			dialog.Show(); | ||||
| #endif | ||||
| 		} | ||||
|  | ||||
| 		private void ShowFtpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath) | ||||
|  | ||||
|         private void ShowSmbDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath) | ||||
|         { | ||||
| #if !EXCLUDE_JAVAFILESTORAGE && !NoNet | ||||
|             MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity); | ||||
|             View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.smbcredentials, null); | ||||
|             if (!defaultPath.EndsWith(_schemeSeparator)) | ||||
|             { | ||||
|                 SmbConnectionInfo ci = new SmbConnectionInfo(new IOConnectionInfo() { Path = defaultPath }); | ||||
|                  | ||||
|                 dlgContents.FindViewById<EditText>(Resource.Id.smb_url).Text = ci.GetPathWithoutCredentials(); | ||||
|                 dlgContents.FindViewById<EditText>(Resource.Id.smb_domain).Text = ci.Domain; | ||||
|                 dlgContents.FindViewById<EditText>(Resource.Id.smb_user).Text = ci.Username; | ||||
|                 dlgContents.FindViewById<EditText>(Resource.Id.smb_password).Text = ci.Password; | ||||
|  | ||||
|  | ||||
|             } | ||||
|             builder.SetView(dlgContents); | ||||
|             builder.SetPositiveButton(Android.Resource.String.Ok, | ||||
|                                       (sender, args) => | ||||
|                                       { | ||||
|                                           string url = dlgContents.FindViewById<EditText>(Resource.Id.smb_url).Text; | ||||
|  | ||||
|                                           string user = dlgContents.FindViewById<EditText>(Resource.Id.smb_user).Text; | ||||
|                                           string password = dlgContents.FindViewById<EditText>(Resource.Id.smb_password).Text; | ||||
|                                           string domain = dlgContents.FindViewById<EditText>(Resource.Id.smb_domain).Text; | ||||
|  | ||||
|                                           string fullPath = SmbConnectionInfo.FromUrlAndCredentials(url, user, password, domain).ToPath(); | ||||
|                                           onStartBrowse(fullPath); | ||||
|                                       }); | ||||
| 			builder.SetCancelable(false); | ||||
| 			 | ||||
|             EventHandler<DialogClickEventArgs> evtH = new EventHandler<DialogClickEventArgs>((sender, e) => onCancel()); | ||||
|  | ||||
|             builder.SetNegativeButton(Android.Resource.String.Cancel, evtH); | ||||
|             builder.SetTitle(activity.GetString(Resource.String.enter_smb_login_title)); | ||||
|             Dialog dialog = builder.Create(); | ||||
|  | ||||
|             dialog.Show(); | ||||
| #endif | ||||
| 		} | ||||
| #if !NoNet | ||||
|         private static WebDavStorage CreateWebdavStorage(Activity activity) | ||||
|         { | ||||
|             return new WebDavStorage(App.Kp2a.CertificateErrorHandler, App.Kp2a.WebDavChunkedUploadSize); | ||||
|         } | ||||
| #endif | ||||
|         private void ShowFtpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath) | ||||
| 		{ | ||||
| #if !NoNet | ||||
| 			MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity); | ||||
| @@ -400,7 +456,8 @@ namespace keepass2android | ||||
|  | ||||
| 			builder.SetNegativeButton(Android.Resource.String.Cancel, evtH); | ||||
| 			builder.SetTitle(activity.GetString(Resource.String.enter_ftp_login_title)); | ||||
| 			Dialog dialog = builder.Create(); | ||||
|             builder.SetCancelable(false); | ||||
|             Dialog dialog = builder.Create(); | ||||
|  | ||||
| 			dialog.Show(); | ||||
| #endif | ||||
| @@ -460,7 +517,8 @@ namespace keepass2android | ||||
|  | ||||
| 			builder.SetNegativeButton(Android.Resource.String.Cancel, evtH); | ||||
| 			builder.SetTitle(activity.GetString(Resource.String.enter_mega_login_title)); | ||||
| 			Dialog dialog = builder.Create(); | ||||
|             builder.SetCancelable(false); | ||||
|             Dialog dialog = builder.Create(); | ||||
|  | ||||
| 			dialog.Show(); | ||||
| #endif | ||||
| @@ -476,7 +534,9 @@ namespace keepass2android | ||||
| 				ShowFtpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath); | ||||
| 			else if ((defaultPath.StartsWith("http://")) || (defaultPath.StartsWith("https://"))) | ||||
| 				ShowHttpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath); | ||||
| 			else if (defaultPath.StartsWith("owncloud://")) | ||||
|             else if ((defaultPath.StartsWith("smb://"))) | ||||
|                 ShowSmbDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath); | ||||
|             else if (defaultPath.StartsWith("owncloud://")) | ||||
| 				ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "owncloud"); | ||||
| 			else if (defaultPath.StartsWith("nextcloud://")) | ||||
| 			    ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "nextcloud"); | ||||
| @@ -515,7 +575,7 @@ namespace keepass2android | ||||
| 										  string scheme = defaultPath.Substring(0,defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal)); | ||||
| 										  if (host.Contains(_schemeSeparator) == false) | ||||
| 											  host = scheme + _schemeSeparator + host; | ||||
| 										  string httpPath = new Keepass2android.Javafilestorage.WebDavStorage(null).BuildFullPath(WebDavFileStorage.Owncloud2Webdav(host, subtype == "owncloud" ? WebDavFileStorage.owncloudPrefix : WebDavFileStorage.nextcloudPrefix), user, | ||||
| 										  string httpPath = CreateWebdavStorage(activity).BuildFullPath(WebDavFileStorage.Owncloud2Webdav(host, subtype == "owncloud" ? WebDavFileStorage.owncloudPrefix : WebDavFileStorage.nextcloudPrefix), user, | ||||
| 																										  password); | ||||
| 										  onStartBrowse(httpPath); | ||||
| 									  }); | ||||
| @@ -523,7 +583,8 @@ namespace keepass2android | ||||
|  | ||||
| 			builder.SetNegativeButton(Android.Resource.String.Cancel, evtH); | ||||
| 			builder.SetTitle(activity.GetString(subtype == "owncloud" ?  Resource.String.enter_owncloud_login_title : Resource.String.enter_nextcloud_login_title)); | ||||
| 			Dialog dialog = builder.Create(); | ||||
|             builder.SetCancelable(false); | ||||
|             Dialog dialog = builder.Create(); | ||||
| 		    dlgContents.FindViewById<EditText>(Resource.Id.owncloud_url).SetHint(subtype == "owncloud" ? Resource.String.hint_owncloud_url : Resource.String.hint_nextcloud_url); | ||||
|             dialog.Show(); | ||||
| #endif | ||||
| @@ -581,9 +642,9 @@ namespace keepass2android | ||||
| 			// Make sure file name exists | ||||
| 			if (filename.Length == 0) | ||||
| 			{ | ||||
| 				Toast.MakeText(_activity, | ||||
| 				App.Kp2a.ShowMessage(_activity, | ||||
| 								Resource.String.error_filename_required, | ||||
| 								ToastLength.Long).Show(); | ||||
| 								 MessageSeverity.Error); | ||||
| 				return false; | ||||
| 			} | ||||
|  | ||||
| @@ -604,9 +665,9 @@ namespace keepass2android | ||||
| 			} | ||||
| 			catch (NoFileStorageFoundException) | ||||
| 			{ | ||||
| 				Toast.MakeText(_activity, | ||||
| 				App.Kp2a.ShowMessage(_activity, | ||||
| 								"Unexpected scheme in "+filename, | ||||
| 								ToastLength.Long).Show(); | ||||
| 								 MessageSeverity.Error); | ||||
| 				return false; | ||||
| 			} | ||||
|  | ||||
| @@ -620,9 +681,9 @@ namespace keepass2android | ||||
|  | ||||
| 					if (parent == null || (parent.Exists() && !parent.IsDirectory)) | ||||
| 					{ | ||||
| 						Toast.MakeText(_activity, | ||||
| 						App.Kp2a.ShowMessage(_activity, | ||||
| 							            Resource.String.error_invalid_path, | ||||
| 							            ToastLength.Long).Show(); | ||||
| 							             MessageSeverity.Error); | ||||
| 						return false; | ||||
| 					} | ||||
|  | ||||
| @@ -631,9 +692,9 @@ namespace keepass2android | ||||
| 						// Create parent dircetory | ||||
| 						if (!parent.Mkdirs()) | ||||
| 						{ | ||||
| 							Toast.MakeText(_activity, | ||||
| 							App.Kp2a.ShowMessage(_activity, | ||||
| 								            Resource.String.error_could_not_create_parent, | ||||
| 								            ToastLength.Long).Show(); | ||||
| 								             MessageSeverity.Error); | ||||
| 							return false; | ||||
|  | ||||
| 						} | ||||
| @@ -643,11 +704,11 @@ namespace keepass2android | ||||
| 				} | ||||
| 				catch (Java.IO.IOException ex) | ||||
| 				{ | ||||
| 					Toast.MakeText( | ||||
| 					App.Kp2a.ShowMessage( | ||||
| 						_activity, | ||||
| 						_activity.GetText(Resource.String.error_file_not_create) + " " | ||||
| 						+ ex.LocalizedMessage, | ||||
| 						ToastLength.Long).Show(); | ||||
| 						 MessageSeverity.Error); | ||||
| 					return false; | ||||
| 				} | ||||
|  | ||||
| @@ -700,7 +761,7 @@ namespace keepass2android | ||||
| 			_activity.StartActivityForResult(i, _requestCode); | ||||
|  | ||||
| #else | ||||
| 			Toast.MakeText(LocaleManager.LocalizedAppContext, "File chooser is excluded!", ToastLength.Long).Show(); | ||||
| 			App.Kp2a.ShowMessage(LocaleManager.LocalizedAppContext, "File chooser is excluded!",  MessageSeverity.Error); | ||||
| #endif | ||||
| 			return true; | ||||
| 		} | ||||
| @@ -782,7 +843,7 @@ namespace keepass2android | ||||
| 							{ | ||||
| 								if (!success) | ||||
| 								{ | ||||
| 									Toast.MakeText(newActivity, messageOrFilename, ToastLength.Long).Show(); | ||||
| 									App.Kp2a.ShowMessage(newActivity, messageOrFilename,  MessageSeverity.Error); | ||||
| 									return; | ||||
| 								} | ||||
| 								var ioc = new IOConnectionInfo { Path = ConvertFilenameToIocPath(messageOrFilename) }; | ||||
|   | ||||
| @@ -251,7 +251,7 @@ namespace keepass2android | ||||
| 			catch (Exception e) | ||||
| 			{ | ||||
| 				CheckCurrentRadioButton(); | ||||
| 				Toast.MakeText(this, e.ToString(), ToastLength.Long).Show(); | ||||
| 				App.Kp2a.ShowMessage(this, e.ToString(),  MessageSeverity.Error); | ||||
| 				FindViewById(Resource.Id.radio_buttons).Visibility = ViewStates.Visible; | ||||
| 				FindViewById(Resource.Id.fingerprint_auth_container).Visibility = ViewStates.Gone; | ||||
| 			} | ||||
|   | ||||
| @@ -23,6 +23,7 @@ using Android.App; | ||||
| using Android.App.Admin; | ||||
| using Android.Content; | ||||
| using Android.Content.PM; | ||||
| using Android.Content.Res; | ||||
| using Android.Graphics; | ||||
| using Android.OS; | ||||
| using Android.Preferences; | ||||
| @@ -66,10 +67,15 @@ namespace keepass2android | ||||
|             Resource.Id.cb_exclude_lookalike | ||||
|         }; | ||||
|  | ||||
|          | ||||
|  | ||||
| 		PasswordFont _passwordFont = new PasswordFont(); | ||||
|  | ||||
|         private static object _popularPasswordsLock = new object(); | ||||
|         private static bool _popularPasswordsInitialized = false; | ||||
|  | ||||
| 		private ActivityDesign _design; | ||||
|  | ||||
|         private ActivityDesign _design; | ||||
| 	    public GeneratePasswordActivity() | ||||
| 	    { | ||||
| 		    _design = new ActivityDesign(this); | ||||
| @@ -139,12 +145,14 @@ namespace keepass2android | ||||
| 		protected override void OnCreate(Bundle savedInstanceState) { | ||||
| 			_design.ApplyTheme();  | ||||
| 			base.OnCreate(savedInstanceState); | ||||
| 			 | ||||
|  | ||||
| 			SetContentView(Resource.Layout.generate_password); | ||||
| 			SetResult(KeePass.ExitNormal); | ||||
|              | ||||
|  | ||||
| 			var prefs = GetPreferences(FileCreationMode.Private); | ||||
|             new Util.InsetListener(FindViewById(Resource.Id.main_container)).Apply(); | ||||
|  | ||||
|  | ||||
|             var prefs = GetPreferences(FileCreationMode.Private); | ||||
|  | ||||
|  | ||||
|              | ||||
| @@ -302,6 +310,10 @@ namespace keepass2android | ||||
|  | ||||
|  | ||||
|             EditText txtPasswordToSet = (EditText) FindViewById(Resource.Id.password_edit); | ||||
|             txtPasswordToSet.TextChanged += (sender, args) => | ||||
|             { | ||||
|                 Task.Run(() => UpdatePasswordStrengthEstimate(txtPasswordToSet.Text)); | ||||
|             }; | ||||
|  | ||||
|             _passwordFont.ApplyTo(txtPasswordToSet); | ||||
|  | ||||
| @@ -467,51 +479,76 @@ namespace keepass2android | ||||
|                 return; | ||||
|  | ||||
|             String password = ""; | ||||
|             uint passwordBits = 0; | ||||
|              | ||||
|              | ||||
|             Task.Run(() => | ||||
|             { | ||||
|                 password = GeneratePassword(); | ||||
|                 passwordBits = QualityEstimation.EstimatePasswordBits(password.ToCharArray()); | ||||
|                 RunOnUiThread(() => | ||||
|                 { | ||||
|                     EditText txtPassword = (EditText)FindViewById(Resource.Id.password_edit); | ||||
|                     txtPassword.Text = password; | ||||
|  | ||||
|                     var progressBar = FindViewById<ProgressBar>(Resource.Id.pb_password_strength); | ||||
|  | ||||
|                     progressBar.Progress = (int)passwordBits; | ||||
|                     progressBar.Max = 128; | ||||
|  | ||||
|                     Color color = new Color(196, 63, 49); | ||||
|                     if (passwordBits > 40) | ||||
|                     { | ||||
|                         color = new Color(219, 152, 55); | ||||
|                     } | ||||
|  | ||||
|                     if (passwordBits > 64) | ||||
|                     { | ||||
|                         color = new Color(96, 138, 38); | ||||
|                     } | ||||
|  | ||||
|                     if (passwordBits > 100) | ||||
|                     { | ||||
|                         color = new Color(31, 128, 31); | ||||
|                     } | ||||
|  | ||||
|                     progressBar.ProgressDrawable.SetColorFilter(new PorterDuffColorFilter(color, | ||||
|                         PorterDuff.Mode.SrcIn)); | ||||
|  | ||||
|                     FindViewById<TextView>(Resource.Id.tv_password_strength).Text = " " + passwordBits + " bits"; | ||||
|  | ||||
|  | ||||
|  | ||||
|                     UpdateProfileSpinnerSelection(); | ||||
|                 }); | ||||
|             }); | ||||
|              | ||||
|         } | ||||
|  | ||||
|         private void UpdatePasswordStrengthEstimate(string password) | ||||
|         { | ||||
|             lock (_popularPasswordsLock) | ||||
|             { | ||||
|                 if (!_popularPasswordsInitialized) | ||||
|                 { | ||||
|  | ||||
|                     using (StreamReader sr = new StreamReader(Assets.Open("MostPopularPasswords.txt"))) | ||||
|                     { | ||||
|                         var bytes = default(byte[]); | ||||
|                         using (var memstream = new MemoryStream()) | ||||
|                         { | ||||
|                             sr.BaseStream.CopyTo(memstream); | ||||
|                             bytes = memstream.ToArray(); | ||||
|                         } | ||||
|                         PopularPasswords.Add(bytes, false); | ||||
|                     } | ||||
|                 } | ||||
|                      | ||||
|             } | ||||
|             uint passwordBits = QualityEstimation.EstimatePasswordBits(password.ToCharArray()); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|             RunOnUiThread(() => | ||||
|             { | ||||
|                 var progressBar = FindViewById<ProgressBar>(Resource.Id.pb_password_strength); | ||||
|  | ||||
|                 progressBar.Progress = (int)passwordBits; | ||||
|                 progressBar.Max = 128; | ||||
|  | ||||
|                 Color color = new Color(196, 63, 49); | ||||
|                 if (passwordBits > 40) | ||||
|                 { | ||||
|                     color = new Color(219, 152, 55); | ||||
|                 } | ||||
|  | ||||
|                 if (passwordBits > 64) | ||||
|                 { | ||||
|                     color = new Color(96, 138, 38); | ||||
|                 } | ||||
|  | ||||
|                 if (passwordBits > 100) | ||||
|                 { | ||||
|                     color = new Color(31, 128, 31); | ||||
|                 } | ||||
|  | ||||
|                 progressBar.ProgressDrawable.SetColorFilter(new PorterDuffColorFilter(color, | ||||
|                     PorterDuff.Mode.SrcIn)); | ||||
|  | ||||
|                 FindViewById<TextView>(Resource.Id.tv_password_strength).Text = " " + passwordBits + " bits"; | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         private void UpdateProfileSpinnerSelection() | ||||
|         { | ||||
|             int? lastUsedIndex = _profiles.TryFindLastUsedProfileIndex(); | ||||
| @@ -543,7 +580,7 @@ namespace keepass2android | ||||
|             } | ||||
|             catch (Exception e)  | ||||
|             { | ||||
| 				Toast.MakeText(this, e.Message, ToastLength.Long).Show(); | ||||
| 				App.Kp2a.ShowMessage(this, Util.GetErrorMessage(e),  MessageSeverity.Error); | ||||
| 			} | ||||
| 			 | ||||
| 			return password; | ||||
|   | ||||
| @@ -56,6 +56,8 @@ namespace keepass2android | ||||
|  | ||||
|         public const int RequestCodeActivateRealSearch = 12366; | ||||
|  | ||||
|         protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content); | ||||
|  | ||||
|         static readonly Dictionary<int /*resource id*/, int /*prio*/> bottomBarElementsPriority = new Dictionary<int, int>() | ||||
|         { | ||||
|             { Resource.Id.cancel_insert_element, 20 }, | ||||
| @@ -892,9 +894,14 @@ namespace keepass2android | ||||
|                     RegisterInfoTextDisplay( | ||||
|                         "DbReadOnly"); //this ensures that we don't show the general info texts too soon | ||||
|  | ||||
|                     FindViewById<TextView>(Resource.Id.dbreadonly_infotext_text).Text = | ||||
|                     (GetString(Resource.String.FileReadOnlyMessagePre) + " " + | ||||
|                      App.Kp2a.GetResourceString(reason.Result)); | ||||
|                     var infotext_view = FindViewById<TextView>(Resource.Id.dbreadonly_infotext_text); | ||||
|                     if (infotext_view != null) | ||||
|                     { | ||||
|                         infotext_view.Text = | ||||
|                             (GetString(Resource.String.FileReadOnlyMessagePre) + " " + | ||||
|                              App.Kp2a.GetResourceString(reason.Result)); | ||||
|                     } | ||||
|                      | ||||
|                 } | ||||
|             } | ||||
|             UpdateBottomBarElementVisibility(Resource.Id.dbreadonly_infotext, canShow); | ||||
| @@ -927,7 +934,7 @@ namespace keepass2android | ||||
|                 { | ||||
|                     ((GroupBaseActivity)activity)?.StopMovingElements(); | ||||
|                     if (!String.IsNullOrEmpty(message)) | ||||
|                         Toast.MakeText(activity, message, ToastLength.Long).Show(); | ||||
|                         App.Kp2a.ShowMessage(activity, message,  MessageSeverity.Error); | ||||
|                 })); | ||||
|             var progressTask = new ProgressTask(App.Kp2a, this, moveElement); | ||||
|             progressTask.Run(); | ||||
| @@ -1328,7 +1335,7 @@ namespace keepass2android | ||||
|                 { | ||||
|                     Handler.Post(() => | ||||
|                     { | ||||
|                         Toast.MakeText(ActiveActivity ?? LocaleManager.LocalizedAppContext, "Unrecoverable error: " + Message, ToastLength.Long).Show(); | ||||
|                         App.Kp2a.ShowMessage(ActiveActivity ?? LocaleManager.LocalizedAppContext, "Unrecoverable error: " + Message,  MessageSeverity.Error); | ||||
|                     }); | ||||
|  | ||||
|                     App.Kp2a.Lock(false); | ||||
|   | ||||
| @@ -111,9 +111,10 @@ namespace keepass2android | ||||
| 					SetResult (Result.Ok, intent); | ||||
| 					 | ||||
| 					Finish (); | ||||
| 				} else { | ||||
| 					Toast.MakeText (this, Resource.String.error_no_name, ToastLength.Long).Show (); | ||||
| 				} | ||||
| 				} else | ||||
|                 { | ||||
|                     App.Kp2a.ShowMessage(this, Resource.String.error_no_name, MessageSeverity.Error); | ||||
|                 } | ||||
| 			}; | ||||
|  | ||||
| 			if (Intent.HasExtra(KeyGroupUuid)) | ||||
|   | ||||
| @@ -316,7 +316,7 @@ namespace keepass2android | ||||
|                 try { ck.AddUserKey(new KcpKeyFile(strAbs)); } | ||||
|                 catch (InvalidOperationException) | ||||
|                 { | ||||
|                     Toast.MakeText(LocaleManager.LocalizedAppContext,Resource.String.error_adding_keyfile,ToastLength.Long).Show(); | ||||
|                     App.Kp2a.ShowMessage(LocaleManager.LocalizedAppContext,Resource.String.error_adding_keyfile, MessageSeverity.Error); | ||||
|                     return false; | ||||
|                 } | ||||
|                 catch (Exception) { throw; } | ||||
|   | ||||
| @@ -146,7 +146,16 @@ namespace KeeChallenge | ||||
|             { | ||||
|                 using (CryptoStream csDecrypt = (CryptoStream)aes.DecryptStream(msDecrypt, key, inf.IV)) | ||||
|                 { | ||||
|                     csDecrypt.Read(secret, 0, secret.Length); | ||||
|                     //read the secret from the stream | ||||
|                     int totalBytesRead = 0; | ||||
|  | ||||
|                     int bytesRead = csDecrypt.Read(secret, totalBytesRead, secret.Length - totalBytesRead); | ||||
|                     while (bytesRead > 0 && totalBytesRead < secret.Length) | ||||
|                     { | ||||
|                         totalBytesRead += bytesRead; | ||||
|                         bytesRead = csDecrypt.Read(secret, totalBytesRead, secret.Length - totalBytesRead); | ||||
|                     } | ||||
|  | ||||
|                     csDecrypt.Close(); | ||||
|                 } | ||||
|                 msDecrypt.Close(); | ||||
|   | ||||
| @@ -21,8 +21,10 @@ using Android.App; | ||||
| using Android.Content; | ||||
| using Android.Content.PM; | ||||
| using Android.Runtime; | ||||
| using Android.Views; | ||||
| using Google.Android.Material.Dialog; | ||||
| using keepass2android; | ||||
| using keepass2android.Utils; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| @@ -76,19 +78,34 @@ namespace keepass2android | ||||
| 			base.OnPause(); | ||||
| 			 | ||||
| 			TimeoutHelper.Pause(this); | ||||
| 		} | ||||
| 			App.Kp2a.MessagePresenter = new NonePresenter(); | ||||
|         } | ||||
|  | ||||
| 		protected override void OnDestroy() | ||||
| 		{ | ||||
| 			base.OnDestroy(); | ||||
| 			GC.Collect(); | ||||
| 		} | ||||
| 		 | ||||
| 		protected override void OnResume() { | ||||
| 			base.OnResume(); | ||||
| 			 | ||||
| 			TimeoutHelper.Resume(this); | ||||
| 		} | ||||
|  | ||||
|         protected override void OnResume() | ||||
|         { | ||||
|             base.OnResume(); | ||||
|  | ||||
|             TimeoutHelper.Resume(this); | ||||
|             var snackbarAnchorView = SnackbarAnchorView; | ||||
|             if (snackbarAnchorView != null) | ||||
|             { | ||||
|                 App.Kp2a.MessagePresenter = new ChainedSnackbarPresenter(snackbarAnchorView); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 App.Kp2a.MessagePresenter = new ToastPresenter(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|  | ||||
|         protected virtual View? SnackbarAnchorView => null; | ||||
|      | ||||
|  | ||||
| 	    public const int RequestCodeChallengeYubikey = 793; | ||||
|  | ||||
|   | ||||
| @@ -43,7 +43,7 @@ | ||||
|   </queries> | ||||
|  | ||||
|  | ||||
|   <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" /> | ||||
|   <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="35" /> | ||||
|   <permission android:description="@string/permission_desc2" android:icon="@drawable/ic_notify_locked" android:label="KP2A entry search" android:name="keepass2android.keepass2android_debug.permission.KP2aInternalSearch" android:protectionLevel="signature" /> | ||||
|   <permission android:description="@string/permission_desc3" android:icon="@drawable/ic_launcher" android:label="KP2A choose autofill dataset" android:name="keepass2android.keepass2android_debug.permission.Kp2aChooseAutofill" android:protectionLevel="signature" /> | ||||
|   <application | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 			android:versionCode="204" | ||||
| 			android:versionName="1.12-r3" | ||||
| 			package="keepass2android.keepass2android" | ||||
| 			xmlns:tools="http://schemas.android.com/tools" | ||||
| 			android:installLocation="auto"> | ||||
|       android:versionCode="223" | ||||
|       android:versionName="1.14-pre0" | ||||
|       package="keepass2android.keepass2android" | ||||
|       xmlns:tools="http://schemas.android.com/tools" | ||||
|       android:installLocation="auto"> | ||||
|  | ||||
|  | ||||
|   <queries> | ||||
| @@ -42,17 +42,15 @@ | ||||
|       <action android:name="android.intent.action.VIEW" /> | ||||
|     </intent> | ||||
|   </queries> | ||||
|   <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" /> | ||||
|   <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="35" /> | ||||
|  | ||||
|   <permission android:description="@string/permission_desc2" android:icon="@drawable/ic_launcher" android:label="KP2A entry search" android:name="keepass2android.keepass2android.permission.KP2aInternalSearch" android:protectionLevel="signature" /> | ||||
|   <permission android:description="@string/permission_desc3" android:icon="@drawable/ic_launcher" android:label="KP2A choose autofill dataset" android:name="keepass2android.keepass2android.permission.Kp2aChooseAutofill" android:protectionLevel="signature" /> | ||||
|  | ||||
|   <application android:label="keepass2android" | ||||
| 	android:icon="@mipmap/ic_launcher_online" | ||||
| 	android:roundIcon="@mipmap/ic_launcher_online_round" | ||||
| 	android:networkSecurityConfig="@xml/network_security_config" | ||||
|  | ||||
| 	> | ||||
|     android:icon="@mipmap/ic_launcher_online" | ||||
|     android:roundIcon="@mipmap/ic_launcher_online_round" | ||||
|     android:networkSecurityConfig="@xml/network_security_config" | ||||
|   > | ||||
|  | ||||
|     <meta-data | ||||
|       android:name="com.google.mlkit.vision.DEPENDENCIES" | ||||
| @@ -107,16 +105,15 @@ | ||||
|       </intent-filter> | ||||
|     </activity> | ||||
|     <activity android:name="keepass2android.softkeyboard.InputLanguageSelection" | ||||
|                | ||||
|                android:exported="true"> | ||||
|       <!-- android:label="@string/language_selection_title" TODO  --> | ||||
|       <intent-filter> | ||||
|         <action android:name="android.intent.action.MAIN"/> | ||||
|         <action android:name="keepass2android.softkeyboard.INPUT_LANGUAGE_SELECTION"/> | ||||
|         <category android:name="android.intent.category.DEFAULT" /> | ||||
|       </intent-filter> | ||||
|     </activity> | ||||
|     <activity android:configChanges="orientation|keyboard|keyboardHidden" android:label="@string/app_name" android:theme="@style/Kp2aTheme_BlueNoActionBar" android:name="keepass2android.SelectCurrentDbActivity"  android:windowSoftInputMode="adjustResize" android:exported="true"> | ||||
|     <activity android:configChanges="orientation|keyboard|keyboardHidden" android:label="@string/app_name" android:theme="@style/Kp2aTheme_BlueNoActionBar" android:name="keepass2android.SelectCurrentDbActivity" android:windowSoftInputMode="adjustResize" | ||||
|     android:exported="true"> | ||||
|       <intent-filter android:label="@string/app_name"> | ||||
|         <action android:name="android.intent.action.VIEW" /> | ||||
|         <category android:name="android.intent.category.DEFAULT" /> | ||||
|   | ||||
| @@ -1,24 +1,27 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android"  | ||||
| 			android:versionCode="200"  | ||||
| 			android:versionName="1.11-r0"  | ||||
| 			package="keepass2android.keepass2android_nonet" | ||||
| 			xmlns:tools="http://schemas.android.com/tools" | ||||
| 			android:installLocation="auto"> | ||||
| 			 | ||||
| 			 | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|       android:versionCode="223" | ||||
|       android:versionName="1.14-pre0" | ||||
|       package="keepass2android.keepass2android_nonet" | ||||
|       xmlns:tools="http://schemas.android.com/tools" | ||||
|       android:installLocation="auto"> | ||||
|  | ||||
|  | ||||
|   <queries> | ||||
|     <!-- Specific intents and packages we query for (required since Android 11) --> | ||||
|     <package android:name="keepass2android.plugin.keyboardswap2" /> | ||||
|     <package android:name="keepass2android.AncientIconSet" /> | ||||
| 	<package android:name="com.dropbox.android" /> | ||||
|     <package android:name="keepass2android.plugin.qr" /> | ||||
|     <package android:name="it.andreacioni.kp2a.plugin.keelink" /> | ||||
|     <package android:name="com.inputstick.apps.kp2aplugin" /> | ||||
|     <package android:name="com.dropbox.android" /> | ||||
|  | ||||
|     <intent> | ||||
|       <action android:name="android.intent.action.OPEN_DOCUMENT" /> | ||||
|       <data android:mimeType="*/*" /> | ||||
|     </intent> | ||||
|  | ||||
|     <intent> | ||||
|         <action android:name="android.intent.action.OPEN_DOCUMENT" /> | ||||
|         <data android:mimeType="*/*" /> | ||||
|       </intent> | ||||
| 	   | ||||
| 	  <intent> | ||||
|       <action android:name="android.intent.action.GET_DOCUMENT" /> | ||||
|       <data android:mimeType="*/*" /> | ||||
|     </intent> | ||||
| @@ -36,59 +39,57 @@ | ||||
|     </intent> | ||||
|  | ||||
|     <intent> | ||||
|         <action android:name="android.intent.action.VIEW" /> | ||||
| 	  </intent> | ||||
|   </queries>			 | ||||
| 			 | ||||
| 	<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" /> | ||||
|       <action android:name="android.intent.action.VIEW" /> | ||||
|     </intent> | ||||
|   </queries> | ||||
|   <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="35" /> | ||||
|  | ||||
|   <permission android:description="@string/permission_desc2" android:icon="@drawable/ic_launcher_offline" android:label="KP2A entry search" android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalSearch" android:protectionLevel="signature" /> | ||||
|   <permission android:description="@string/permission_desc3" android:icon="@drawable/ic_launcher_offline" android:label="KP2A choose autofill dataset" android:name="keepass2android.keepass2android_nonet.permission.Kp2aChooseAutofill" android:protectionLevel="signature" /> | ||||
| 	<application  | ||||
| 		android:label="keepass2android"  | ||||
| 		android:icon="@mipmap/ic_launcher_offline" | ||||
| 		android:networkSecurityConfig="@xml/network_security_config" | ||||
| 	> | ||||
|   <application android:label="keepass2android" | ||||
|     android:icon="@mipmap/ic_launcher_offline" | ||||
|     android:roundIcon="@mipmap/ic_launcher_offline_round" | ||||
|     android:networkSecurityConfig="@xml/network_security_config" | ||||
|   > | ||||
|  | ||||
|     <meta-data | ||||
|       android:name="com.google.mlkit.vision.DEPENDENCIES" | ||||
|       android:value="barcode_ui"/> | ||||
|  | ||||
| 		<uses-library | ||||
|     <uses-library | ||||
|             android:name="org.apache.http.legacy" | ||||
|             android:required="false"/> | ||||
| 	 | ||||
| 		<provider android:name="group.pals.android.lib.ui.filechooser.providers.localfile.LocalFileProvider" android:authorities="keepass2android.keepass2android_nonet.android-filechooser.localfile" android:exported="false" /> | ||||
| 		<provider android:name="group.pals.android.lib.ui.filechooser.providers.history.HistoryProvider" android:authorities="keepass2android.keepass2android_nonet.android-filechooser.history" android:exported="false" /> | ||||
| 		<activity android:name="group.pals.android.lib.ui.filechooser.FileChooserActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:screenOrientation="user" android:theme="@style/Afc.Theme.Light"> | ||||
| 		</activity> | ||||
|    | ||||
|     <provider android:name="group.pals.android.lib.ui.filechooser.providers.localfile.LocalFileProvider" android:authorities="keepass2android.keepass2android_nonet.android-filechooser.localfile" android:exported="false" /> | ||||
|     <provider android:name="group.pals.android.lib.ui.filechooser.providers.history.HistoryProvider" android:authorities="keepass2android.keepass2android_nonet.android-filechooser.history" android:exported="false" /> | ||||
|     <activity android:name="group.pals.android.lib.ui.filechooser.FileChooserActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:screenOrientation="user" android:theme="@style/Afc.Theme.Light"> | ||||
|     </activity> | ||||
|  | ||||
| 		<service android:name="keepass2android.softkeyboard.KP2AKeyboard" android:permission="android.permission.BIND_INPUT_METHOD" android:exported="true"> | ||||
| 			<intent-filter> | ||||
| 				<action android:name="android.view.InputMethod" /> | ||||
| 			</intent-filter> | ||||
| 			<meta-data android:name="android.view.im" android:resource="@xml/method" /> | ||||
| 		</service> | ||||
| 		<activity android:name="keepass2android.softkeyboard.LatinIMESettings" android:label="@string/english_ime_settings" android:exported="true"> | ||||
| 			<intent-filter> | ||||
| 				<action android:name="android.intent.action.MAIN" /> | ||||
| 			</intent-filter> | ||||
| 			<intent-filter> | ||||
| 				<action android:name="keepass2android.softkeyboard.LatinIMESettings" /> | ||||
| 				<category android:name="android.intent.category.DEFAULT" /> | ||||
| 			</intent-filter> | ||||
| 		</activity> | ||||
| 		 <activity android:name="keepass2android.softkeyboard.InputLanguageSelection" | ||||
|                 android:label="@string/language_selection_title" | ||||
|                 android:exported="true"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.MAIN"/> | ||||
|                 <action android:name="keepass2android.softkeyboard.INPUT_LANGUAGE_SELECTION"/> | ||||
|                 <category android:name="android.intent.category.DEFAULT" /> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
|   | ||||
| 		<activity android:configChanges="orientation|keyboard|keyboardHidden" android:label="@string/app_name" android:theme="@style/Kp2aTheme_BlueNoActionBar" android:name="keepass2android.SelectCurrentDbActivity" android:windowSoftInputMode="adjustResize" | ||||
|               android:exported="true"> | ||||
|     <service android:name="keepass2android.softkeyboard.KP2AKeyboard" android:permission="android.permission.BIND_INPUT_METHOD" android:exported="true"> | ||||
|       <intent-filter> | ||||
|         <action android:name="android.view.InputMethod" /> | ||||
|       </intent-filter> | ||||
|       <meta-data android:name="android.view.im" android:resource="@xml/method" /> | ||||
|     </service> | ||||
|     <activity android:name="keepass2android.softkeyboard.LatinIMESettings" android:label="@string/english_ime_settings" android:exported="true"> | ||||
|       <intent-filter> | ||||
|         <action android:name="android.intent.action.MAIN" /> | ||||
|       </intent-filter> | ||||
|       <intent-filter> | ||||
|         <action android:name="keepass2android.softkeyboard.LatinIMESettings" /> | ||||
|         <category android:name="android.intent.category.DEFAULT" /> | ||||
|       </intent-filter> | ||||
|     </activity> | ||||
|     <activity android:name="keepass2android.softkeyboard.InputLanguageSelection" | ||||
|                android:exported="true"> | ||||
|       <intent-filter> | ||||
|         <action android:name="android.intent.action.MAIN"/> | ||||
|         <action android:name="keepass2android.softkeyboard.INPUT_LANGUAGE_SELECTION"/> | ||||
|         <category android:name="android.intent.category.DEFAULT" /> | ||||
|       </intent-filter> | ||||
|     </activity> | ||||
|     <activity android:configChanges="orientation|keyboard|keyboardHidden" android:label="@string/app_name" android:theme="@style/Kp2aTheme_BlueNoActionBar" android:name="keepass2android.SelectCurrentDbActivity" android:windowSoftInputMode="adjustResize" | ||||
|     android:exported="true"> | ||||
|       <intent-filter android:label="@string/app_name"> | ||||
|         <action android:name="android.intent.action.VIEW" /> | ||||
|         <category android:name="android.intent.category.DEFAULT" /> | ||||
| @@ -98,11 +99,11 @@ | ||||
|         <data android:host="*" /> | ||||
|       </intent-filter> | ||||
|  | ||||
| 	  <intent-filter> | ||||
|       <intent-filter> | ||||
|         <action android:name="kp2a.action.SelectCurrentDbActivity" /> | ||||
|         <category android:name="android.intent.category.DEFAULT" /> | ||||
|       </intent-filter> | ||||
| 	   | ||||
|  | ||||
|       <intent-filter android:label="@string/app_name"> | ||||
|         <action android:name="android.intent.action.SEND" /> | ||||
|         <action android:name="android.intent.action.SEND_MULTIPLE" /> | ||||
| @@ -112,7 +113,7 @@ | ||||
|         <data android:mimeType="application/*" /> | ||||
|       </intent-filter> | ||||
|  | ||||
| <!-- intent filter for opening database files  | ||||
|       <!-- intent filter for opening database files  | ||||
| Note that this stopped working nicely with Android 7, see e.g. https://stackoverflow.com/a/26635162/292233 | ||||
| KP2A was using  | ||||
| <data android:scheme="content" /> | ||||
| @@ -126,7 +127,7 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik | ||||
| --> | ||||
|  | ||||
|  | ||||
| <!-- This intent filter is for apps which use content with a URI containing the extension but no specific mimeType, e.g. ASTRO file manager --> | ||||
|       <!-- This intent filter is for apps which use content with a URI containing the extension but no specific mimeType, e.g. ASTRO file manager --> | ||||
|  | ||||
|       <intent-filter android:label="@string/app_name"> | ||||
|         <action android:name="android.intent.action.VIEW" /> | ||||
| @@ -168,7 +169,7 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik | ||||
|       </intent-filter> | ||||
|  | ||||
|       <!-- This intent filter is for apps which use content with a URI not containing the extension but at least specify mimeType=application/octet-stream, e.g. GoogleDrive or FolderSync --> | ||||
| <intent-filter android:label="@string/app_name"> | ||||
|       <intent-filter android:label="@string/app_name"> | ||||
|         <action android:name="android.intent.action.VIEW" /> | ||||
|         <category android:name="android.intent.category.DEFAULT" /> | ||||
|         <category android:name="android.intent.category.BROWSABLE" /> | ||||
| @@ -218,24 +219,15 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik | ||||
|         <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdb" /> | ||||
|       </intent-filter> | ||||
|  | ||||
|       <intent-filter> | ||||
|         <action android:name="android.intent.action.VIEW"/> | ||||
|         <category android:name="android.intent.category.BROWSABLE"/> | ||||
|         <data | ||||
|           android:scheme="https" | ||||
|           android:host="my.yubico.com" | ||||
|           android:pathPrefix="/neo"/> | ||||
|       <intent-filter android:label="@string/kp2a_findUrl"> | ||||
|         <action android:name="android.intent.action.SEND" /> | ||||
|         <category android:name="android.intent.category.DEFAULT" /> | ||||
|         <data android:mimeType="text/plain" /> | ||||
|       </intent-filter> | ||||
|       <intent-filter> | ||||
|         <action android:name="keepass2android.ACTION_START_WITH_TASK" /> | ||||
|         <category android:name="android.intent.category.DEFAULT" /> | ||||
|       </intent-filter> | ||||
| 	   | ||||
| 			<intent-filter android:label="@string/kp2a_findUrl"> | ||||
| 				<action android:name="android.intent.action.SEND" /> | ||||
| 				<category android:name="android.intent.category.DEFAULT" /> | ||||
| 				<data android:mimeType="text/plain" /> | ||||
| 			</intent-filter> | ||||
| 			<intent-filter> | ||||
| 				<action android:name="keepass2android.ACTION_START_WITH_TASK" /> | ||||
| 				<category android:name="android.intent.category.DEFAULT" /> | ||||
| 			</intent-filter> | ||||
|       <intent-filter> | ||||
|         <action android:name="android.intent.action.VIEW" /> | ||||
|         <category android:name="android.intent.category.DEFAULT" /> | ||||
| @@ -244,24 +236,28 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik | ||||
|         <data android:host="totp"/> | ||||
|         <data android:host="hotp"/> | ||||
|       </intent-filter> | ||||
| 		</activity> | ||||
| 		<uses-library android:required="false" android:name="com.sec.android.app.multiwindow" /> | ||||
| 		<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" /> | ||||
| 		<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W" android:value="632.0dip" /> | ||||
| 		<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H" android:value="598.0dip" /> | ||||
| 		<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W" android:value="426.0dip" /> | ||||
| 		<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H" android:value="360.0dip" /> | ||||
| 	</application> | ||||
| 	<uses-permission android:name="android.permission.VIBRATE" /> | ||||
| 	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||||
| 	<uses-permission android:name="android.permission.USE_FINGERPRINT" /> | ||||
|   <uses-permission android:name="android.permission.USE_BIOMETRIC" /> | ||||
|   <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> | ||||
|    | ||||
|   <uses-feature android:name="android.hardware.camera" android:required="false" /> | ||||
|  | ||||
|     </activity> | ||||
|     <uses-library android:required="false" android:name="com.sec.android.app.multiwindow" /> | ||||
|     <meta-data android:name="com.sec.android.support.multiwindow" android:value="true" /> | ||||
|     <meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W" android:value="632.0dip" /> | ||||
|     <meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H" android:value="598.0dip" /> | ||||
|     <meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W" android:value="426.0dip" /> | ||||
|     <meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H" android:value="360.0dip" /> | ||||
|   </application> | ||||
|   <uses-permission android:name="android.permission.VIBRATE" /> | ||||
|   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||||
|   <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> | ||||
|   <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> | ||||
|   <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> | ||||
|   <uses-permission android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalFileBrowsing" /> | ||||
|   <uses-permission android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalSearch" /> | ||||
|   <uses-permission android:name="android.permission.USE_FINGERPRINT" /> | ||||
|   <uses-permission android:name="android.permission.USE_BIOMETRIC" /> | ||||
|   <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> | ||||
|  | ||||
|   <uses-feature android:name="android.hardware.camera" android:required="false" /> | ||||
|  | ||||
|   <!-- Samsung Pass permission --> | ||||
|   <uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" /> | ||||
|   <uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" /> | ||||
| 	 | ||||
| </manifest> | ||||
|   | ||||
| @@ -145,7 +145,7 @@ namespace keepass2android | ||||
| 			catch (Exception e) | ||||
| 			{ | ||||
| 				Kp2aLog.LogUnexpectedError(e); | ||||
| 				Toast.MakeText(this, "No Yubikey OTP found!", ToastLength.Long).Show(); | ||||
| 				App.Kp2a.ShowMessage(this, "No Yubikey OTP found!",  MessageSeverity.Error); | ||||
| 				Finish(); | ||||
| 				return; | ||||
| 			} | ||||
|   | ||||
| @@ -15,6 +15,36 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file | ||||
|   along with Keepass2Android.  If not, see <http://www.gnu.org/licenses/>. | ||||
|   */ | ||||
|  | ||||
| using Android.App; | ||||
| using Android.Content; | ||||
| using Android.Content.PM; | ||||
| using Android.Database; | ||||
| using Android.Graphics; | ||||
| using Android.Graphics.Drawables; | ||||
| using Android.OS; | ||||
| using Android.Preferences; | ||||
| using Android.Runtime; | ||||
| using Android.Text; | ||||
| using Android.Views; | ||||
| using Android.Views.InputMethods; | ||||
| using Android.Widget; | ||||
| using AndroidX.AppCompat.App; | ||||
| using AndroidX.CoordinatorLayout.Widget; | ||||
| using AndroidX.Core.Content; | ||||
| using AndroidX.Core.View; | ||||
| using AndroidX.DrawerLayout.Widget; | ||||
| using Google.Android.Material.AppBar; | ||||
| using Google.Android.Material.Dialog; | ||||
| using Java.Lang; | ||||
| using Java.Net; | ||||
| using KeeChallenge; | ||||
| using keepass2android; | ||||
| using keepass2android.Io; | ||||
| using keepass2android.Utils; | ||||
| using Keepass2android.Pluginsdk; | ||||
| using KeePassLib.Keys; | ||||
| using KeePassLib.Serialization; | ||||
| using OtpKeyProv; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| @@ -23,49 +53,17 @@ using System.Net; | ||||
| using System.Threading.Tasks; | ||||
| using System.Xml; | ||||
| using System.Xml.Serialization; | ||||
| using keepass2android; | ||||
| using Android.App; | ||||
| using Android.Content; | ||||
| using Android.Database; | ||||
| using Android.Graphics.Drawables; | ||||
| using Android.OS; | ||||
| using Android.Runtime; | ||||
| using Android.Views; | ||||
| using Android.Views.InputMethods; | ||||
| using Android.Widget; | ||||
| using Java.Net; | ||||
| using Android.Preferences; | ||||
| using Android.Text; | ||||
| using Android.Content.PM; | ||||
| using Android.Graphics; | ||||
| using AndroidX.AppCompat.App; | ||||
| using AndroidX.CoordinatorLayout.Widget; | ||||
| using AndroidX.Core.View; | ||||
| using AndroidX.DrawerLayout.Widget; | ||||
| using Google.Android.Material.AppBar; | ||||
| using Google.Android.Material.Dialog; | ||||
| using Java.Lang; | ||||
| using KeePassLib.Keys; | ||||
| using KeePassLib.Serialization; | ||||
| using Keepass2android.Pluginsdk; | ||||
| using OtpKeyProv; | ||||
| using keepass2android.Io; | ||||
| using keepass2android.Utils; | ||||
|  | ||||
| using File = Java.IO.File; | ||||
| using FileNotFoundException = Java.IO.FileNotFoundException; | ||||
|  | ||||
| using Object = Java.Lang.Object; | ||||
| using Process = Android.OS.Process; | ||||
|  | ||||
| using KeeChallenge; | ||||
| using static Android.Locations.GpsStatus; | ||||
| using AlertDialog = Android.App.AlertDialog; | ||||
| using ClipboardManager = Android.Content.ClipboardManager; | ||||
| using Enum = System.Enum; | ||||
| using Exception = System.Exception; | ||||
| using File = Java.IO.File; | ||||
| using FileNotFoundException = Java.IO.FileNotFoundException; | ||||
| using Object = Java.Lang.Object; | ||||
| using Process = Android.OS.Process; | ||||
| using String = System.String; | ||||
| using Toolbar = AndroidX.AppCompat.Widget.Toolbar; | ||||
| using AndroidX.Core.Content; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| @@ -131,7 +129,8 @@ namespace keepass2android | ||||
| 		ISharedPreferences _prefs; | ||||
|  | ||||
| 		private bool _starting; | ||||
| 		private OtpInfo _otpInfo; | ||||
|         private bool _resumeCompleted; | ||||
|         private OtpInfo _otpInfo; | ||||
| 		private IOConnectionInfo _otpAuxIoc; | ||||
|         private ChallengeInfo _chalInfo; | ||||
|         private byte[] _challengeSecret; | ||||
| @@ -309,7 +308,7 @@ namespace keepass2android | ||||
| 		            catch (Exception e) | ||||
| 		            { | ||||
| 		                Kp2aLog.Log(e.ToString()); | ||||
| 		                Toast.MakeText(this, "Error: " + e.Message, ToastLength.Long).Show(); | ||||
| 		                App.Kp2a.ShowMessage(this, "Error: " + Util.GetErrorMessage(e),  MessageSeverity.Error); | ||||
| 		                return; | ||||
| 		            } | ||||
|  | ||||
| @@ -328,8 +327,7 @@ namespace keepass2android | ||||
| 		                            ChallengeInfo temp = _challengeProv.Encrypt(_challengeSecret); | ||||
| 		                            if (!temp.Save(_otpAuxIoc)) | ||||
| 		                            { | ||||
| 		                                Toast.MakeText(this, Resource.String.ErrorUpdatingChalAuxFile, ToastLength.Long) | ||||
| 		                                    .Show(); | ||||
| 		                                App.Kp2a.ShowMessage(this, Resource.String.ErrorUpdatingChalAuxFile, MessageSeverity.Error); | ||||
| 		                                return false; | ||||
| 		                            } | ||||
|  | ||||
| @@ -348,7 +346,7 @@ namespace keepass2android | ||||
| 		            } | ||||
| 		            else | ||||
| 		            { | ||||
| 		                Toast.MakeText(this, Resource.String.bad_resp, ToastLength.Long).Show(); | ||||
| 		                App.Kp2a.ShowMessage(this, Resource.String.bad_resp,  MessageSeverity.Error); | ||||
| 		            } | ||||
| 		        } | ||||
| 		    } | ||||
| @@ -420,8 +418,14 @@ namespace keepass2android | ||||
| 							try | ||||
| 							{ | ||||
| 								var iocAux = GetDefaultAuxLocation(); | ||||
| 								LoadFile(iocAux); | ||||
| 							} | ||||
|                                 LoadFile(iocAux); | ||||
|  | ||||
|                                 if (Activity._chalInfo == null) | ||||
|                                 { | ||||
|                                     throw new Java.Lang.Exception("Failed to load challenge aux file"); | ||||
|                                 } | ||||
|  | ||||
|                             } | ||||
| 							catch (Exception e) | ||||
| 							{ | ||||
| 								//this can happen e.g. if the file storage does not support GetParentPath | ||||
| @@ -458,7 +462,7 @@ namespace keepass2android | ||||
| 									} | ||||
| 									else | ||||
| 									{ | ||||
| 										Toast.MakeText(Activity,GetErrorMessage(), ToastLength.Long).Show(); | ||||
| 										App.Kp2a.ShowMessage(Activity,GetErrorMessage(),  MessageSeverity.Error); | ||||
| 									} | ||||
| 									return; | ||||
|  | ||||
| @@ -645,7 +649,7 @@ namespace keepass2android | ||||
| 			_activityDesign.ApplyTheme(); | ||||
| 			base.OnCreate(savedInstanceState); | ||||
|  | ||||
| 		    _intentReceiver = new PasswordActivityBroadcastReceiver(this); | ||||
|             _intentReceiver = new PasswordActivityBroadcastReceiver(this); | ||||
| 			IntentFilter filter = new IntentFilter(); | ||||
| 			filter.AddAction(Intent.ActionScreenOff); | ||||
|             ContextCompat.RegisterReceiver(this, _intentReceiver, filter, (int)ReceiverFlags.Exported); | ||||
| @@ -801,8 +805,6 @@ namespace keepass2android | ||||
|  | ||||
|             _password = i.GetStringExtra(KeyPassword) ?? ""; | ||||
|             if (!KeyProviderTypes.Any()) | ||||
|  | ||||
|  | ||||
|             { | ||||
|                 SetKeyProviderFromString(LoadKeyProviderStringForIoc(_ioConnection.Path)); | ||||
|             } | ||||
| @@ -957,7 +959,7 @@ namespace keepass2android | ||||
| 			{ | ||||
| 				btn.SetImageResource(Resource.Drawable.baseline_fingerprint_24); | ||||
|             }, 1300); | ||||
| 			Toast.MakeText(this, message, ToastLength.Long).Show(); | ||||
| 			App.Kp2a.ShowMessage(this, message,  MessageSeverity.Error); | ||||
| 		} | ||||
|  | ||||
|         public void OnBiometricAttemptFailed(string message) | ||||
| @@ -996,8 +998,12 @@ namespace keepass2android | ||||
| 			 | ||||
| 			btn.PostDelayed(() => | ||||
| 			{ | ||||
|                 //fire | ||||
|                 OnOk(true); | ||||
|                 //fire if everything else is ready | ||||
|                 if (FindViewById(Resource.Id.pass_ok).Enabled) | ||||
|                 { | ||||
|                     OnOk(true); | ||||
|                 } | ||||
|                  | ||||
| 			    FindViewById<EditText>(Resource.Id.password_edit).Enabled = true; | ||||
| 			}, 500); | ||||
|  | ||||
| @@ -1036,7 +1042,7 @@ namespace keepass2android | ||||
| 				if (_appnameclickCount == 6) | ||||
| 				{ | ||||
| 					Kp2aLog.LogUnexpectedError(new Exception("some blabla")); | ||||
| 					Toast.MakeText(this, "Once again and the app will crash.", ToastLength.Long).Show(); | ||||
| 					App.Kp2a.ShowMessage(this, "Once again and the app will crash.",  MessageSeverity.Warning); | ||||
| 				} | ||||
| 					 | ||||
| 				if (_appnameclickCount == 7) | ||||
| @@ -1123,7 +1129,7 @@ namespace keepass2android | ||||
| 				//For security reasons: discard the OTP (otherwise the user might not select a database now and forget  | ||||
| 				//about the OTP, but it would still be stored in the Intents and later be passed to PasswordActivity again. | ||||
|  | ||||
| 				Toast.MakeText(this, GetString(Resource.String.otp_discarded_because_no_db), ToastLength.Long).Show(); | ||||
| 				App.Kp2a.ShowMessage(this, GetString(Resource.String.otp_discarded_because_no_db),  MessageSeverity.Warning); | ||||
| 				GoToFileSelectActivity(); | ||||
| 				return false; | ||||
| 			} | ||||
| @@ -1157,7 +1163,9 @@ namespace keepass2android | ||||
| 		    changeDbButton.Click += (sender, args) => GoToFileSelectActivity(); | ||||
|  | ||||
| 			Util.MoveBottomBarButtons(Resource.Id.change_db, Resource.Id.pass_ok, Resource.Id.bottom_bar, this); | ||||
| 		} | ||||
|             Util.InsetListener.ForBottomElement(FindViewById(Resource.Id.bottom_bar)).Apply(); | ||||
|             Util.InsetListener.ForTopElement(FindViewById(Resource.Id.appbar)).Apply(); | ||||
|         } | ||||
|  | ||||
| 		private void OnOk(bool usedFingerprintUnlock = false) | ||||
| 		{ | ||||
| @@ -1251,7 +1259,7 @@ namespace keepass2android | ||||
| 							case 6: | ||||
| 							    KeyProviderTypes.Add(KeyProviders.ChallengeXC); | ||||
| 							    break; | ||||
| 						        case 7: | ||||
|                             case 7: | ||||
| 						            //don't set to "" to prevent losing the filename. (ItemSelected is also called during recreation!) | ||||
| 							    Kp2aLog.Log("key file length before: " + _keyFile?.Length); | ||||
| 						            _keyFile = (FindViewById(Resource.Id.label_keyfilename).Tag ?? "").ToString(); | ||||
| @@ -1400,7 +1408,7 @@ namespace keepass2android | ||||
| 					                        string errorMessage; | ||||
| 					                        if (!CreateCompositeKey(out compositeKey, out errorMessage)) return (() => | ||||
| 						                        { | ||||
| 							                        Toast.MakeText(this, errorMessage, ToastLength.Long).Show(); | ||||
| 							                        App.Kp2a.ShowMessage(this, errorMessage,  MessageSeverity.Warning); | ||||
| 						                            _performingLoad = false; | ||||
|                                                 }); | ||||
| 											return () => { PerformLoadDatabaseWithCompositeKey(compositeKey); }; | ||||
| @@ -1415,18 +1423,22 @@ namespace keepass2android | ||||
| 			if (cbQuickUnlock == null) | ||||
| 				throw new NullPointerException("cpQuickUnlock"); | ||||
| 			App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked); | ||||
|             App.Kp2a.ScreenLockWasEnabledWhenOpeningDatabase = | ||||
|                 (((KeyguardManager)GetSystemService(Context.KeyguardService)!)!).IsDeviceSecure; | ||||
| 			App.Kp2a.QuickUnlockBlockedWhenDeviceNotSecureWhenOpeningDatabase = PreferenceManager.GetDefaultSharedPreferences(this) | ||||
| 		        .GetBoolean(GetString(Resource.String.QuickUnlockBlockedWhenDeviceNotSecure_key), true); | ||||
|  | ||||
|             if ((_loadDbFileTask != null) &&  (App.Kp2a.OfflineMode != _loadDbTaskOffline)) | ||||
|             if ((_loadDbFileTask != null) && (App.Kp2a.OfflineMode != _loadDbTaskOffline)) | ||||
| 			{ | ||||
|                 if (App.Kp2a == null) | ||||
| 				if (App.Kp2a == null) | ||||
| 					throw new NullPointerException("App.Kp2a"); | ||||
| 				//keep the loading result if we loaded in online-mode (now offline) and the task is completed | ||||
| 				if (!App.Kp2a.OfflineMode || !_loadDbFileTask.IsCompleted) | ||||
| 				{ | ||||
| 					//discard the pre-loading task | ||||
| 					_loadDbFileTask = null;	 | ||||
| 					_loadDbFileTask = null; | ||||
| 				} | ||||
| 				 | ||||
|  | ||||
| 			} | ||||
|  | ||||
| 			//avoid password being visible while loading: | ||||
| @@ -1481,7 +1493,7 @@ namespace keepass2android | ||||
| 				catch (Exception e) | ||||
| 				{ | ||||
| 					Kp2aLog.LogUnexpectedError(e); | ||||
| 					errorMessage = e.Message; | ||||
| 					errorMessage = Util.GetErrorMessage(e); | ||||
| 					return false; | ||||
| 				} | ||||
| 			} | ||||
| @@ -1566,15 +1578,21 @@ namespace keepass2android | ||||
|  | ||||
|             base.OnPause(); | ||||
| 		} | ||||
| 		protected override void OnStart() | ||||
|  | ||||
|         private bool hasRequestedKeyboardActivation = false; | ||||
|          | ||||
|  | ||||
|         protected override void OnStart() | ||||
| 		{ | ||||
| 			base.OnStart(); | ||||
| 			_starting = true; | ||||
|  | ||||
| 		    if (PreferenceManager.GetDefaultSharedPreferences(this) | ||||
| 		        .GetBoolean(GetString(Resource.String.UseKp2aKeyboardInKp2a_key), false)) | ||||
| 		    { | ||||
| 		        CopyToClipboardService.ActivateKeyboard(this); | ||||
| 		        .GetBoolean(GetString(Resource.String.UseKp2aKeyboardInKp2a_key), false) | ||||
|                 && !hasRequestedKeyboardActivation) | ||||
|             { | ||||
|                 hasRequestedKeyboardActivation = true; | ||||
|                 CopyToClipboardService.ActivateKeyboard(this); | ||||
| 		    } | ||||
|  | ||||
|             DonateReminder.ShowDonateReminderIfAppropriate(this); | ||||
| @@ -1639,60 +1657,65 @@ namespace keepass2android | ||||
|             if (intent != null) | ||||
|             { | ||||
| 				if (intent.HasExtra(Intents.OtpExtraKey)) | ||||
|                 { | ||||
|                     string otp = intent.GetStringExtra(Intents.OtpExtraKey); | ||||
|                     _keepPasswordInOnResume = true; | ||||
|                     if (KeyProviderTypes.Contains(KeyProviders.Otp)) | ||||
| 				{ | ||||
| 					string otp = intent.GetStringExtra(Intents.OtpExtraKey); | ||||
| 					_keepPasswordInOnResume = true; | ||||
| 					if (KeyProviderTypes.Contains(KeyProviders.Otp)) | ||||
| 					{ | ||||
|  | ||||
| 						if (_otpInfo == null) | ||||
| 						{ | ||||
| 							//Entering OTPs not yet initialized: | ||||
| 							_pendingOtps.Add(otp); | ||||
| 							UpdateKeyProviderUiState(); | ||||
| 						} | ||||
| 						else | ||||
| 						{ | ||||
| 							//Entering OTPs is initialized. Write OTP into first empty field: | ||||
| 							bool foundEmptyField = false; | ||||
| 							foreach (int otpId in _otpTextViewIds) | ||||
| 							{ | ||||
| 								EditText otpEdit = FindViewById<EditText>(otpId); | ||||
| 								if ((otpEdit.Visibility == ViewStates.Visible) && String.IsNullOrEmpty(otpEdit.Text)) | ||||
| 								{ | ||||
| 									otpEdit.Text = otp; | ||||
| 									foundEmptyField = true; | ||||
| 									break; | ||||
| 								} | ||||
| 							} | ||||
| 							//did we find a field? | ||||
| 							if (!foundEmptyField) | ||||
| 							{ | ||||
| 								App.Kp2a.ShowMessage(this, GetString(Resource.String.otp_discarded_no_space), MessageSeverity.Error); | ||||
| 							} | ||||
| 						} | ||||
|  | ||||
| 						Spinner passwordModeSpinner = FindViewById<Spinner>(Resource.Id.password_mode_spinner); | ||||
| 						if (passwordModeSpinner.SelectedItemPosition != (int)KeyProviders.Otp) | ||||
| 						{ | ||||
| 							passwordModeSpinner.SetSelection((int)KeyProviders.Otp); | ||||
| 						} | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 						//assume the key should be used as static password | ||||
| 						FindViewById<EditText>(Resource.Id.password_edit).Text += otp; | ||||
| 					} | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
|                     // if the activity is launched twice and the first initialization hasn't even finished, we cannot  | ||||
|                     // reset the state and re-initialize the activity. | ||||
| 					// This can happen with autofill in some cases (#2869) | ||||
|                     if (_resumeCompleted) | ||||
|                     { | ||||
|  | ||||
|                         if (_otpInfo == null) | ||||
|                         { | ||||
|                             //Entering OTPs not yet initialized: | ||||
|                             _pendingOtps.Add(otp); | ||||
|                             UpdateKeyProviderUiState(); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             //Entering OTPs is initialized. Write OTP into first empty field: | ||||
|                             bool foundEmptyField = false; | ||||
|                             foreach (int otpId in _otpTextViewIds) | ||||
|                             { | ||||
|                                 EditText otpEdit = FindViewById<EditText>(otpId); | ||||
|                                 if ((otpEdit.Visibility == ViewStates.Visible) && String.IsNullOrEmpty(otpEdit.Text)) | ||||
|                                 { | ||||
|                                     otpEdit.Text = otp; | ||||
|                                     foundEmptyField = true; | ||||
|                                     break; | ||||
|                                 } | ||||
|                             } | ||||
|                             //did we find a field? | ||||
|                             if (!foundEmptyField) | ||||
|                             { | ||||
|                                 Toast.MakeText(this, GetString(Resource.String.otp_discarded_no_space), ToastLength.Long).Show(); | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                         Spinner passwordModeSpinner = FindViewById<Spinner>(Resource.Id.password_mode_spinner); | ||||
|                         if (passwordModeSpinner.SelectedItemPosition != (int)KeyProviders.Otp) | ||||
|                         { | ||||
|                             passwordModeSpinner.SetSelection((int)KeyProviders.Otp); | ||||
|                         } | ||||
|                         ResetState(); | ||||
|                         GetIocFromLaunchIntent(intent); | ||||
|                         InitializeAfterSetIoc(); | ||||
|                         OnStart(); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         //assume the key should be used as static password | ||||
|                         FindViewById<EditText>(Resource.Id.password_edit).Text += otp; | ||||
|                     } | ||||
|  | ||||
|  | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     ResetState(); | ||||
|                     GetIocFromLaunchIntent(intent); | ||||
| 					InitializeAfterSetIoc(); | ||||
|                     OnStart(); | ||||
|                 } | ||||
| 					 | ||||
| 				} | ||||
| 			} | ||||
| 	 | ||||
| 		} | ||||
| @@ -1729,19 +1752,26 @@ namespace keepass2android | ||||
|             UsedFingerprintUnlock = false; | ||||
|         } | ||||
|  | ||||
|         protected override void OnResume() | ||||
|         protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content); | ||||
|  | ||||
| 		protected override void OnResume() | ||||
| 		{ | ||||
| 			base.OnResume(); | ||||
|  | ||||
| 			_activityDesign.ReapplyTheme(); | ||||
|  | ||||
| 			Kp2aLog.Log("starting: " + _starting + ", Finishing: " + IsFinishing + ", _performingLoad: " + _performingLoad); | ||||
| 			Kp2aLog.Log("starting: " + _starting + ", Finishing: " + IsFinishing + ", _performingLoad: " + | ||||
| 						_performingLoad); | ||||
|  | ||||
| 			CheckBox cbOfflineMode = (CheckBox)FindViewById(Resource.Id.work_offline); | ||||
| 			App.Kp2a.OfflineMode = cbOfflineMode.Checked = App.Kp2a.OfflineModePreference; //this won't overwrite new user settings because every change is directly saved in settings | ||||
| 			App.Kp2a.OfflineMode = | ||||
| 				cbOfflineMode.Checked = | ||||
| 					App.Kp2a | ||||
| 						.OfflineModePreference; //this won't overwrite new user settings because every change is directly saved in settings | ||||
| 			LinearLayout offlineModeContainer = FindViewById<LinearLayout>(Resource.Id.work_offline_container); | ||||
| 			var cachingFileStorage = App.Kp2a.GetFileStorage(_ioConnection) as CachingFileStorage; | ||||
| 			if ((cachingFileStorage != null) && cachingFileStorage.IsCached(_ioConnection)) | ||||
| 			{	 | ||||
| 			{ | ||||
| 				offlineModeContainer.Visibility = ViewStates.Visible; | ||||
| 			} | ||||
| 			else | ||||
| @@ -1749,18 +1779,18 @@ namespace keepass2android | ||||
| 				offlineModeContainer.Visibility = ViewStates.Gone; | ||||
| 				App.Kp2a.OfflineMode = false; | ||||
| 			} | ||||
| 			 | ||||
|  | ||||
| 			 | ||||
|  | ||||
|  | ||||
|  | ||||
| 			View killButton = FindViewById(Resource.Id.kill_app); | ||||
| 			if (PreferenceManager.GetDefaultSharedPreferences(this) | ||||
| 								 .GetBoolean(GetString(Resource.String.show_kill_app_key), false)) | ||||
| 				.GetBoolean(GetString(Resource.String.show_kill_app_key), false)) | ||||
| 			{ | ||||
| 				killButton.Click += (sender, args) => | ||||
| 				{ | ||||
| 					_killOnDestroy = true; | ||||
|                     SetResult(Result.Canceled); | ||||
| 					SetResult(Result.Canceled); | ||||
| 					Finish(); | ||||
|  | ||||
| 				}; | ||||
| @@ -1772,20 +1802,23 @@ namespace keepass2android | ||||
| 				killButton.Visibility = ViewStates.Gone; | ||||
| 			} | ||||
|  | ||||
| 		    TryGetOtpFromClipboard(); | ||||
| 			TryGetOtpFromClipboard(); | ||||
|  | ||||
| 		    if (!_keepPasswordInOnResume) | ||||
| 		    { | ||||
| 		        if ( | ||||
|                     _lastOnPauseTime < DateTime.Now - TimeSpan.FromSeconds(5) //only clear when user left the app for more than 5 seconds (allows to use Yubiclip, also allows to switch shortly to another app) | ||||
|                     &&  | ||||
| 		            PreferenceManager.GetDefaultSharedPreferences(this) | ||||
| 		                .GetBoolean(GetString(Resource.String.ClearPasswordOnLeave_key), true)) | ||||
| 		        { | ||||
| 		            ClearEnteredPassword(); | ||||
| 		        } | ||||
| 			if (!_keepPasswordInOnResume) | ||||
| 			{ | ||||
| 				if ( | ||||
| 					_lastOnPauseTime < | ||||
| 					DateTime.Now - | ||||
| 					TimeSpan.FromSeconds( | ||||
| 						5) //only clear when user left the app for more than 5 seconds (allows to use Yubiclip, also allows to switch shortly to another app) | ||||
| 					&& | ||||
| 					PreferenceManager.GetDefaultSharedPreferences(this) | ||||
| 						.GetBoolean(GetString(Resource.String.ClearPasswordOnLeave_key), true)) | ||||
| 				{ | ||||
| 					ClearEnteredPassword(); | ||||
| 				} | ||||
|  | ||||
| 		    } | ||||
| 			} | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -1797,71 +1830,74 @@ namespace keepass2android | ||||
|  | ||||
| 			if (KeyProviderTypes.Contains(KeyProviders.Challenge)) | ||||
| 			{ | ||||
| 				FindViewById(Resource.Id.otpInitView).Visibility = _challengeSecret == null ? ViewStates.Visible : ViewStates.Gone; | ||||
| 				FindViewById(Resource.Id.otpInitView).Visibility = | ||||
| 					_challengeSecret == null ? ViewStates.Visible : ViewStates.Gone; | ||||
| 			} | ||||
|  | ||||
| 			//use !IsFinishing to make sure we're not starting another activity when we're already finishing (e.g. due to TaskComplete in OnActivityResult) | ||||
| 			//use !performingLoad to make sure we're not already loading the database (after ActivityResult from File-Prepare-Activity; this would cause _loadDbFileTask to exist when we reload later!) | ||||
| 			if ( !IsFinishing && !_performingLoad)   | ||||
| 			if (!IsFinishing && !_performingLoad) | ||||
| 			{ | ||||
| 				 | ||||
| 				 | ||||
| 			    // OnResume is run every time the activity comes to the foreground. This code should only run when the activity is started (OnStart), but must | ||||
|  | ||||
|  | ||||
| 				// OnResume is run every time the activity comes to the foreground. This code should only run when the activity is started (OnStart), but must | ||||
| 				// be run in OnResume rather than OnStart so that it always occurrs after OnActivityResult (when re-creating a killed activity, OnStart occurs before OnActivityResult) | ||||
| 			    if (_starting) | ||||
| 			    { | ||||
| 				if (_starting) | ||||
| 				{ | ||||
|  | ||||
| 			        _starting = false; | ||||
| 					_starting = false; | ||||
|  | ||||
| 			        //database not yet loaded. | ||||
| 					//database not yet loaded. | ||||
|  | ||||
| 			        //check if pre-loading is enabled but wasn't started yet: | ||||
| 			        if (_loadDbFileTask == null && | ||||
| 			            _prefs.GetBoolean(GetString(Resource.String.PreloadDatabaseEnabled_key), true)) | ||||
| 			        { | ||||
| 			            // Create task to kick off file loading while the user enters the password | ||||
| 			            _loadDbFileTask = Task.Factory.StartNew(PreloadDbFile); | ||||
| 			            _loadDbTaskOffline = App.Kp2a.OfflineMode; | ||||
| 			        } | ||||
| 			    } | ||||
| 					//check if pre-loading is enabled but wasn't started yet: | ||||
| 					if (_loadDbFileTask == null && | ||||
| 						_prefs.GetBoolean(GetString(Resource.String.PreloadDatabaseEnabled_key), true)) | ||||
| 					{ | ||||
| 						// Create task to kick off file loading while the user enters the password | ||||
| 						_loadDbFileTask = Task.Factory.StartNew(PreloadDbFile); | ||||
| 						_loadDbTaskOffline = App.Kp2a.OfflineMode; | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 			} | ||||
|  | ||||
| 		    if (compositeKeyForImmediateLoad != null) | ||||
| 		    { | ||||
| 		        //reload the database (without most other stuff performed in PerformLoadDatabase. | ||||
| 		        // We're assuming that the db file (and if appropriate also the key file) are still available  | ||||
| 		        // and there's no need to re-init the file storage. if it is, loading will fail and the user has  | ||||
| 		        // to retry with typing the full password, but that's intended to avoid showing the password to a  | ||||
| 		        // a potentially unauthorized user (feature request https://keepass2android.codeplex.com/workitem/274) | ||||
| 		        Handler handler = new Handler(); | ||||
| 		        OnFinish onFinish = new AfterLoad(handler, this, _ioConnection); | ||||
| 		        _performingLoad = true; | ||||
|                 LoadDb task = new LoadDb(this, App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(), | ||||
| 		            onFinish, false, _makeCurrent); | ||||
| 		        _loadDbFileTask = null; // prevent accidental re-use | ||||
| 		        new ProgressTask(App.Kp2a, this, task).Run(); | ||||
| 		        compositeKeyForImmediateLoad = null; //don't reuse or keep in memory | ||||
| 			if (compositeKeyForImmediateLoad != null) | ||||
| 			{ | ||||
| 				//reload the database (without most other stuff performed in PerformLoadDatabase. | ||||
| 				// We're assuming that the db file (and if appropriate also the key file) are still available  | ||||
| 				// and there's no need to re-init the file storage. if it is, loading will fail and the user has  | ||||
| 				// to retry with typing the full password, but that's intended to avoid showing the password to a  | ||||
| 				// a potentially unauthorized user (feature request https://keepass2android.codeplex.com/workitem/274) | ||||
| 				Handler handler = new Handler(); | ||||
| 				OnFinish onFinish = new AfterLoad(handler, this, _ioConnection); | ||||
| 				_performingLoad = true; | ||||
| 				LoadDb task = new LoadDb(this, App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(), | ||||
| 					onFinish, false, _makeCurrent); | ||||
| 				_loadDbFileTask = null; // prevent accidental re-use | ||||
| 				new ProgressTask(App.Kp2a, this, task).Run(); | ||||
| 				compositeKeyForImmediateLoad = null; //don't reuse or keep in memory | ||||
|  | ||||
| 		    } | ||||
| 		    else | ||||
| 		    { | ||||
| 		        bool showKeyboard = true; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				bool showKeyboard = true; | ||||
|  | ||||
| 		        | ||||
| 		        EditText pwd = (EditText) FindViewById(Resource.Id.password_edit); | ||||
| 		        pwd.PostDelayed(() => | ||||
| 		        { | ||||
| 		            InputMethodManager keyboard = (InputMethodManager) GetSystemService(InputMethodService); | ||||
|                     if (showKeyboard) | ||||
|                     { | ||||
|                         pwd.RequestFocus(); | ||||
|                         keyboard.ShowSoftInput(pwd, 0); | ||||
|                     } | ||||
|                     else | ||||
| 		                keyboard.HideSoftInputFromWindow(pwd.WindowToken, HideSoftInputFlags.ImplicitOnly); | ||||
| 		        }, 50); | ||||
| 		    } | ||||
|  | ||||
| 				EditText pwd = (EditText)FindViewById(Resource.Id.password_edit); | ||||
| 				pwd.PostDelayed(() => | ||||
| 				{ | ||||
| 					InputMethodManager keyboard = (InputMethodManager)GetSystemService(InputMethodService); | ||||
| 					if (showKeyboard) | ||||
| 					{ | ||||
| 						pwd.RequestFocus(); | ||||
| 						keyboard.ShowSoftInput(pwd, 0); | ||||
| 					} | ||||
| 					else | ||||
| 						keyboard.HideSoftInputFromWindow(pwd.WindowToken, HideSoftInputFlags.ImplicitOnly); | ||||
| 				}, 50); | ||||
| 			} | ||||
|  | ||||
| 			_resumeCompleted = true; | ||||
|         } | ||||
|  | ||||
| 	    private void TryGetOtpFromClipboard() | ||||
| @@ -1954,7 +1990,7 @@ namespace keepass2android | ||||
| 			     | ||||
|                 btn.Tag = error; | ||||
|  | ||||
| 			    Toast.MakeText(this, Resource.String.fingerprint_reenable2, ToastLength.Long).Show(); | ||||
| 			    App.Kp2a.ShowMessage(this, Resource.String.fingerprint_reenable2,  MessageSeverity.Error); | ||||
|  | ||||
| 				_biometricDec = null; | ||||
| 				return false; | ||||
| @@ -2024,7 +2060,7 @@ namespace keepass2android | ||||
| 		/* | ||||
| 	private void errorMessage(CharSequence text) | ||||
| 	{ | ||||
| 		Toast.MakeText(this, text, ToastLength.Long).Show(); | ||||
| 		App.Kp2a.ShowMessage(this, text,  MessageSeverity.Error); | ||||
| 	} | ||||
| 	*/ | ||||
|  | ||||
| @@ -2084,7 +2120,9 @@ namespace keepass2android | ||||
| 			        _act.LoadingErrorCount++; | ||||
| 			    } | ||||
|  | ||||
| 			    if ((Exception != null) && (Exception.Message == KeePassLib.Resources.KLRes.FileCorrupted)) | ||||
| 				 | ||||
|  | ||||
|                 if ((Exception != null) && (Exception.Message == KeePassLib.Resources.KLRes.FileCorrupted)) | ||||
| 			    { | ||||
| 			        Message = _act.GetString(Resource.String.CorruptDatabaseHelp); | ||||
| 			    } | ||||
| @@ -2150,7 +2188,8 @@ namespace keepass2android | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					DisplayMessage(_act); | ||||
|                     MessageSeverity severity = Success ? MessageSeverity.Info : MessageSeverity.Error; | ||||
|                     App.Kp2a.ShowMessage(_act, Message, severity); | ||||
| 				    if (Success) | ||||
| 				    { | ||||
| 				        _act.LaunchNextActivity(); | ||||
| @@ -2218,7 +2257,7 @@ namespace keepass2android | ||||
| 				{ | ||||
| 					Kp2aLog.LogUnexpectedError(e); | ||||
|  | ||||
| 					ShowError( _act.GetString(Resource.String.ErrorUpdatingOtpAuxFile) + " " + e.Message); | ||||
| 					ShowError( _act.GetString(Resource.String.ErrorUpdatingOtpAuxFile) + " " + Util.GetErrorMessage(e)); | ||||
| 				} | ||||
|  | ||||
|  | ||||
| @@ -2234,7 +2273,7 @@ namespace keepass2android | ||||
|  | ||||
| 		    private void ShowError(string message) | ||||
| 		    { | ||||
| 		        App.Kp2a.ShowToast(message); | ||||
| 		        App.Kp2a.ShowToast(message, MessageSeverity.Error); | ||||
| 		    } | ||||
| 		} | ||||
| 		private class PasswordActivityBroadcastReceiver : BroadcastReceiver | ||||
|   | ||||
| @@ -128,11 +128,11 @@ namespace keepass2android | ||||
| 				Kp2aLog.LogUnexpectedError(e); | ||||
| 			} | ||||
| 			if (String.IsNullOrEmpty(_requestedUrl)) | ||||
| 				Toast.MakeText(this, GetString(Resource.String.query_credentials, new Java.Lang.Object[] {pluginDisplayName}), ToastLength.Long).Show(); | ||||
| 				App.Kp2a.ShowMessage(this, GetString(Resource.String.query_credentials, new Java.Lang.Object[] {pluginDisplayName}),  MessageSeverity.Info); | ||||
| 			else | ||||
| 				Toast.MakeText(this, | ||||
| 				App.Kp2a.ShowMessage(this, | ||||
| 				               GetString(Resource.String.query_credentials_for_url, | ||||
| 										 new Java.Lang.Object[] { pluginDisplayName, _requestedUrl }), ToastLength.Long).Show(); ; | ||||
| 										 new Java.Lang.Object[] { pluginDisplayName, _requestedUrl }),  MessageSeverity.Info); ; | ||||
| 		} | ||||
|  | ||||
| 		private void StartQuery() | ||||
|   | ||||
| @@ -25,6 +25,7 @@ using Android.Widget; | ||||
| using Android.Content.PM; | ||||
| using KeePassLib.Keys; | ||||
| using Android.Preferences; | ||||
| using Android.Provider; | ||||
| using Android.Runtime; | ||||
|  | ||||
| using Android.Views.InputMethods; | ||||
| @@ -35,6 +36,7 @@ using KeePassLib; | ||||
| using KeePassLib.Serialization; | ||||
| using Toolbar = AndroidX.AppCompat.Widget.Toolbar; | ||||
| using AndroidX.Core.Content; | ||||
| using keepass2android.Utils; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| @@ -79,8 +81,11 @@ namespace keepass2android | ||||
|  | ||||
| 			SetContentView(Resource.Layout.QuickUnlock); | ||||
|  | ||||
|             Util.InsetListener.ForBottomElement(FindViewById(Resource.Id.bottom_bar)).Apply(); | ||||
|             Util.InsetListener.ForTopElement(FindViewById(Resource.Id.appbar)).Apply(); | ||||
|  | ||||
| 			var collapsingToolbar = FindViewById<CollapsingToolbarLayout>(Resource.Id.collapsing_toolbar); | ||||
|  | ||||
|             var collapsingToolbar = FindViewById<CollapsingToolbarLayout>(Resource.Id.collapsing_toolbar); | ||||
| 			collapsingToolbar.SetTitle(GetString(Resource.String.QuickUnlock_prefs)); | ||||
|             SetSupportActionBar(FindViewById<Toolbar>(Resource.Id.toolbar)); | ||||
|              | ||||
| @@ -161,6 +166,29 @@ namespace keepass2android | ||||
|             if (bundle != null) | ||||
|                 numFailedAttempts = bundle.GetInt(NumFailedAttemptsKey, 0); | ||||
|  | ||||
|             FindViewById(Resource.Id.QuickUnlock_buttonEnableLock).Click += (object sender, EventArgs e) => | ||||
|             { | ||||
| 				Intent intent = new Intent(Settings.ActionSecuritySettings); | ||||
|                 StartActivity(intent); | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             FindViewById(Resource.Id.QuickUnlock_buttonCloseDb).Click += (object sender, EventArgs e) => | ||||
|             { | ||||
|                 App.Kp2a.Lock(false); | ||||
|             }; | ||||
|  | ||||
|             if (App.Kp2a.ScreenLockWasEnabledWhenOpeningDatabase == false && App.Kp2a.QuickUnlockBlockedWhenDeviceNotSecureWhenOpeningDatabase) | ||||
|             { | ||||
| 				FindViewById(Resource.Id.QuickUnlockForm).Visibility = ViewStates.Gone; | ||||
|                 FindViewById(Resource.Id.QuickUnlockBlocked).Visibility = ViewStates.Visible; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 FindViewById(Resource.Id.QuickUnlockForm).Visibility = ViewStates.Visible; | ||||
|                 FindViewById(Resource.Id.QuickUnlockBlocked).Visibility = ViewStates.Gone; | ||||
|             } | ||||
|  | ||||
|  | ||||
|  | ||||
|         } | ||||
| @@ -203,7 +231,7 @@ namespace keepass2android | ||||
| 				btn.SetImageResource(Resource.Drawable.baseline_fingerprint_24); | ||||
| 				 | ||||
| 			}, 1300); | ||||
| 			Toast.MakeText(this, message, ToastLength.Long).Show(); | ||||
| 			App.Kp2a.ShowMessage(this, message,  MessageSeverity.Error); | ||||
| 		} | ||||
|  | ||||
|          | ||||
| @@ -325,7 +353,7 @@ namespace keepass2android | ||||
| 			{ | ||||
| 				Kp2aLog.Log("QuickUnlock not successful!"); | ||||
| 				App.Kp2a.Lock(false); | ||||
| 				Toast.MakeText(this, GetString(Resource.String.QuickUnlock_fail), ToastLength.Long).Show(); | ||||
| 				App.Kp2a.ShowMessage(this, GetString(Resource.String.QuickUnlock_fail),  MessageSeverity.Error); | ||||
|                 Finish(); | ||||
| 			} | ||||
| 			 | ||||
| @@ -383,8 +411,9 @@ namespace keepass2android | ||||
| 		{ | ||||
| 			base.OnResume(); | ||||
| 			_design.ReapplyTheme(); | ||||
| 			 | ||||
| 			CheckIfUnloaded(); | ||||
|             App.Kp2a.MessagePresenter = new ChainedSnackbarPresenter(FindViewById(Resource.Id.main_content)); | ||||
|  | ||||
|             CheckIfUnloaded(); | ||||
|  | ||||
|             InitFingerprintUnlock(); | ||||
|  | ||||
| @@ -449,7 +478,8 @@ namespace keepass2android | ||||
|  | ||||
| 		protected override void OnPause() | ||||
| 		{ | ||||
| 			if (_biometryIdentifier != null) | ||||
|             App.Kp2a.MessagePresenter = new NonePresenter(); | ||||
|             if (_biometryIdentifier != null) | ||||
| 			{ | ||||
| 				Kp2aLog.Log("FP: Stop listening"); | ||||
| 				_biometryIdentifier.StopListening(); | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 5.8 KiB | 
| @@ -1,11 +1,11 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout | ||||
| xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|  | ||||
| android:layout_width="fill_parent" | ||||
| android:layout_height="fill_parent" | ||||
| android:fitsSystemWindows="true"> | ||||
| android:fitsSystemWindows="false"> | ||||
| <keepass2android.MeasuringLinearLayout | ||||
|   android:layout_width="fill_parent" | ||||
|   android:layout_height="fill_parent" | ||||
| @@ -17,7 +17,7 @@ android:fitsSystemWindows="true"> | ||||
|           android:layout_width="match_parent" | ||||
|           android:layout_height="match_parent" | ||||
|           android:layout_weight="1" | ||||
|           android:fitsSystemWindows="true"> | ||||
|           android:fitsSystemWindows="false"> | ||||
|     <com.google.android.material.appbar.AppBarLayout | ||||
|         android:id="@+id/appbar" | ||||
|         android:layout_width="match_parent" | ||||
| @@ -28,7 +28,7 @@ android:fitsSystemWindows="true"> | ||||
|           android:layout_width="match_parent" | ||||
|           android:layout_height="match_parent" | ||||
|           app:layout_scrollFlags="scroll|exitUntilCollapsed" | ||||
|           android:fitsSystemWindows="true" | ||||
|           android:fitsSystemWindows="false" | ||||
|           app:expandedTitleMarginStart="16dp" | ||||
|           app:expandedTitleMarginEnd="24dp" | ||||
|           app:expandedTitleMarginBottom="20sp"> | ||||
| @@ -78,20 +78,27 @@ android:paddingLeft="16dp" | ||||
| android:paddingRight="16dp" | ||||
|           android:paddingTop="16dp"> | ||||
|  | ||||
|  | ||||
|         <TextView | ||||
|                 android:id="@+id/QuickUnlock_label" | ||||
|                 android:text="@string/QuickUnlock_label" | ||||
|                 android:layout_width="fill_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_below="@id/filename_label" | ||||
|                 android:textSize="14sp" | ||||
|                 /> | ||||
|          | ||||
|           | ||||
|         <LinearLayout | ||||
|           android:layout_width="wrap_content" | ||||
|           android:layout_width="fill_parent" | ||||
|           android:layout_height="wrap_content" | ||||
|           android:orientation="horizontal"> | ||||
|           android:orientation="vertical" | ||||
|           android:id="@+id/QuickUnlockForm"> | ||||
|  | ||||
|           <TextView | ||||
|             android:id="@+id/QuickUnlock_label" | ||||
|             android:text="@string/QuickUnlock_label" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_below="@id/filename_label" | ||||
|             android:textSize="14sp" | ||||
|           /> | ||||
|  | ||||
|           <LinearLayout | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:orientation="horizontal"> | ||||
|  | ||||
|  | ||||
|           <EditText | ||||
|             android:inputType="textPassword" | ||||
| @@ -116,11 +123,65 @@ android:paddingRight="16dp" | ||||
|                                     android:src="@drawable/baseline_fingerprint_24" | ||||
|                                     android:scaleType="fitXY" | ||||
|                                     android:background="?android:selectableItemBackground" /> | ||||
|            | ||||
|              | ||||
|  | ||||
|           </LinearLayout> | ||||
|  | ||||
|         </LinearLayout> | ||||
|  | ||||
|         <LinearLayout | ||||
|           android:layout_width="match_parent" | ||||
|           android:layout_height="wrap_content" | ||||
|           android:orientation="vertical" | ||||
|           android:background="@color/md_theme_secondaryContainer" | ||||
|           android:id="@+id/QuickUnlockBlocked" | ||||
|           android:padding="16dp" | ||||
|           android:layout_gravity="center"> | ||||
|  | ||||
|           <TextView | ||||
|             android:id="@+id/quick_unlock_blocked_title" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:text="@string/password_based_quick_unlock_not_available" | ||||
|             android:textSize="16sp" | ||||
|             android:textStyle="bold" | ||||
|             android:gravity="center" | ||||
|             android:paddingBottom="8dp"/> | ||||
|  | ||||
|           <TextView | ||||
|             android:id="@+id/alert_message" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:text="@string/password_based_quick_unlock_not_available_text" | ||||
|             android:textSize="16sp" | ||||
|                             android:paddingBottom="8dp"/> | ||||
|           <LinearLayout | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:orientation="vertical"> | ||||
|             <Button   | ||||
|               android:id="@+id/QuickUnlock_buttonEnableLock" | ||||
|               android:layout_width="wrap_content" | ||||
|               android:layout_height="wrap_content" | ||||
|               android:layout_gravity="center" | ||||
|               android:backgroundTint="@color/md_theme_secondary" | ||||
|               android:textColor="@android:color/white" | ||||
|               android:text="@string/enable_screen_lock" | ||||
|               android:fontFamily="sans-serif-medium"   /> | ||||
|  | ||||
|               <Button   | ||||
|               android:id="@+id/QuickUnlock_buttonCloseDb" | ||||
|               android:layout_width="wrap_content" | ||||
|               android:layout_height="wrap_content" | ||||
|               android:layout_gravity="center" | ||||
|               android:backgroundTint="@color/md_theme_secondary" | ||||
|               android:textColor="@android:color/white" | ||||
|               android:fontFamily="sans-serif-medium"   | ||||
|               android:text="@string/QuickUnlock_lockButton" /> | ||||
|              | ||||
|             </LinearLayout> | ||||
|              | ||||
|  | ||||
|           </LinearLayout> | ||||
|        | ||||
|         <View | ||||
|         android:id="@+id/spacing" | ||||
|   | ||||
| @@ -1,5 +1,11 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|                                                      android:id="@+id/main_content" | ||||
|                                                      android:layout_width="match_parent" | ||||
|                                                      android:layout_height="match_parent" | ||||
|                                                      android:fitsSystemWindows="true"> | ||||
|  | ||||
| <ScrollView  | ||||
|             xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     android:id="@+id/entry_scroll" | ||||
|     android:background="?android:attr/colorBackground" | ||||
| @@ -267,4 +273,5 @@ | ||||
|             </LinearLayout> | ||||
|         </LinearLayout> | ||||
|     </LinearLayout> | ||||
| </ScrollView> | ||||
| </ScrollView> | ||||
| </androidx.coordinatorlayout.widget.CoordinatorLayout> | ||||
| @@ -1,4 +1,7 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="fill_parent"> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:orientation="vertical" | ||||
|     android:layout_width="fill_parent" | ||||
| @@ -75,4 +78,5 @@ | ||||
| 			 /> | ||||
|  | ||||
|  | ||||
| </LinearLayout> | ||||
| </LinearLayout> | ||||
| </ScrollView> | ||||
| @@ -1,5 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|                android:id="@+id/main_container" | ||||
|                 android:layout_width="fill_parent" | ||||
|                 android:background="?android:attr/colorBackground" | ||||
|                 android:layout_height="fill_parent"> | ||||
| @@ -322,4 +323,3 @@ | ||||
|     </ScrollView> | ||||
|  | ||||
|   </RelativeLayout> | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,7 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="fill_parent"> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:orientation="vertical" | ||||
|     android:layout_width="fill_parent" | ||||
| @@ -41,4 +44,5 @@ | ||||
|  | ||||
|  | ||||
|  | ||||
| </LinearLayout> | ||||
| </LinearLayout> | ||||
| </ScrollView> | ||||
| @@ -1,4 +1,7 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="fill_parent"> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:orientation="vertical" | ||||
|     android:layout_width="fill_parent" | ||||
| @@ -27,4 +30,5 @@ | ||||
|             android:importantForAccessibility="no"/> | ||||
|  | ||||
|  | ||||
| </LinearLayout> | ||||
| </LinearLayout> | ||||
| </ScrollView> | ||||
| @@ -1,4 +1,7 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="fill_parent"> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:orientation="vertical" | ||||
|     android:layout_width="fill_parent" | ||||
| @@ -42,4 +45,5 @@ | ||||
|  | ||||
|  | ||||
|  | ||||
| </LinearLayout> | ||||
| </LinearLayout> | ||||
| </ScrollView> | ||||
| @@ -1,10 +1,10 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <keepass2android.FixedDrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| <androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     android:id="@+id/drawer_layout" | ||||
|     android:layout_width="fill_parent" | ||||
|     android:layout_height="fill_parent" | ||||
|     android:fitsSystemWindows="true"> | ||||
| > | ||||
| <!-- activity view --> | ||||
|     <keepass2android.MeasuringLinearLayout | ||||
|         android:layout_width="fill_parent" | ||||
| @@ -17,14 +17,13 @@ | ||||
|                                                              android:id="@+id/main_content" | ||||
|                                                              android:layout_width="match_parent" | ||||
|                                                              android:layout_height="match_parent" | ||||
|                                                              android:layout_weight="1" | ||||
|                                                              android:fitsSystemWindows="true"> | ||||
|                                                              android:layout_weight="1"> | ||||
|  | ||||
|           <com.google.android.material.appbar.AppBarLayout | ||||
|             android:id="@+id/appbar" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="@dimen/detail_backdrop_height" | ||||
|             android:fitsSystemWindows="true" | ||||
|             android:fitsSystemWindows="false" | ||||
|             android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"> | ||||
|  | ||||
|             <com.google.android.material.appbar.CollapsingToolbarLayout | ||||
| @@ -32,14 +31,17 @@ | ||||
|               android:layout_width="match_parent" | ||||
|               android:layout_height="match_parent" | ||||
|               app:layout_scrollFlags="scroll|exitUntilCollapsed" | ||||
|               android:fitsSystemWindows="true" | ||||
|               app:expandedTitleMarginStart="48dp" | ||||
|               android:fitsSystemWindows="false" | ||||
|               app:collapsedTitleGravity="center" | ||||
|               app:expandedTitleGravity="bottom|left" | ||||
|            | ||||
|               app:expandedTitleMarginStart="8dp" | ||||
|               app:expandedTitleMarginEnd="24dp" | ||||
|               app:expandedTitleMarginBottom="20sp"> | ||||
|  | ||||
|               <RelativeLayout xmlns:tools="http://schemas.android.com/tools" | ||||
|                               android:layout_width="fill_parent" | ||||
|                               android:fitsSystemWindows="true" | ||||
|                               android:fitsSystemWindows="false" | ||||
|                               android:layout_height="fill_parent"> | ||||
|                 <ImageView | ||||
|                   android:id="@+id/backdrop" | ||||
| @@ -52,7 +54,7 @@ | ||||
|                   android:layout_alignParentBottom="true" | ||||
|                              | ||||
|                   android:layout_marginBottom="0sp" | ||||
|                   android:layout_marginLeft="48dp" | ||||
|                   android:layout_marginLeft="8dp" | ||||
|                   android:layout_marginRight="8dp" | ||||
|                   android:layout_width="wrap_content" | ||||
|                   android:layout_height="wrap_content"> | ||||
| @@ -446,4 +448,4 @@ | ||||
|         </LinearLayout> | ||||
|         </ScrollView> | ||||
|     </com.google.android.material.navigation.NavigationView> | ||||
| </keepass2android.FixedDrawerLayout> | ||||
| </androidx.drawerlayout.widget.DrawerLayout> | ||||
| @@ -1,61 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:orientation="vertical" | ||||
|     android:layout_width="fill_parent" | ||||
|     android:layout_height="fill_parent" | ||||
|     android:layout_margin="12dip"> | ||||
|     <LinearLayout | ||||
|         android:orientation="horizontal" | ||||
|         android:layout_width="fill_parent" | ||||
|         android:layout_height="wrap_content"> | ||||
|         <EditText | ||||
|             android:id="@+id/sftp_host" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:singleLine="true" | ||||
|             android:inputType="textNoSuggestions" | ||||
|             android:text="144.76.169.229" | ||||
|             android:hint="@string/hint_sftp_host" /> | ||||
|         <TextView | ||||
|             android:id="@+id/portsep" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:text=":" /> | ||||
|         <EditText | ||||
|             android:id="@+id/sftp_port" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:singleLine="true" | ||||
|             android:inputType="number" | ||||
|             android:text="22" | ||||
|             android:hint="@string/hint_sftp_port" /> | ||||
|     </LinearLayout> | ||||
|     <EditText | ||||
|         android:id="@+id/sftp_user" | ||||
|         android:layout_width="fill_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:singleLine="true" | ||||
|         android:text="philipp" | ||||
|         android:hint="@string/hint_username" /> | ||||
|     <EditText | ||||
|         android:id="@+id/sftp_password" | ||||
|         android:layout_width="fill_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:inputType="textPassword" | ||||
|         android:singleLine="true" | ||||
|         android:text="l2uientTjVhvyfzNpksa" | ||||
|         android:hint="@string/hint_pass" /> | ||||
|     <TextView | ||||
|         android:id="@+id/initial_dir" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginLeft="4dip" | ||||
|         android:layout_marginTop="4dip" | ||||
|         android:text="@string/initial_directory" /> | ||||
|     <EditText | ||||
|         android:id="@+id/sftp_initial_dir" | ||||
|         android:layout_width="fill_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:singleLine="true" | ||||
|         android:text="/home/philipp" /> | ||||
| </LinearLayout> | ||||
| @@ -1,4 +1,7 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="fill_parent"> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:orientation="vertical" | ||||
|     android:layout_width="fill_parent" | ||||
| @@ -197,3 +200,5 @@ | ||||
| 			 /> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
| </ScrollView> | ||||
							
								
								
									
										59
									
								
								src/keepass2android-app/Resources/layout/smbcredentials.axml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/keepass2android-app/Resources/layout/smbcredentials.axml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="fill_parent"> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:orientation="vertical" | ||||
|     android:layout_width="fill_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:padding="12dp" | ||||
|     android:layout_margin="12dip" | ||||
|     > | ||||
|     <TextView android:layout_width="fill_parent" | ||||
|               android:layout_height="wrap_content" | ||||
|               android:text="@string/hint_smb_credentials" /> | ||||
|   <LinearLayout | ||||
|     android:orientation="horizontal" | ||||
|     android:layout_width="fill_parent" | ||||
|     android:layout_height="wrap_content"> | ||||
|  | ||||
|     <EditText | ||||
|             android:id="@+id/smb_url" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:singleLine="true" | ||||
| 			android:layout_weight="1" | ||||
|             android:text="" | ||||
|       android:inputType="textWebEmailAddress" | ||||
| 			android:hint="@string/hint_smb_url" /> | ||||
|     | ||||
|   </LinearLayout> | ||||
|   <EditText | ||||
|     android:id="@+id/smb_domain" | ||||
|     android:layout_width="fill_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:singleLine="true" | ||||
|     android:text="" | ||||
|     android:hint="@string/hint_smb_domain" /> | ||||
|   <EditText | ||||
|              android:id="@+id/smb_user" | ||||
|              android:layout_width="fill_parent" | ||||
|              android:layout_height="wrap_content" | ||||
|              android:singleLine="true" | ||||
|              android:text="" | ||||
|              android:hint="@string/hint_smb_username" /> | ||||
|  | ||||
|   <EditText | ||||
|             android:id="@+id/smb_password" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:inputType="textPassword" | ||||
|             android:singleLine="true" | ||||
|             android:text="" | ||||
|             android:hint="@string/hint_pass" | ||||
|     android:importantForAccessibility="no"/> | ||||
|  | ||||
|  | ||||
|  | ||||
| </LinearLayout> | ||||
| </ScrollView> | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user