0
0
mirror of https://github.com/keepassxreboot/keepassxc.git synced 2024-09-20 04:12:15 +02:00

Merge branch 'develop' into issue-1148-msi-package

This commit is contained in:
Sebastian Marsching 2017-12-22 12:14:59 +01:00 committed by GitHub
commit 470ddb4704
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
146 changed files with 8853 additions and 1307 deletions

View File

@ -15,8 +15,8 @@ compiler:
- gcc
env:
- CONFIG=Release ASAN_OPTIONS=detect_odr_violation=1:leak_check_at_exit=0
- CONFIG=Debug ASAN_OPTIONS=detect_odr_violation=1:leak_check_at_exit=0
- CONFIG=Release ASAN_OPTIONS=detect_odr_violation=1
- CONFIG=Debug ASAN_OPTIONS=detect_odr_violation=1
git:
depth: 3
@ -37,7 +37,7 @@ script:
- cmake -DCMAKE_BUILD_TYPE=${CONFIG} -DWITH_GUI_TESTS=ON -DWITH_ASAN=ON -DWITH_XC_HTTP=ON -DWITH_XC_AUTOTYPE=ON -DWITH_XC_YUBIKEY=ON $CMAKE_ARGS ..
- make -j2
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then make test ARGS+="-E testgui --output-on-failure"; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then xvfb-run -a --server-args="-screen 0 800x600x24" make test ARGS+="-R testgui --output-on-failure"; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then ASAN_OPTIONS=${ASAN_OPTIONS}:leak_check_at_exit=0 xvfb-run -a --server-args="-screen 0 800x600x24" make test ARGS+="-R testgui --output-on-failure"; fi
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then make test ARGS+="--output-on-failure"; fi
# Generate snapcraft build when merging into master/develop branches

View File

@ -87,14 +87,15 @@ else
fi
EOF
chmod +x ./usr/bin/keepassxc_env
sed -i 's/Exec=keepassxc/Exec=keepassxc_env/' org.${LOWERAPP}.desktop
get_desktopintegration "org.${LOWERAPP}"
GLIBC_NEEDED=$(glibc_needed)
sed -i 's/Exec=keepassxc/Exec=keepassxc_env/' org.${LOWERAPP}.${APP}.desktop
get_desktopintegration "org.${LOWERAPP}.${APP}"
cd ..
generate_type2_appimage
GLIBC_NEEDED=$(glibc_needed)
NO_GLIBC_VERSION=true
mv ../out/*.AppImage ../KeePassXC-${VERSION}-${ARCH}.AppImage
rmdir ../out > /dev/null 2>&1
generate_type2_appimage -u "gh-releases-zsync|keepassxreboot|keepassxc|latest|KeePassXC-*-${ARCH}.AppImage.zsync"
mv ../out/*.AppImage* ../
rm -rf ../out

View File

@ -1,3 +1,13 @@
2.2.4 (2017-12-13)
=========================
- Prevent database corruption when locked [#1219]
- Fixes apply button not saving new entries [#1141]
- Switch to Consolas font on Windows for password edit [#1229]
- Multiple fixes to AppImage deployment [#1115, #1228]
- Fixes multiple memory leaks [#1213]
- Resize message close to 16x16 pixels [#1253]
2.2.2 (2017-10-22)
=========================

View File

@ -14,16 +14,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
cmake_minimum_required(VERSION 3.1.0)
project(KeePassXC)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
"Choose the type of build, options are: None Debug Release RelWithDebInfo Debug DebugFull Profile MinSizeRel."
FORCE)
endif()
project(KeePassXC)
cmake_minimum_required(VERSION 3.1.0)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
# Support Visual Studio Code
@ -43,13 +43,14 @@ option(WITH_APP_BUNDLE "Enable Application Bundle for macOS" ON)
option(WITH_XC_AUTOTYPE "Include Auto-Type." ON)
option(WITH_XC_HTTP "Include KeePassHTTP and Custom Icon Downloads." OFF)
option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
# Process ui files automatically from source files
set(CMAKE_AUTOUIC ON)
set(KEEPASSXC_VERSION_MAJOR "2")
set(KEEPASSXC_VERSION_MINOR "2")
set(KEEPASSXC_VERSION_PATCH "2")
set(KEEPASSXC_VERSION_PATCH "4")
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}")
# Distribution info
@ -96,7 +97,7 @@ if(WITH_APP_BUNDLE)
endif()
add_gcc_compiler_flags("-fno-common")
add_gcc_compiler_flags("-Wall -Wextra -Wundef -Wpointer-arith -Wno-long-long")
add_gcc_compiler_flags("-Wall -Werror -Wextra -Wundef -Wpointer-arith -Wno-long-long")
add_gcc_compiler_flags("-Wformat=2 -Wmissing-format-attribute")
add_gcc_compiler_flags("-fvisibility=hidden")
add_gcc_compiler_cxxflags("-fvisibility-inlines-hidden")

View File

@ -55,10 +55,6 @@ Files: cmake/GenerateProductVersion.cmake
Copyright: 2015 halex2005 <akharlov@gmail.com>
License: MIT
Files: cmake/CodeCoverage.cmake
Copyright: 2012 - 2015, Lars Bilke
License: BSD-3-clause
Files: share/icons/application/*/apps/keepassxc.png
share/icons/application/scalable/apps/keepassxc.svgz
share/icons/application/*/apps/keepassxc-dark.png
@ -179,6 +175,7 @@ Files: share/icons/application/*/actions/application-exit.png
share/icons/application/*/actions/view-history.png
share/icons/application/*/apps/internet-web-browser.png
share/icons/application/*/apps/preferences-desktop-icons.png
share/icons/application/*/apps/utilities-terminal.png
share/icons/application/*/categories/preferences-other.png
share/icons/application/*/status/dialog-error.png
share/icons/application/*/status/dialog-information.png

View File

@ -24,7 +24,8 @@ RUN set -x \
&& apt-get -y install software-properties-common
RUN set -x \
&& add-apt-repository ppa:beineri/opt-qt${QT5_PPA_VERSION}-trusty
&& add-apt-repository ppa:beineri/opt-qt${QT5_PPA_VERSION}-trusty \
&& add-apt-repository ppa:phoerious/keepassxc
RUN set -x \
&& apt-get update -y \
@ -42,7 +43,9 @@ RUN set -x \
zlib1g-dev \
libxi-dev \
libxtst-dev \
mesa-common-dev
mesa-common-dev \
libyubikey-dev \
libykpers-1-dev
ENV CMAKE_PREFIX_PATH=/opt/qt${QT5_VERSION}/lib/cmake
ENV LD_LIBRARY_PATH=/opt/qt${QT5_VERSION}/lib
@ -52,34 +55,8 @@ RUN set -x \
# AppImage dependencies
RUN set -x \
&& apt-get install -y \
wget \
libfuse2
# build libyubikey
ENV YUBIKEY_VERSION=1.13
RUN set -x \
&& wget "https://developers.yubico.com/yubico-c/Releases/libyubikey-${YUBIKEY_VERSION}.tar.gz" \
&& tar xf libyubikey-${YUBIKEY_VERSION}.tar.gz \
&& cd libyubikey-${YUBIKEY_VERSION} \
&& ./configure --prefix=/usr --libdir=/usr/lib/x86_64-linux-gnu \
&& make \
&& make install \
&& cd .. \
&& rm -Rf libyubikey-${YUBIKEY_VERSION}*
# build libykpers-1
ENV YKPERS_VERSION=1.18.0
RUN set -x \
&& apt-get install -y libusb-dev
RUN set -x \
&& wget "https://developers.yubico.com/yubikey-personalization/Releases/ykpers-${YKPERS_VERSION}.tar.gz" \
&& tar xf ykpers-${YKPERS_VERSION}.tar.gz \
&& cd ykpers-${YKPERS_VERSION} \
&& ./configure --prefix=/usr --libdir=/usr/lib/x86_64-linux-gnu \
&& make \
&& make install \
&& cd .. \
&& rm -Rf ykpers-${YKPERS_VERSION}*
libfuse2 \
wget
VOLUME /keepassxc/src
VOLUME /keepassxc/out

View File

@ -1,11 +1,13 @@
Install KeePassXC
Build and Install KeePassXC
=================
This document will guide you across the steps to install KeePassXC.
You can visit the online version of this document at the following link
This document will guide you through the steps to build and install KeePassXC from source.
You can visit the online version of this document at the following link:
https://github.com/keepassxreboot/keepassx/wiki/Install-Instruction-from-Source
The [KeePassXC QuickStart](./docs/QUICKSTART.md) gets you started using KeePassXC on your
Windows, Mac, or Linux computer using the pre-built binaries.
Build Dependencies
==================
@ -28,10 +30,9 @@ The following libraries are required:
Prepare the Building Environment
================================
* Building Environment on Linux ==> https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-Linux
* Building Environment on Windows ==> https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-Windows
* Building Environment on MacOS ==> https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-OS-X
* [Building Environment on Linux](https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-Linux)
* [Building Environment on Windows](https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-Windows)
* [Building Environment on MacOS](https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-OS-X)
Build Steps
===========
@ -39,32 +40,68 @@ Build Steps
To compile from source, open a **Terminal (on Linux/MacOS)** or a **MSYS2-MinGW shell (on Windows)**<br/>
**Note:** on Windows make sure you are using a **MINGW shell** by checking the label before the current path
Navigate to the path you have downloaded KeePassXC and type these commands:
First, download the KeePassXC [source tarball](https://keepassxc.org/download#source)
or check out the latest version from our [Git repository](https://github.com/keepassxreboot/keepassxc).
To clone the project from Git, `cd` to a suitable location and run
```bash
git clone https://github.com/keepassxreboot/keepassxc.git
```
This will clone the entire contents of the repository and check out the current `develop` branch.
To update the project from within the project's folder, you can run the following command:
```bash
git pull
```
Navigate to the directory where you have downloaded KeePassXC and type these commands:
```
cd directory-where-sources-live
mkdir build
cd build
cmake -DWITH_TESTS=OFF
cmake -DWITH_TESTS=OFF ...and other options - see below...
make
```
These steps place the compiled KeePassXC binary inside the `./build/src/` directory.
(Note the cmake notes/options below.)
**Note:** If you are on MacOS you must add this parameter to **Cmake**, with the Qt version you have installed<br/> `-DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake/`
**Cmake Notes:**
You will have the compiled KeePassXC binary inside the `./build/src/` directory.
* Common cmake parameters
Common cmake parameters
```
-DCMAKE_INSTALL_PREFIX=/usr/local
-DCMAKE_VERBOSE_MAKEFILE=ON
-DCMAKE_BUILD_TYPE=<RelWithDebInfo/Debug/Release>
-DWITH_GUI_TESTS=ON
```
```
-DCMAKE_INSTALL_PREFIX=/usr/local
-DCMAKE_VERBOSE_MAKEFILE=ON
-DCMAKE_BUILD_TYPE=<RelWithDebInfo/Debug/Release>
-DWITH_GUI_TESTS=ON
```
* cmake accepts the following options:
```
-DWITH_XC_AUTOTYPE=[ON|OFF] Enable/Disable Auto-Type (default: ON)
-DWITH_XC_HTTP=[ON|OFF] Enable/Disable KeePassHTTP and custom icon downloads (default: OFF)
-DWITH_XC_YUBIKEY=[ON|OFF] Enable/Disable YubiKey HMAC-SHA1 authentication support (default: OFF)
-DWITH_TESTS=[ON|OFF] Enable/Disable building of unit tests (default: ON)
-DWITH_GUI_TESTS=[ON|OFF] Enable/Disable building of GUI tests (default: OFF)
-DWITH_DEV_BUILD=[ON|OFF] Enable/Disable deprecated method warnings (default: OFF)
-DWITH_ASAN=[ON|OFF] Enable/Disable address sanitizer checks (Linux / macOS only) (default: OFF)
-DWITH_COVERAGE=[ON|OFF] Enable/Disable coverage tests (GCC only) (default: OFF)
```
* If you are on MacOS you must add this parameter to **Cmake**, with the Qt version you have installed<br/> `-DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake/`
:exclamation: When building with ASan support on macOS, you need to use `export ASAN_OPTIONS=detect_leaks=0` before running the tests (no LSan support in macOS).
Installation
============
To install this binary execute the following:
After you have successfully built KeePassXC, install the binary by executing the following:
```bash
sudo make install

View File

@ -1,10 +1,20 @@
# <img src="https://keepassxc.org/logo.png" width="40" height="40"/> KeePassXC [![Travis Build Status](https://travis-ci.org/keepassxreboot/keepassxc.svg?branch=develop)](https://travis-ci.org/keepassxreboot/keepassxc) [![Coverage Status](https://coveralls.io/repos/github/keepassxreboot/keepassxc/badge.svg)](https://coveralls.io/github/keepassxreboot/keepassxc)
# <img src="https://keepassxc.org/logo.png" width="40" height="40"/> KeePassXC
[![Travis Build Status](https://travis-ci.org/keepassxreboot/keepassxc.svg?branch=develop)](https://travis-ci.org/keepassxreboot/keepassxc) [![Coverage Status](https://coveralls.io/repos/github/keepassxreboot/keepassxc/badge.svg)](https://coveralls.io/github/keepassxreboot/keepassxc)
KeePass Cross-platform Community Edition
## About
[KeePassXC](https://keepassxc.org) is a community fork of [KeePassX](https://www.keepassx.org/) with the goal to extend and improve it with new features and bugfixes to provide a feature-rich, fully cross-platform and modern open-source password manager.
## About KeePassXC
[KeePassXC](https://keepassxc.org) is a cross-platform community fork of
[KeePassX](https://www.keepassx.org/).
Our goal is to extend and improve it with new features and bugfixes
to provide a feature-rich, fully cross-platform and modern
open-source password manager.
## Installation
The [KeePassXC QuickStart](./docs/QUICKSTART.md) gets you started using
KeePassXC on your Windows, Mac, or Linux computer using pre-compiled binaries
from the [downloads page](https://keepassxc.org/download).
Additionally, individual Linux distributions may ship their own versions,
so please check out your distribution's package list to see if KeePassXC is available.
## Additional features compared to KeePassX
- Auto-Type on all three major platforms (Linux, Windows, macOS)
@ -19,67 +29,39 @@ KeePass Cross-platform Community Edition
- Using website favicons as entry icons
- Merging of databases
- Automatic reload when the database changed on disk
- KeePassHTTP support for use with KeePassHTTP-Connector for [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepasshttp-connector/) and [Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepasshttp-connector/dafgdjggglmmknipkhngniifhplpcldb), and [passafari](https://github.com/mmichaa/passafari.safariextension/) in Safari.
- Browser integration with KeePassHTTP-Connector for
[Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepasshttp-connector/) and
[Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepasshttp-connector/dafgdjggglmmknipkhngniifhplpcldb), and
[passafari](https://github.com/mmichaa/passafari.safariextension/) in Safari. [[See note about KeePassHTTP]](#Note_about_KeePassHTTP)
- Many bug fixes
For a full list of features and changes, read the [CHANGELOG](CHANGELOG) document.
### Note about KeePassHTTP
KeePassHTTP is not a highly secure protocol and has certain flaw which allow an attacker to decrypt your passwords when they manage to intercept communication between a KeePassHTTP server and PassIFox/chromeIPass over a network connection (see [here](https://github.com/pfn/keepasshttp/issues/258) and [here](https://github.com/keepassxreboot/keepassxc/issues/147)). KeePassXC therefore strictly limits communication between itself and the browser plugin to your local computer. As long as your computer is not compromised, your passwords are fairly safe that way, but use it at your own risk!
## Building KeePassXC
### Installation
Pre-compiled binaries can be found on the [downloads page](https://keepassxc.org/download). Additionally, individual Linux distributions may ship their own versions, so please check out your distribution's package list to see if KeePassXC is available.
Detailed instructions are available in the [Build and Install](./INSTALL.md)
page or on the [Wiki page](https://github.com/keepassxreboot/keepassxc/wiki/Building-KeePassXC).
### Building KeePassXC
## Contributing
*More detailed instructions are available in the INSTALL file or on the [Wiki page](https://github.com/keepassxreboot/keepassxc/wiki/Building-KeePassXC).*
First, you must download the KeePassXC [source tarball](https://keepassxc.org/download#source) or check out the latest version from our [Git repository](https://github.com/keepassxreboot/keepassxc).
To clone the project from Git, `cd` to a suitable location and run
```bash
git clone https://github.com/keepassxreboot/keepassxc.git
```
This will clone the entire contents of the repository and check out the current `develop` branch.
To update the project from within the project's folder, you can run the following command:
```bash
git pull
```
Once you have downloaded the source code, you can `cd` into the source code directory, build and install KeePassXC:
```bash
mkdir build
cd build
cmake -DWITH_TESTS=OFF ..
make -j8
sudo make install
```
cmake accepts the following options:
```
-DWITH_XC_AUTOTYPE=[ON|OFF] Enable/Disable Auto-Type (default: ON)
-DWITH_XC_HTTP=[ON|OFF] Enable/Disable KeePassHTTP and custom icon downloads (default: OFF)
-DWITH_XC_YUBIKEY=[ON|OFF] Enable/Disable YubiKey HMAC-SHA1 authentication support (default: OFF)
-DWITH_TESTS=[ON|OFF] Enable/Disable building of unit tests (default: ON)
-DWITH_GUI_TESTS=[ON|OFF] Enable/Disable building of GUI tests (default: OFF)
-DWITH_DEV_BUILD=[ON|OFF] Enable/Disable deprecated method warnings (default: OFF)
-DWITH_ASAN=[ON|OFF] Enable/Disable address sanitizer checks (Linux / macOS only) (default: OFF)
-DWITH_COVERAGE=[ON|OFF] Enable/Disable coverage tests (GCC only) (default: OFF)
```
:exclamation: When building with ASan support on macOS, you need to use `export ASAN_OPTIONS=detect_leaks=0` before running the tests (no LSan support in macOS).
### Contributing
We are always looking for suggestions how to improve our application. If you find any bugs or have an idea for a new feature, please let us know by opening a report in our [issue tracker](https://github.com/keepassxreboot/keepassxc/issues) on GitHub or join us on IRC on freenode channels #keepassxc or #keepassxc-dev.
We are always looking for suggestions how to improve our application.
If you find any bugs or have an idea for a new feature, please let us know by
opening a report in our [issue tracker](https://github.com/keepassxreboot/keepassxc/issues)
on GitHub or join us on IRC on freenode channels #keepassxc or #keepassxc-dev.
You can of course also directly contribute your own code. We are happy to accept your pull requests.
Please read the [CONTRIBUTING document](.github/CONTRIBUTING.md) for further information.
### Note about KeePassHTTP
The KeePassHTTP protocol is not a highly secure protocol.
It has a certain flaw which could allow an attacker to decrypt your passwords
should they manage to impersonate the web browser extension from a remote address.
<!--intercept communication between a KeePassHTTP server
and PassIFox/chromeIPass over a network connection -->
(See [here](https://github.com/pfn/keepasshttp/issues/258) and [here](https://github.com/keepassxreboot/keepassxc/issues/147)).
To minimize the risk, KeePassXC strictly limits communication between itself
and the browser plugin to your local computer (localhost).
This makes your passwords quite safe,
but as with all open source software, use it at your own risk!

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
docs/KeePassXC-Confirm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
docs/KeePassXC-Connect.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

47
docs/QUICKSTART.md Normal file
View File

@ -0,0 +1,47 @@
# Quick Start for KeePassXC
This procedure gets KeePassXC running on your computer with browser integration,
using the pre-built binaries available for [download](https://keepassxc.org/download)
from [KeePassXC site](https://keepassxc.org).
**TL;DR** KeePassXC saves your passwords securely.
When you double-click a URL in KeePassXC, it launches your default browser to that URL.
With browser integration configured, KeePassXC automatically enters
username/password credentials into web page fields.
## Installing and Starting KeePassXC
* [Download the native installer](https://keepassxc.org/download) and install
KeePassXC for your Windows, macOS, or Linux computer in the usual way for your platform.
* Open the KeePassXC application.
* Create a new database and give it a master key that's used to unlock the database file.
This database holds entries (usernames, passwords, account numbers, notes)
for all your websites, programs, etc.
* Create a few entries - enter the username, password, URL, and optionally notes about the entry.
* KeePassXC securely stores those entries in the database.
## Setting up Browser Integration with KeePassXC
* *Within KeePassXC*, go to **Tools->Settings** (on macOS, go to **KeePassXC->Preferences**.)
* In **Browser Integration**, check **Enable KeePassHTTP server**
Leave the other options at their defaults.
* *In your default web browser,* install the KeePassHTTP-Connector extension/add-on. Instructions for [Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepasshttp-connector/?src=api) or [Chrome](https://chrome.google.com/webstore/detail/keepasshttp-connector/dafgdjggglmmknipkhngniifhplpcldb?utm_source=chrome-app-launcher-info-dialog)
* Click the KeePassXC icon in the upper-right corner. You'll see the dialog below.
* Click the blue Connect button to make the browser extension connect to the KeePassXC application.
<img src="./KeePassXC-Connect.png" height="200" alt="KeePassXC Connect dialog">
* *Switch back to KeePassXC.* You'll see a dialog (below) indicating that a request to connect has arrived.
* Give the connection a name (perhaps *Keepass-Browsername*, any unique name will suffice) and click OK to accept it.
* This one-time operation connects KeePassXC and your browser.
<img src="./KeePassXC-Accept-Button.png" height="200" alt="KeePassXC accept connection dialog">
## Using Browser Integration
* *Within KeePassXC,* double-click the URL of an entry,
or select it and type Ctrl+U (Cmd+U on macOS).
* Your browser opens to that URL.
* If there are username/password fields on that page, you will see the dialog below.
Click *Allow* to confirm that KeePassXC may access the credentials to auto-fill the fields.
* Check *Remember this decision* to allow this each time you visit the page.
<img src="./KeePassXC-Confirm.png" height="200" alt="KeePassCX Confirm Access dialog">

View File

@ -50,14 +50,14 @@ printUsage() {
local cmd
if [ "" == "$1" ] || [ "help" == "$1" ]; then
cmd="COMMAND"
elif [ "check" == "$1" ] || [ "merge" == "$1" ] || [ "build" == "$1" ] || [ "sign" == "$1" ]; then
elif [ "check" == "$1" ] || [ "merge" == "$1" ] || [ "build" == "$1" ] || [ "gpgsign" == "$1" ] || [ "appsign" == "$1" ]; then
cmd="$1"
else
logError "Unknown command: '$1'\n"
cmd="COMMAND"
fi
printf "\e[1mUsage:\e[0m $(basename $0) $cmd [--version x.y.z] [options]\n"
printf "\e[1mUsage:\e[0m $(basename $0) $cmd [options]\n"
if [ "COMMAND" == "$cmd" ]; then
cat << EOF
@ -66,7 +66,8 @@ Commands:
check Perform a dry-run check, nothing is changed
merge Merge release branch into main branch and create release tags
build Build and package binary release from sources
sign Sign previously compiled release packages
gpgsign Sign previously compiled release packages with GPG
appsign Sign binaries with code signing certificates on Windows and macOS
help Show help for the given command
EOF
elif [ "merge" == "$cmd" ]; then
@ -113,16 +114,25 @@ Options:
-n, --no-source-tarball Don't build source tarball
-h, --help Show this help
EOF
elif [ "sign" == "$cmd" ]; then
elif [ "gpgsign" == "$cmd" ]; then
cat << EOF
Sign previously compiled release packages
Sign previously compiled release packages with GPG
Options:
-f, --files Files to sign (required)
-g, --gpg-key GPG key used to sign the files (default: '${GPG_KEY}')
--signtool Specify the signtool executable (default: 'signtool')
--signtool-key Provide a key to be used with signtool (for Windows EXE)
-h, --help Show this help
EOF
elif [ "appsign" == "$cmd" ]; then
cat << EOF
Sign binaries with code signing certificates on Windows and macOS
Options:
-f, --files Files to sign (required)
-k, --signtool-key Key to be used with signtool (required for Windows EXE)
-i, --identity Apple Developer ID to be used with codesign (required for macOS APP and DMG)
-h, --help Show this help
EOF
fi
@ -264,13 +274,13 @@ checkChangeLog() {
}
checkAppStreamInfo() {
if [ ! -f share/linux/org.keepassxc.appdata.xml ]; then
if [ ! -f share/linux/org.keepassxc.KeePassXC.appdata.xml ]; then
exitError "No AppStream info file found!"
fi
grep -qPzo "<release version=\"${RELEASE_NAME}\" date=\"\d{4}-\d{2}-\d{2}\">" share/linux/org.keepassxc.appdata.xml
grep -qPzo "<release version=\"${RELEASE_NAME}\" date=\"\d{4}-\d{2}-\d{2}\">" share/linux/org.keepassxc.KeePassXC.appdata.xml
if [ $? -ne 0 ]; then
exitError "'share/linux/org.keepassxc.appdata.xml' has not been updated to the '${RELEASE_NAME}' release!"
exitError "'share/linux/org.keepassxc.KeePassXC.appdata.xml' has not been updated to the '${RELEASE_NAME}' release!"
fi
}
@ -289,7 +299,28 @@ checkSnapcraft() {
checkTransifexCommandExists() {
command -v tx > /dev/null
if [ 0 -ne $? ]; then
exitError "Transifex tool 'tx' not installed! Please install it using 'pip install transifex-client'"
exitError "Transifex tool 'tx' not installed! Please install it using 'pip install transifex-client'."
fi
}
checkOsslsigncodeCommandExists() {
command -v osslsigncode > /dev/null
if [ 0 -ne $? ]; then
exitError "osslsigncode command not found on the PATH! Please install it using 'pacman -S mingw-w64-osslsigncode'."
fi
}
checkCodesignCommandExists() {
command -v codesign > /dev/null
if [ 0 -ne $? ]; then
exitError "codesign command not found on the PATH! Please check that you have correctly installed Xcode."
fi
}
checkCreateDMGCommandExists() {
command -v create-dmg > /dev/null
if [ 0 -ne $? ]; then
exitError "create-dmg command not found on the PATH! Please install it using 'npm install --global create-dmg'."
fi
}
@ -665,81 +696,54 @@ build() {
# -----------------------------------------------------------------------
# sign command
# gpgsign command
# -----------------------------------------------------------------------
sign() {
SIGN_FILES=()
SIGNTOOL="signtool"
SIGNTOOL_KEY=""
gpgsign() {
local sign_files=()
while [ $# -ge 1 ]; do
local arg="$1"
case "$arg" in
-f|--files)
while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
SIGN_FILES+=("$2")
sign_files+=("$2")
shift
done ;;
-g|--gpg-key)
GPG_KEY="$2"
shift ;;
--signtool)
SIGNTOOL="$2"
shift ;;
--signtool-key)
SIGNTOOL_KEY="$2"
shift ;;
-h|--help)
printUsage "sign"
printUsage "gpgsign"
exit ;;
*)
logError "Unknown option '$arg'\n"
printUsage "sign"
printUsage "gpgsign"
exit 1 ;;
esac
shift
done
if [ -z "$SIGN_FILES" ]; then
if [ -z "${sign_files}" ]; then
logError "Missing arguments, --files is required!\n"
printUsage "sign"
printUsage "gpgsign"
exit 1
fi
if [[ -n "$SIGNTOOL_KEY" && ! -f "$SIGNTOOL_KEY" ]]; then
exitError "Signtool Key was not found!"
elif [[ -f "$SIGNTOOL_KEY" && ! -x $(command -v "${SIGNTOOL}") ]]; then
exitError "signtool program not found on PATH!"
fi
for f in "${SIGN_FILES[@]}"; do
for f in "${sign_files[@]}"; do
if [ ! -f "$f" ]; then
exitError "File '${f}' does not exist!"
exitError "File '${f}' does not exist or is not a file!"
fi
if [[ -n "$SIGNTOOL_KEY" && ${f: -4} == '.exe' ]]; then
logInfo "Signing file '${f}' using signtool...\n"
read -s -p "Signtool Key Password: " password
echo
"${SIGNTOOL}" sign -f "${SIGNTOOL_KEY}" -p ${password} -v -t http://timestamp.comodoca.com/authenticode ${f}
if [ 0 -ne $? ]; then
exitError "Signing failed!"
fi
fi
logInfo "Signing file '${f}' using release key..."
gpg --output "${f}.sig" --armor --local-user "$GPG_KEY" --detach-sig "$f"
if [ 0 -ne $? ]; then
exitError "Signing failed!"
fi
logInfo "Creating digest for file '${f}'..."
local rp="$(realpath "$f")"
local bname="$(basename "$f")"
@ -750,6 +754,143 @@ sign() {
}
# -----------------------------------------------------------------------
# appsign command
# -----------------------------------------------------------------------
appsign() {
local sign_files=()
local signtool_key
local codesign_identity
while [ $# -ge 1 ]; do
local arg="$1"
case "$arg" in
-f|--files)
while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
sign_files+=("$2")
shift
done ;;
-k|--signtool-key)
signtool_key="$2"
shift ;;
-i|--identity)
codesign_identity="$2"
shift ;;
-h|--help)
printUsage "appsign"
exit ;;
*)
logError "Unknown option '$arg'\n"
printUsage "appsign"
exit 1 ;;
esac
shift
done
if [ -z "${sign_files}" ]; then
logError "Missing arguments, --files is required!\n"
printUsage "appsign"
exit 1
fi
for f in "${sign_files[@]}"; do
if [ ! -f "${f}" ]; then
exitError "File '${f}' does not exist or is not a file!"
fi
done
if [ "$(uname -s)" == "Darwin" ]; then
if [ -z "${codesign_identity}" ]; then
logError "Missing arguments, --identity is required on macOS!\n"
printUsage "appsign"
exit 1
fi
checkCodesignCommandExists
checkCreateDMGCommandExists
local orig_dir="$(pwd)"
for f in "${sign_files[@]}"; do
if [[ ${f: -4} == '.dmg' ]]; then
logInfo "Unpacking disk image '${f}'..."
local tmp_dir="/tmp/KeePassXC_${RANDOM}"
mkdir -p ${tmp_dir}/{mnt,app}
hdiutil attach -quiet -noautoopen -mountpoint ${tmp_dir}/mnt "${f}"
cd ${tmp_dir}
cp -a ./mnt/KeePassXC.app ./app
hdiutil detach -quiet ${tmp_dir}/mnt
if [ ! -d ./app/KeePassXC.app ]; then
cd "${orig_dir}"
exitError "Unpacking failed!"
fi
logInfo "Signing app using codesign..."
codesign --sign "${codesign_identity}" --verbose --deep ./app/KeePassXC.app
if [ 0 -ne $? ]; then
cd "${orig_dir}"
exitError "Signing failed!"
fi
logInfo "Repacking and signing disk image..."
create-dmg ./app/KeePassXC.app
cd "${orig_dir}"
cp -f ${tmp_dir}/KeePassXC-*.dmg "${f}"
rm -Rf ${tmp_dir}
else
logInfo "Skipping non-DMG file '${f}'..."
fi
done
elif [ "$(uname -o)" == "Msys" ]; then
if [ -z "${signtool_key}" ]; then
logError "Missing arguments, --signtool-key is required on Windows!\n"
printUsage "appsign"
exit 1
fi
checkOsslsigncodeCommandExists
if [[ ! -f "${signtool_key}" ]]; then
exitError "Key file was not found!"
fi
read -s -p "Key password: " password
echo
for f in "${sign_files[@]}"; do
if [[ ${f: -4} == '.exe' ]]; then
logInfo "Signing file '${f}' using osslsigncode..."
# output a signed exe; we have to use a different name due to osslsigntool limitations
osslsigncode sign -pkcs12 "${signtool_key}" -pass "${password}" \
-t "http://timestamp.comodoca.com/authenticode" -in "${f}" -out "${f}.signed"
if [ 0 -ne $? ]; then
rm -f "${f}.signed"
exitError "Signing failed!"
fi
# overwrite the original exe with the signed exe
mv -f "${f}.signed" "${f}"
else
logInfo "Skipping non-EXE file '${f}'..."
fi
done
else
exitError "Unsupported platform for code signing!\n"
fi
logInfo "All done!"
}
# -----------------------------------------------------------------------
# parse global command line
# -----------------------------------------------------------------------
@ -762,8 +903,8 @@ if [ "" == "$MODE" ]; then
elif [ "help" == "$MODE" ]; then
printUsage "$1"
exit
elif [ "check" == "$MODE" ] || [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] || [ "sign" == "$MODE" ]; then
$MODE "$@"
elif [ "check" == "$MODE" ] || [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] || [ "gpgsign" == "$MODE" ] || [ "appsign" == "$MODE" ]; then
${MODE} "$@"
else
printUsage "$MODE"
fi

View File

@ -30,8 +30,8 @@ if(UNIX AND NOT APPLE)
install(DIRECTORY icons/application/ DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor
FILES_MATCHING PATTERN "application-x-keepassxc.png" PATTERN "application-x-keepassxc.svgz"
PATTERN "status" EXCLUDE PATTERN "actions" EXCLUDE PATTERN "categories" EXCLUDE)
install(FILES linux/org.keepassxc.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
install(FILES linux/org.keepassxc.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
install(FILES linux/org.keepassxc.KeePassXC.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
install(FILES linux/org.keepassxc.KeePassXC.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
install(FILES linux/keepassxc.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages)
endif(UNIX AND NOT APPLE)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 457 B

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 958 B

Binary file not shown.

View File

@ -1,5 +1,4 @@
[General]
ShowToolbar=true
RememberLastDatabases=true
RememberLastKeyFiles=true
OpenPreviousDatabasesOnStartup=true

View File

@ -1,16 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2017 KeePassXC Team <team@keepassxc.org> -->
<!--Copyright 2017 KeePassXC Team <team@keepassxc.org> -->
<component type="desktop-application">
<id>org.keepassxc</id>
<id>org.keepassxc.KeePassXC.desktop</id>
<name>KeePassXC</name>
<metadata_license>CC-BY-3.0</metadata_license>
<project_license>GPL-3.0+</project_license>
<icon type="stock">keepassxc</icon>
<url type="homepage">https://keepassxc.org</url>
<mimetypes>
<mimetype>application/x-keepass2</mimetype>
</mimetypes>
<summary>Community-driven port of the Windows application “KeePass Password Safe”</summary>
<developer_name>KeePassXC Team</developer_name>
<description>
<p>
KeePassXC is an application for people with extremely high demands on secure
@ -19,56 +18,53 @@
</p>
</description>
<launchable type="desktop-id">org.keepassxc.desktop</launchable>
<launchable type="desktop-id">org.keepassxc.KeePassXC.desktop</launchable>
<url type="homepage">https://keepassxc.org</url>
<url type="bugtracker">https://github.com/keepassxreboot/keepassxc/issues</url>
<url type="faq">https://keepassxc.org/docs#faq</url>
<url type="help">https://keepassxc.org/docs</url>
<url type="translate">https://www.transifex.com/keepassxc/keepassxc</url>
<screenshots>
<screenshot type="default">
<image>https://keepassxc.org/images/screenshots/linux/screen_001.png</image>
<caption>Create, Import or Open Databases</caption>
</screenshot>
<screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_002.png</image>
<caption>Organize with Groups and Entries</caption>
</screenshot>
<screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_003.png</image>
<caption>Database Entry</caption>
</screenshot>
<screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_004.png</image>
</screenshot>
<screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_005.png</image>
<caption>Icon Selection for Entry</caption>
</screenshot>
<screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_006.png</image>
</screenshot>
<screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_007.png</image>
</screenshot>
<screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_008.png</image>
</screenshot>
<screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_009.png</image>
</screenshot>
<screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_010.png</image>
</screenshot>
<screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_011.png</image>
</screenshot>
<screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_012.png</image>
</screenshot>
<screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_013.png</image>
</screenshot>
<screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_014.png</image>
<caption>Password Generator</caption>
</screenshot>
</screenshots>
<releases>
<release version="2.2.4" date="2017-12-13">
<description>
<ul>
<li>Prevent database corruption when locked [#1219]</li>
<li>Fixes apply button not saving new entries [#1141]</li>
<li>Switch to Consolas font on Windows for password edit [#1229]</li>
<li>Multiple fixes to AppImage deployment [#1115, #1228]</li>
<li>Fixes multiple memory leaks [#1213]</li>
<li>Resize message close to 16x16 pixels [#1253]</li>
</ul>
</description>
</release>
<release version="2.2.2" date="2017-10-22">
<description>
<p>Changes included in this release:</p>
<ul>
<li>Fixed entries with empty URLs being reported to KeePassHTTP clients [#1031]</li>
<li>Fixed YubiKey detection and enabled CLI tool for AppImage binary [#1100]</li>
@ -85,8 +81,10 @@
<li>Fixed screen lock and Google fallback settings [#1029]</li>
</ul>
</description>
</release><release version="2.2.1" date="2017-10-01">
</release>
<release version="2.2.1" date="2017-10-01">
<description>
<p>Changes included in this release:</p>
<ul>
<li>Corrected multiple snap issues [#934, #1011]</li>
<li>Corrected multiple custom icon issues [#708, #719, #994]</li>
@ -103,6 +101,7 @@
</release>
<release version="2.2.0" date="2017-06-23">
<description>
<p>Changes included in this release:</p>
<ul>
<li>Added YubiKey 2FA integration for unlocking databases [#127]</li>
<li>Added TOTP support [#519]</li>
@ -138,6 +137,7 @@
</release>
<release version="2.1.4" date="2017-04-09">
<description>
<p>Changes included in this release:</p>
<ul>
<li>Bumped KeePassHTTP version to 1.8.4.2</li>
<li>KeePassHTTP confirmation window comes to foreground [#466]</li>
@ -146,6 +146,7 @@
</release>
<release version="2.1.3" date="2017-03-03">
<description>
<p>Changes included in this release:</p>
<ul>
<li>Fix possible overflow in zxcvbn library [#363]</li>
<li>Revert HiDPI setting to avoid problems on laptop screens [#332]</li>
@ -160,6 +161,7 @@
</release>
<release version="2.1.2" date="2017-02-17">
<description>
<p>Changes included in this release:</p>
<ul>
<li>Ask for save location when creating a new database [#302]</li>
<li>Remove Libmicrohttpd dependency to clean up the code and ensure better OS X compatibility [#317, #265]</li>
@ -177,6 +179,7 @@
</release>
<release version="2.1.1" date="2017-02-06">
<description>
<p>Changes included in this release:</p>
<ul>
<li>Enabled HTTP plugin build; plugin is disabled by default and limited to localhost [#147]</li>
<li>Escape HTML in dialog boxes [#247]</li>
@ -189,6 +192,7 @@
</release>
<release version="2.1.0" date="2017-01-22">
<description>
<p>Changes included in this release:</p>
<ul>
<li>Show unlock dialog when using autotype on a closed database [#10, #89]</li>
<li>Show different tray icon when database is locked [#37, #46]</li>

View File

@ -9,6 +9,7 @@ Comment=Community-driven port of the Windows application “KeePass Password Saf
Exec=keepassxc %f
TryExec=keepassxc
Icon=keepassxc
StartupWMClass=keepassxc
Terminal=false
Type=Application
Version=1.0

File diff suppressed because it is too large Load Diff

View File

@ -571,7 +571,7 @@ Sie können sie speichern oder die Sperre freigeben.</translation>
Do you want to save the database before locking it?
Otherwise your changes are lost.</source>
<translation>Dieses Datenbank wurde geändert.
Soll sie gespeichert werden bevor sie gesperrt wirt?
Soll sie gespeichert werden bevor sie gesperrt wird?
Anderenfalls gehen Ihre Änderungen verloren.</translation>
</message>
<message>

View File

@ -69,7 +69,7 @@ Núcleo: %3 %4</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
<translation>Distribución: %1</translation>
</message>
</context>
<context>
@ -250,7 +250,7 @@ Por favor seleccione si desea autorizar su acceso.</translation>
</message>
<message>
<source>Codec</source>
<translation>Codificación</translation>
<translation>Códec</translation>
</message>
<message>
<source>Text is qualified by</source>
@ -302,11 +302,11 @@ Por favor seleccione si desea autorizar su acceso.</translation>
</message>
<message>
<source>Original data: </source>
<translation>Datos originales:</translation>
<translation>Dato original:</translation>
</message>
<message>
<source>Error(s) detected in CSV file !</source>
<translation>¡Se detectaron errores en el archivo CSV!</translation>
<translation>¡Error(es) detectado(s) en el archivo CSV!</translation>
</message>
<message>
<source> more messages skipped]</source>
@ -728,7 +728,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Merge Request</source>
<translation>Solicitud de unión</translation>
<translation>Solicitud de Unión</translation>
</message>
<message>
<source>The database file has changed and you have unsaved changes.Do you want to merge your changes?</source>
@ -744,7 +744,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Are you sure you want to permanently delete everything from your recycle bin?</source>
<translation>¿Está seguro(a) que quiere permanentemente eliminar todo de su papelera de reciclaje?</translation>
<translation>¿Está seguro que quiere eliminar permanentemente todo de su papelera de reciclaje?</translation>
</message>
</context>
<context>
@ -795,7 +795,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Select file</source>
<translation>Seleccionar archivo llave</translation>
<translation>Seleccionar archivo</translation>
</message>
<message>
<source>Unable to open file</source>
@ -817,11 +817,11 @@ Do you want to open it anyway?</source>
</message>
<message numerus="yes">
<source>%n week(s)</source>
<translation><numerusform>%n semana</numerusform><numerusform>%n semanas</numerusform></translation>
<translation><numerusform>%n semana</numerusform><numerusform>%n semana(s)</numerusform></translation>
</message>
<message numerus="yes">
<source>%n month(s)</source>
<translation><numerusform>%n mes</numerusform><numerusform>%n meses</numerusform></translation>
<translation><numerusform>%n mes</numerusform><numerusform>%n mes(es)</numerusform></translation>
</message>
<message>
<source>1 year</source>
@ -969,7 +969,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Presets</source>
<translation>Predeterminado</translation>
<translation>Programar</translation>
</message>
<message>
<source>Notes:</source>
@ -1098,7 +1098,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Custom icon already exists</source>
<translation type="unfinished"/>
<translation>El icono personalizado ya existe</translation>
</message>
</context>
<context>
@ -1352,7 +1352,7 @@ Esta migración es en único sentido. No podrá abrir la base de datos importada
</message>
<message>
<source>Existing single-instance lock file is invalid. Launching new instance.</source>
<translation type="unfinished"/>
<translation>El archivo de bloqueo de instancia única existente no es válido. Lanzando nueva instancia.</translation>
</message>
</context>
<context>
@ -1483,7 +1483,7 @@ Esta migración es en único sentido. No podrá abrir la base de datos importada
</message>
<message>
<source>Timed one-time password</source>
<translation>Contraseña programada de único uso (TOTP)</translation>
<translation>Contraseña programada de único uso</translation>
</message>
<message>
<source>Copy &amp;TOTP</source>

View File

@ -29,27 +29,27 @@
</message>
<message>
<source>Revision: %1</source>
<translation>Revision: %1</translation>
<translation>Révision : %1</translation>
</message>
<message>
<source>Libraries:</source>
<translation>Bibliothèques:</translation>
<translation>Bibliothèques :</translation>
</message>
<message>
<source>Operating system: %1
CPU architecture: %2
Kernel: %3 %4</source>
<translation>Système d&apos;exploitation: %1
Architecture CPU: %2
Kernel: %3 %4</translation>
<translation>Système d&apos;exploitation : %1
Architecture CPU : %2
Kernel : %3 %4</translation>
</message>
<message>
<source>Enabled extensions:</source>
<translation>Extensions activées:</translation>
<translation>Extensions activées :</translation>
</message>
<message>
<source>Report bugs at: &lt;a href=&quot;https://github.com/keepassxreboot/keepassxc/issues&quot; style=&quot;text-decoration: underline;&quot;&gt;https://github.com&lt;/a&gt;</source>
<translation>Signaler les bugs sur: &lt;a href=&quot;https://github.com/keepassxreboot/keepassxc/issues&quot; style=&quot;text-decoration: underline;&quot;&gt;https://github.com&lt;/a&gt;</translation>
<translation>Signaler les bugs sur : &lt;a href=&quot;https://github.com/keepassxreboot/keepassxc/issues&quot; style=&quot;text-decoration: underline;&quot;&gt;https://github.com&lt;/a&gt;</translation>
</message>
<message>
<source>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</source>
@ -57,7 +57,7 @@ Kernel: %3 %4</translation>
</message>
<message>
<source>Project Maintainers:</source>
<translation>Mainteneurs du projet:</translation>
<translation>Mainteneurs du projet :</translation>
</message>
<message>
<source>&lt;a href=&quot;https://github.com/keepassxreboot/keepassxc/graphs/contributors&quot;&gt;See Contributions on GitHub&lt;/a&gt;</source>
@ -65,11 +65,11 @@ Kernel: %3 %4</translation>
</message>
<message>
<source>Include the following information whenever you report a bug:</source>
<translation>Inclure l&apos;information suivante lorsque vous signaler un bug:</translation>
<translation>Inclure l&apos;information suivante lorsque vous signaler un bug :</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
<translation>Distribution : %1</translation>
</message>
</context>
<context>
@ -302,7 +302,7 @@ Veuillez sélectionner si vous souhaitez autoriser laccès.</translation>
</message>
<message>
<source>Original data: </source>
<translation>Données originales:</translation>
<translation>Données originales :</translation>
</message>
<message>
<source>Error(s) detected in CSV file !</source>
@ -397,7 +397,7 @@ Veuillez sélectionner si vous souhaitez autoriser laccès.</translation>
</message>
<message>
<source>Challenge Response:</source>
<translation>Challenge-réponse:</translation>
<translation>Challenge-réponse :</translation>
</message>
</context>
<context>
@ -477,15 +477,15 @@ Vous pouvez maintenant la sauvegarder.</translation>
</message>
<message>
<source>AES: 256 Bit (default)</source>
<translation>AES: 256 Bits (par défault)</translation>
<translation>AES : 256 Bits (par défaut)</translation>
</message>
<message>
<source>Twofish: 256 Bit</source>
<translation>Twofish: 256 bits</translation>
<translation>Twofish : 256 bits</translation>
</message>
<message>
<source>Algorithm:</source>
<translation>Algorithme:</translation>
<translation>Algorithme :</translation>
</message>
</context>
<context>
@ -737,7 +737,7 @@ Voulez vous l&apos;ouvrir quand même ?</translation>
</message>
<message>
<source>Could not open the new database file while attempting to autoreload this database.</source>
<translation>La nouvelle base de données ne peux être ouverte pendant qu&apos;un rafraîchissement automatique de l&apos;actuelle est en cours.</translation>
<translation>La nouvelle base de données ne peut être ouverte pendant qu&apos;un rafraîchissement automatique de l&apos;actuelle est en cours.</translation>
</message>
<message>
<source>Empty recycle bin?</source>
@ -1095,11 +1095,11 @@ Voulez vous l&apos;ouvrir quand même ?</translation>
</message>
<message>
<source>Hint: You can enable Google as a fallback under Tools&gt;Settings&gt;Security</source>
<translation>Astuce: Vous pouvez activer Google en tant que repli sous Outils&gt;Paramètres&gt;Sécurité</translation>
<translation>Astuce : Vous pouvez activer Google en tant que repli sous Outils&gt;Paramètres&gt;Sécurité</translation>
</message>
<message>
<source>Custom icon already exists</source>
<translation type="unfinished"/>
<translation>L&apos;icône personnalisée existe déjà</translation>
</message>
</context>
<context>
@ -1175,7 +1175,7 @@ Voulez vous l&apos;ouvrir quand même ?</translation>
<message>
<source>Ref: </source>
<comment>Reference abbreviation</comment>
<translation>Réf:</translation>
<translation>Réf : </translation>
</message>
</context>
<context>
@ -1353,7 +1353,7 @@ Il s&apos;agit d&apos;une migration à sens unique. Vous ne pourrez pas ouvrir l
</message>
<message>
<source>Existing single-instance lock file is invalid. Launching new instance.</source>
<translation type="unfinished"/>
<translation>Le fichier de verrouillage de linstance unique existant nest pas valide. Lancement d&apos;une nouvelle instance.</translation>
</message>
</context>
<context>
@ -1647,11 +1647,11 @@ Il s&apos;agit d&apos;une migration à sens unique. Vous ne pourrez pas ouvrir l
</message>
<message>
<source>HTTP Port:</source>
<translation>Port HTTP:</translation>
<translation>Port HTTP :</translation>
</message>
<message>
<source>Default port: 19455</source>
<translation>Port par défaut: 19455</translation>
<translation>Port par défaut : 19455</translation>
</message>
<message>
<source>Re&amp;quest to unlock the database if it is locked</source>
@ -1681,7 +1681,7 @@ Restauration du port 19455 par défaut.</translation>
</message>
<message>
<source>&amp;Return advanced string fields which start with &quot;KPH: &quot;</source>
<translation>&amp; Retourne les champs avancés de type chaîne qui commencent par &quot;KPH:&quot;</translation>
<translation>&amp; Retourne les champs avancés de type chaîne qui commencent par &quot;KPH :&quot;</translation>
</message>
<message>
<source>Automatically creating or updating string fields is not supported.</source>
@ -1774,7 +1774,7 @@ Ne les changez que si vous savez ce que vous faites.</translation>
</message>
<message>
<source>&amp;Length:</source>
<translation>&amp;Longueur:</translation>
<translation>&amp;Longueur :</translation>
</message>
<message>
<source>Pick characters from every group</source>
@ -1794,11 +1794,11 @@ Ne les changez que si vous savez ce que vous faites.</translation>
</message>
<message>
<source>Entropy: %1 bit</source>
<translation>Entropie: %1 bit</translation>
<translation>Entropie : %1 bit</translation>
</message>
<message>
<source>Password Quality: %1</source>
<translation>Qualité du mot de passe: %1</translation>
<translation>Qualité du mot de passe : %1</translation>
</message>
<message>
<source>Poor</source>
@ -1830,15 +1830,15 @@ Ne les changez que si vous savez ce que vous faites.</translation>
</message>
<message>
<source>Wordlist:</source>
<translation>Liste de mots:</translation>
<translation>Liste de mots :</translation>
</message>
<message>
<source>Word Count:</source>
<translation>Nombre de mots:</translation>
<translation>Nombre de mots :</translation>
</message>
<message>
<source>Word Separator:</source>
<translation>Séparateur de mot:</translation>
<translation>Séparateur de mot :</translation>
</message>
<message>
<source>Copy</source>
@ -2019,7 +2019,7 @@ Veuillez déverrouiller la base de données sélectionnée ou en choisir une qui
</message>
<message>
<source>KeePassXC: New key association request</source>
<translation>KeePassXC: nouvelle demande d&apos;association</translation>
<translation>KeePassXC : nouvelle demande d&apos;association</translation>
</message>
<message>
<source>You have received an association request for the above key.
@ -2031,23 +2031,23 @@ attribuez lui un nom unique pour l&apos;identifier et acceptez la.</translation>
</message>
<message>
<source>KeePassXC: Overwrite existing key?</source>
<translation>KeePassXC: Écraser la clé existante ?</translation>
<translation>KeePassXC : Écraser la clé existante ?</translation>
</message>
<message>
<source>KeePassXC: Update Entry</source>
<translation>KeePassXC: Mettre à jour l&apos;entrée</translation>
<translation>KeePassXC : Mettre à jour l&apos;entrée</translation>
</message>
<message>
<source>KeePassXC: Database locked!</source>
<translation>KeePassXC: Base de données verrouillée !</translation>
<translation>KeePassXC : Base de données verrouillée !</translation>
</message>
<message>
<source>KeePassXC: Removed keys from database</source>
<translation>KeePassXC: Les clés ont é effacées de la base de donnée</translation>
<translation>KeePassXC : Les clés ont é effacées de la base de donnée</translation>
</message>
<message>
<source>KeePassXC: No keys found</source>
<translation>KeePassXC: Aucune clé trouvée</translation>
<translation>KeePassXC : Aucune clé trouvée</translation>
</message>
<message>
<source>KeePassXC: Settings not available!</source>
@ -2055,11 +2055,11 @@ attribuez lui un nom unique pour l&apos;identifier et acceptez la.</translation>
</message>
<message>
<source>KeePassXC: Removed permissions</source>
<translation>KeePassXC: Permissions retirées</translation>
<translation>KeePassXC : Permissions retirées</translation>
</message>
<message>
<source>KeePassXC: No entry with permissions found!</source>
<translation>KeePassXC: Aucune entrée avec permissions trouvée !</translation>
<translation>KeePassXC : Aucune entrée avec permissions trouvée !</translation>
</message>
</context>
<context>
@ -2145,7 +2145,7 @@ attribuez lui un nom unique pour l&apos;identifier et acceptez la.</translation>
</message>
<message>
<source>Don&apos;t mark database as modified for non-data changes (e.g., expanding groups)</source>
<translation>Ne pas indiquer la base de données comme modifiée pour les changements hors-données (par exemple: groupes développés)</translation>
<translation>Ne pas indiquer la base de données comme modifiée pour les changements hors-données (par exemple : groupes développés)</translation>
</message>
<message>
<source>Auto-Type</source>
@ -2227,7 +2227,7 @@ attribuez lui un nom unique pour l&apos;identifier et acceptez la.</translation>
</message>
<message>
<source>Key:</source>
<translation>Clé:</translation>
<translation>Clé :</translation>
</message>
<message>
<source>Use custom settings</source>
@ -2235,11 +2235,11 @@ attribuez lui un nom unique pour l&apos;identifier et acceptez la.</translation>
</message>
<message>
<source>Note: Change these settings only if you know what you are doing.</source>
<translation>Attention: modifiez ces paramètres seulement si vous savez ce que vous faites.</translation>
<translation>Attention : modifiez ces paramètres seulement si vous savez ce que vous faites.</translation>
</message>
<message>
<source>Time step:</source>
<translation>Période de temps:</translation>
<translation>Période de temps :</translation>
</message>
<message>
<source>8 digits</source>
@ -2251,7 +2251,7 @@ attribuez lui un nom unique pour l&apos;identifier et acceptez la.</translation>
</message>
<message>
<source>Code size:</source>
<translation>Taille du code:</translation>
<translation>Taille du code :</translation>
</message>
<message>
<source> sec</source>
@ -2375,7 +2375,7 @@ attribuez lui un nom unique pour l&apos;identifier et acceptez la.</translation>
</message>
<message>
<source>Path of the group to list. Default is /</source>
<translation>Chemin du groupe à lister. Par défaut: /</translation>
<translation>Chemin du groupe à lister. Par défaut : /</translation>
</message>
<message>
<source>Print the UUIDs of the entries and groups.</source>

View File

@ -69,7 +69,7 @@ Kernel: %3 %4</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
<translation>Distribusi: %1</translation>
</message>
</context>
<context>

View File

@ -131,7 +131,7 @@ Seleziona se vuoi consentire l&apos;accesso.</translation>
</message>
<message>
<source>Auto-Type - KeePassXC</source>
<translation> KeePassXC - Auto completamento</translation>
<translation>KeePassXC - Auto completamento</translation>
</message>
</context>
<context>
@ -170,7 +170,7 @@ Seleziona se vuoi consentire l&apos;accesso.</translation>
</message>
<message>
<source>Unable to create Key File : </source>
<translation>Impossibile creare file chiave:</translation>
<translation>Impossibile creare file chiave: </translation>
</message>
<message>
<source>Select a key file</source>
@ -290,11 +290,11 @@ Seleziona se vuoi consentire l&apos;accesso.</translation>
</message>
<message>
<source>Empty fieldname </source>
<translation>Nome campo vuoto</translation>
<translation>Nome campo vuoto </translation>
</message>
<message>
<source>column </source>
<translation>colonna</translation>
<translation>colonna </translation>
</message>
<message>
<source>Imported from CSV file</source>
@ -302,7 +302,7 @@ Seleziona se vuoi consentire l&apos;accesso.</translation>
</message>
<message>
<source>Original data: </source>
<translation>Dati originali:</translation>
<translation>Dati originali: </translation>
</message>
<message>
<source>Error(s) detected in CSV file !</source>
@ -1917,23 +1917,23 @@ Modificale solo se sai quello che stai facendo.</translation>
<name>QtIOCompressor</name>
<message>
<source>Internal zlib error when compressing: </source>
<translation>Errore interno di zlib durante la compressione:</translation>
<translation>Errore interno di zlib durante la compressione: </translation>
</message>
<message>
<source>Error writing to underlying device: </source>
<translation>Errore durante la scrittura nel dispositivo:</translation>
<translation>Errore durante la scrittura nel dispositivo: </translation>
</message>
<message>
<source>Error opening underlying device: </source>
<translation>Errore durante l&apos;apertura dal dispositivo:</translation>
<translation>Errore durante l&apos;apertura dal dispositivo: </translation>
</message>
<message>
<source>Error reading data from underlying device: </source>
<translation>Errore durante la lettura dal dispositivo:</translation>
<translation>Errore durante la lettura dal dispositivo: </translation>
</message>
<message>
<source>Internal zlib error when decompressing: </source>
<translation>Errore interno di zlib durante la decompressione:</translation>
<translation>Errore interno di zlib durante la decompressione: </translation>
</message>
</context>
<context>
@ -1944,7 +1944,7 @@ Modificale solo se sai quello che stai facendo.</translation>
</message>
<message>
<source>Internal zlib error: </source>
<translation>Errore interno di zlib:</translation>
<translation>Errore interno di zlib: </translation>
</message>
</context>
<context>
@ -2018,7 +2018,7 @@ Sblocca il database selezionato o scegli un altro database sbloccato.</translati
</message>
<message>
<source>KeePassXC: New key association request</source>
<translation>KeePassXC: Nuova richiesta di associazione chiave </translation>
<translation>KeePassXC: Nuova richiesta di associazione chiave</translation>
</message>
<message>
<source>You have received an association request for the above key.
@ -2164,7 +2164,7 @@ imposta un nome unico per identificarla ed accettarla.</translation>
</message>
<message>
<source> ms</source>
<translation>ms</translation>
<translation> ms</translation>
</message>
<message>
<source>Start only a single instance of KeePassXC</source>
@ -2179,7 +2179,7 @@ imposta un nome unico per identificarla ed accettarla.</translation>
</message>
<message>
<source> sec</source>
<translation>sec</translation>
<translation> sec</translation>
</message>
<message>
<source>Lock databases after inactivity of</source>
@ -2254,7 +2254,7 @@ imposta un nome unico per identificarla ed accettarla.</translation>
</message>
<message>
<source> sec</source>
<translation>sec</translation>
<translation> sec</translation>
</message>
</context>
<context>

View File

@ -69,7 +69,7 @@ CPU アーキテクチャ: %2
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
<translation>: %1</translation>
</message>
</context>
<context>

View File

@ -69,7 +69,7 @@ CPU 아키텍처: %2
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
<translation>: %1</translation>
</message>
</context>
<context>
@ -1096,7 +1096,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Custom icon already exists</source>
<translation type="unfinished"/>
<translation> </translation>
</message>
</context>
<context>
@ -1350,7 +1350,7 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
</message>
<message>
<source>Existing single-instance lock file is invalid. Launching new instance.</source>
<translation type="unfinished"/>
<translation> . .</translation>
</message>
</context>
<context>

View File

@ -69,7 +69,7 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
<translation>Дистрибутив: %1</translation>
</message>
</context>
<context>
@ -219,7 +219,7 @@ Please select whether you want to allow access.</source>
</message>
<message>
<source>Replace username and password with references</source>
<translation>Заменить имя пользователя и пароль к ссылкам</translation>
<translation>Использовать ссылки для имени пользователя и пароля</translation>
</message>
<message>
<source>Copy history</source>
@ -227,7 +227,7 @@ Please select whether you want to allow access.</source>
</message>
<message>
<source>Append &apos; - Clone&apos; to title</source>
<translation>Добавить&apos; - Клонировать&apos; в заголовок</translation>
<translation>Добавить к названию « - клон»</translation>
</message>
</context>
<context>
@ -579,7 +579,7 @@ Otherwise your changes are lost.</source>
<message>
<source>&quot;%1&quot; is in edit mode.
Discard changes and close anyway?</source>
<translation>«%1» в режиме редактирования.
<translation>«%1» в режиме правки.
Отменить изменения и всё равно закрыть?</translation>
</message>
<message>
@ -741,7 +741,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Empty recycle bin?</source>
<translation>Корзина пустая?</translation>
<translation>Очистить корзину?</translation>
</message>
<message>
<source>Are you sure you want to permanently delete everything from your recycle bin?</source>
@ -784,7 +784,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Edit entry</source>
<translation>Редактировать запись</translation>
<translation>Править запись</translation>
</message>
<message>
<source>Different passwords supplied.</source>
@ -838,7 +838,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>[PROTECTED] Press reveal to view or edit</source>
<translation>[Защищён] Нажмите для открытия просмотра или редактирования</translation>
<translation>[Защищён] Нажмите для открытия просмотра или правки</translation>
</message>
<message>
<source>Are you sure you want to remove this attachment?</source>
@ -853,7 +853,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Add</source>
<translation>Добавить</translation>
<translation>Создать</translation>
</message>
<message>
<source>Remove</source>
@ -946,7 +946,7 @@ Do you want to open it anyway?</source>
<name>EditEntryWidgetMain</name>
<message>
<source>Title:</source>
<translation>Заголовок:</translation>
<translation>Название:</translation>
</message>
<message>
<source>Username:</source>
@ -997,7 +997,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Edit group</source>
<translation>Редактировать группу</translation>
<translation>Править группу</translation>
</message>
<message>
<source>Enable</source>
@ -1095,22 +1095,22 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Hint: You can enable Google as a fallback under Tools&gt;Settings&gt;Security</source>
<translation>Подсказка: вы можете включить Google в качестве резервного копирования в меню «Инструменты»&gt; «Настройки»&gt; «Безопасность»</translation>
<translation>Подсказка: в качестве резервного варианта для получения значков сайтов возможно использовать Google. Включите этот параметр в меню «Инструменты» -&gt; «Настройки» -&gt; «Безопасность»</translation>
</message>
<message>
<source>Custom icon already exists</source>
<translation type="unfinished"/>
<translation>Пользовательский значок уже существует</translation>
</message>
</context>
<context>
<name>EditWidgetProperties</name>
<message>
<source>Created:</source>
<translation>Создано:</translation>
<translation>Создание:</translation>
</message>
<message>
<source>Modified:</source>
<translation>Изменено:</translation>
<translation>Изменение:</translation>
</message>
<message>
<source>Accessed:</source>
@ -1125,7 +1125,7 @@ Do you want to open it anyway?</source>
<name>Entry</name>
<message>
<source> - Clone</source>
<translation> - Клонировать</translation>
<translation> - клон</translation>
</message>
</context>
<context>
@ -1143,7 +1143,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Title</source>
<translation>Заголовок</translation>
<translation>Имя записи</translation>
</message>
<message>
<source>Username</source>
@ -1162,7 +1162,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Title</source>
<translation>Заголовок</translation>
<translation>Имя записи</translation>
</message>
<message>
<source>Username</source>
@ -1353,7 +1353,7 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
</message>
<message>
<source>Existing single-instance lock file is invalid. Launching new instance.</source>
<translation type="unfinished"/>
<translation>Запускается новый экземпляр программы, т.к. файл блокировки запуска повреждён.</translation>
</message>
</context>
<context>
@ -1452,11 +1452,11 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
</message>
<message>
<source>&amp;Add new entry</source>
<translation>&amp;Добавить новую запись</translation>
<translation>&amp;Создать запись</translation>
</message>
<message>
<source>&amp;View/Edit entry</source>
<translation>&amp;Посмотреть/редактировать запись</translation>
<translation>&amp;Открыть/править запись</translation>
</message>
<message>
<source>&amp;Delete entry</source>
@ -1464,11 +1464,11 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
</message>
<message>
<source>&amp;Add new group</source>
<translation>&amp;Добавить новую группу</translation>
<translation>&amp;Создать группу</translation>
</message>
<message>
<source>&amp;Edit group</source>
<translation>&amp;Редактировать группу</translation>
<translation>&amp;Править группу</translation>
</message>
<message>
<source>&amp;Delete group</source>
@ -1524,7 +1524,7 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
</message>
<message>
<source>&amp;Title</source>
<translation>&amp;Заголовок</translation>
<translation>&amp;Имя записи</translation>
</message>
<message>
<source>&amp;URL</source>
@ -1556,7 +1556,7 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
</message>
<message>
<source>Empty recycle bin</source>
<translation>Корзина пустая</translation>
<translation>Очистить корзину</translation>
</message>
<message>
<source>Access error for config file %1</source>
@ -1701,15 +1701,15 @@ Using default port 19455.</source>
</message>
<message>
<source>&amp;Return only best matching entries</source>
<translation>&amp;Возврат только наиболее совпадающих записей</translation>
<translation>&amp;Показывать только лучшие совпадения</translation>
</message>
<message>
<source>Only entries with the same scheme (http://, https://, ftp://, ...) are returned.</source>
<translation>Возвращаются только записи с той же схемой (http: //, https: //, ftp: //, ...).</translation>
<translation>Будут отобраны только записи с совпадающим протоколом (http://, https://, ftp://, ...).</translation>
</message>
<message>
<source>&amp;Match URL schemes</source>
<translation>&amp;Совпадения схем адресов</translation>
<translation>&amp;Проверять протокол</translation>
</message>
<message>
<source>Password Generator</source>
@ -1879,7 +1879,7 @@ Change them only if you know what you are doing.</source>
</message>
<message>
<source>Title</source>
<translation>Заголовок</translation>
<translation>Имя записи</translation>
</message>
<message>
<source>Username</source>
@ -2085,7 +2085,7 @@ give it a unique name to identify and accept it.</source>
<name>SettingsWidgetGeneral</name>
<message>
<source>Remember last databases</source>
<translation>Помнить последнюю базу данных</translation>
<translation>Запоминать последнюю базу данных</translation>
</message>
<message>
<source>Automatically save on exit</source>
@ -2117,7 +2117,7 @@ give it a unique name to identify and accept it.</source>
</message>
<message>
<source>Hide window to system tray when minimized</source>
<translation>При сворачивании прятать окно в системный лоток</translation>
<translation>При сворачивании скрывать окно в системный лоток</translation>
</message>
<message>
<source>Load previous databases on startup</source>
@ -2129,7 +2129,7 @@ give it a unique name to identify and accept it.</source>
</message>
<message>
<source>Hide window to system tray instead of app exit</source>
<translation>Прятать окно в системный лоток вместо выхода</translation>
<translation>Скрывать окно в системный лоток вместо выхода</translation>
</message>
<message>
<source>Minimize window at application startup</source>
@ -2141,11 +2141,11 @@ give it a unique name to identify and accept it.</source>
</message>
<message>
<source>Remember last key files and security dongles</source>
<translation>Помнить последние ключевые файлы и ключи безопасности</translation>
<translation>Запоминать последние использованные файлы ключей и устройства</translation>
</message>
<message>
<source>Don&apos;t mark database as modified for non-data changes (e.g., expanding groups)</source>
<translation>Не помечать базу данных как измененную без изменения данных (например, для расширения групп)</translation>
<translation>Не помечать базу данных изменённой при действиях, не связанных с изменением данных (например, при распахивании групп)</translation>
</message>
<message>
<source>Auto-Type</source>
@ -2153,7 +2153,7 @@ give it a unique name to identify and accept it.</source>
</message>
<message>
<source>Use entry title and URL to match windows for global Auto-Type</source>
<translation>Использовать URL и заголовок записи при сопоставлении окон для глобального автоввода</translation>
<translation>Использовать для поиска URL и название записи</translation>
</message>
<message>
<source>Always ask before performing Auto-Type</source>
@ -2184,7 +2184,7 @@ give it a unique name to identify and accept it.</source>
</message>
<message>
<source>Lock databases after inactivity of</source>
<translation>Заблокировать базу данных после неактивности длительностью</translation>
<translation>Блокировать базу данных при отсутствии активности длительностью</translation>
</message>
<message>
<source>Show passwords in cleartext by default</source>
@ -2192,7 +2192,7 @@ give it a unique name to identify and accept it.</source>
</message>
<message>
<source>Lock databases after minimizing the window</source>
<translation>Блокировать базу данных после сворачивания окна</translation>
<translation>Блокировать базу данных при сворачивания окна</translation>
</message>
<message>
<source>Don&apos;t require password repeat when it is visible</source>
@ -2216,7 +2216,7 @@ give it a unique name to identify and accept it.</source>
</message>
<message>
<source>Use Google as fallback for downloading website icons</source>
<translation>Использовать Google как резерв для загрузки значков веб-сайтов</translation>
<translation>Использовать Google в качестве резервного варианта для получения значков веб-сайтов</translation>
</message>
</context>
<context>

View File

@ -69,7 +69,7 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
<translation>%1</translation>
</message>
</context>
<context>
@ -196,7 +196,7 @@ Please select whether you want to allow access.</source>
</message>
<message>
<source>Cha&amp;llenge Response</source>
<translation type="unfinished"/>
<translation></translation>
</message>
<message>
<source>Refresh</source>
@ -208,7 +208,7 @@ Please select whether you want to allow access.</source>
</message>
<message>
<source>Changing master key failed: no YubiKey inserted.</source>
<translation type="unfinished"/>
<translation> YubiKey</translation>
</message>
</context>
<context>
@ -397,7 +397,7 @@ Please select whether you want to allow access.</source>
</message>
<message>
<source>Challenge Response:</source>
<translation type="unfinished"/>
<translation></translation>
</message>
</context>
<context>
@ -1098,7 +1098,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Custom icon already exists</source>
<translation type="unfinished"/>
<translation></translation>
</message>
</context>
<context>
@ -2140,7 +2140,7 @@ give it a unique name to identify and accept it.</source>
</message>
<message>
<source>Remember last key files and security dongles</source>
<translation></translation>
<translation></translation>
</message>
<message>
<source>Don&apos;t mark database as modified for non-data changes (e.g., expanding groups)</source>

View File

@ -1,5 +1,5 @@
name: keepassxc
version: 2.2.2
version: 2.2.4
grade: stable
summary: Community-driven port of the Windows application “KeePass Password Safe”
description: |
@ -12,7 +12,7 @@ apps:
keepassxc:
command: desktop-launch keepassxc
plugs: [unity7, x11, opengl, gsettings, home, network, network-bind, removable-media, raw-usb]
desktop: usr/share/applications/org.keepassxc.desktop
desktop: usr/share/applications/org.keepassxc.KeePassXC.desktop
cli:
command: keepassxc-cli
plugs: [gsettings, home, removable-media, raw-usb]
@ -41,8 +41,10 @@ parts:
- libxtst-dev
- libyubikey-dev
- libykpers-1-dev
stage-packages:
- dbus
install: |
sed -i 's|Icon=keepassxc|Icon=${SNAP}/usr/share/icons/hicolor/256x256/apps/keepassxc.png|g' $SNAPCRAFT_PART_INSTALL/usr/share/applications/org.keepassxc.desktop
sed -i 's|Icon=keepassxc|Icon=${SNAP}/usr/share/icons/hicolor/256x256/apps/keepassxc.png|g' $SNAPCRAFT_PART_INSTALL/usr/share/applications/org.keepassxc.KeePassXC.desktop
after: [desktop-qt5]
# Redefine desktop-qt5 stage packages to work with Ubuntu 17.04
@ -52,19 +54,12 @@ parts:
- ttf-ubuntu-font-family
- dmz-cursor-theme
- light-themes
- adwaita-icon-theme
- gnome-themes-standard
- shared-mime-info
- libqt5gui5
- libgdk-pixbuf2.0-0
- libqt5svg5 # for loading icon themes which are svg
- locales-all
- xdg-user-dirs
# Overcome limitation in snapd to support URL loading (CTRL+U)
# client needs to install "snapd-xdg-open" on their system
snapd-xdg-open:
source: https://github.com/ubuntu-core/snapd-xdg-open.git
source-depth: 1
plugin: nil
install: |
install -D -t $SNAPCRAFT_PART_INSTALL/usr/bin/ data/xdg-open
stage-packages:
- dbus

View File

@ -105,6 +105,7 @@ set(keepassx_SOURCES
gui/EditWidgetIcons.cpp
gui/EditWidgetProperties.cpp
gui/FileDialog.cpp
gui/Font.cpp
gui/IconModels.cpp
gui/KeePass1OpenWidget.cpp
gui/KMessageWidget.cpp
@ -155,6 +156,8 @@ if(APPLE)
set(keepassx_SOURCES ${keepassx_SOURCES}
core/ScreenLockListenerMac.h
core/ScreenLockListenerMac.cpp
core/MacPasteboard.h
core/MacPasteboard.cpp
)
endif()
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
@ -177,6 +180,7 @@ set(keepassx_SOURCES_MAINEXE
add_feature_info(AutoType WITH_XC_AUTOTYPE "Automatic password typing")
add_feature_info(KeePassHTTP WITH_XC_HTTP "Browser integration compatible with ChromeIPass and PassIFox")
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent")
add_subdirectory(http)
if(WITH_XC_HTTP)
@ -186,6 +190,11 @@ endif()
add_subdirectory(autotype)
add_subdirectory(cli)
add_subdirectory(sshagent)
if(WITH_XC_SSHAGENT)
set(sshagent_LIB sshagent)
endif()
set(autotype_SOURCES
core/Tools.cpp
autotype/AutoType.cpp
@ -222,6 +231,7 @@ set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUIL
target_link_libraries(keepassx_core
${keepasshttp_LIB}
${autotype_LIB}
${sshagent_LIB}
${YUBIKEY_LIBRARIES}
${ZXCVBN_LIBRARIES}
Qt5::Core

View File

@ -256,6 +256,13 @@ void AutoType::resetInAutoType()
m_inAutoType = false;
}
void AutoType::raiseWindow()
{
#if defined(Q_OS_MAC)
m_plugin->raiseOwnWindow();
#endif
}
void AutoType::unloadPlugin()
{
if (m_executor) {

View File

@ -51,6 +51,7 @@ public:
public slots:
void performGlobalAutoType(const QList<Database*>& dbList);
void raiseWindow();
signals:
void globalShortcutTriggered();

View File

@ -22,8 +22,8 @@ set(cli_SOURCES
Command.h
Edit.cpp
Edit.h
EntropyMeter.cpp
EntropyMeter.h
Estimate.cpp
Estimate.h
Extract.cpp
Extract.h
List.cpp

View File

@ -55,9 +55,7 @@ int Clip::execute(QStringList arguments)
parser.addOption(keyFile);
parser.addPositionalArgument("entry", QObject::tr("Path of the entry to clip."));
parser.addPositionalArgument(
"timeout",
QObject::tr("Timeout in seconds before clearing the clipboard."),
QString("[timeout]"));
"timeout", QObject::tr("Timeout in seconds before clearing the clipboard."), QString("[timeout]"));
parser.process(arguments);
const QStringList args = parser.positionalArguments();

View File

@ -23,9 +23,9 @@
#include "Command.h"
#include "Add.h"
#include "Edit.h"
#include "Clip.h"
#include "EntropyMeter.h"
#include "Edit.h"
#include "Estimate.h"
#include "Extract.h"
#include "List.h"
#include "Locate.h"
@ -62,7 +62,7 @@ void populateCommands()
commands.insert(QString("add"), new Add());
commands.insert(QString("clip"), new Clip());
commands.insert(QString("edit"), new Edit());
commands.insert(QString("entropy-meter"), new EntropyMeter());
commands.insert(QString("estimate"), new Estimate());
commands.insert(QString("extract"), new Extract());
commands.insert(QString("locate"), new Locate());
commands.insert(QString("ls"), new List());

View File

@ -1,141 +0,0 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* 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 or (at your option)
* version 3 of the License.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "EntropyMeter.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zxcvbn.h>
/* For pre-compiled headers under windows */
#ifdef _WIN32
#ifndef __MINGW32__
#include "stdafx.h"
#endif
#endif
EntropyMeter::EntropyMeter()
{
this->name = QString("entropy-meter");
this->description = QObject::tr("Calculate password entropy.");
}
EntropyMeter::~EntropyMeter()
{
}
static void calculate(const char *pwd, int advanced)
{
double e;
int len = strlen(pwd);
if (advanced == 0){
e = ZxcvbnMatch(pwd, 0, 0);
printf("Pass '%s' \tLength %d\tEntropy %.3f\tLog10 %.3f\n", pwd, len, e, e * 0.301029996);
} else {
int ChkLen;
ZxcMatch_t *info, *p;
double m = 0.0;
e = ZxcvbnMatch(pwd, 0, &info);
for(p = info; p; p = p->Next) {
m += p->Entrpy;
}
m = e - m;
printf("Pass '%s' \tLength %d\tEntropy %.3f\tLog10 %.3f\n Multi-word extra bits %.1f\n", pwd, len, e, e * 0.301029996, m);
p = info;
ChkLen = 0;
while(p) {
int n;
switch(static_cast<int>(p->Type))
{
case BRUTE_MATCH: printf(" Type: Bruteforce "); break;
case DICTIONARY_MATCH: printf(" Type: Dictionary "); break;
case DICT_LEET_MATCH: printf(" Type: Dict+Leet "); break;
case USER_MATCH: printf(" Type: User Words "); break;
case USER_LEET_MATCH: printf(" Type: User+Leet "); break;
case REPEATS_MATCH: printf(" Type: Repeated "); break;
case SEQUENCE_MATCH: printf(" Type: Sequence "); break;
case SPATIAL_MATCH: printf(" Type: Spatial "); break;
case DATE_MATCH: printf(" Type: Date "); break;
case BRUTE_MATCH+MULTIPLE_MATCH: printf(" Type: Bruteforce(Rep)"); break;
case DICTIONARY_MATCH+MULTIPLE_MATCH: printf(" Type: Dictionary(Rep)"); break;
case DICT_LEET_MATCH+MULTIPLE_MATCH: printf(" Type: Dict+Leet(Rep) "); break;
case USER_MATCH+MULTIPLE_MATCH: printf(" Type: User Words(Rep)"); break;
case USER_LEET_MATCH+MULTIPLE_MATCH: printf(" Type: User+Leet(Rep) "); break;
case REPEATS_MATCH+MULTIPLE_MATCH: printf(" Type: Repeated(Rep) "); break;
case SEQUENCE_MATCH+MULTIPLE_MATCH: printf(" Type: Sequence(Rep) "); break;
case SPATIAL_MATCH+MULTIPLE_MATCH: printf(" Type: Spatial(Rep) "); break;
case DATE_MATCH+MULTIPLE_MATCH: printf(" Type: Date(Rep) "); break;
default: printf(" Type: Unknown%d ", p->Type); break;
}
ChkLen += p->Length;
printf(" Length %d Entropy %6.3f (%.2f) ", p->Length, p->Entrpy, p->Entrpy * 0.301029996);
for(n = 0; n < p->Length; ++n, ++pwd) {
printf("%c", *pwd);
}
printf("\n");
p = p->Next;
}
ZxcvbnFreeInfo(info);
if (ChkLen != len)
printf("*** Password length (%d) != sum of length of parts (%d) ***\n", len, ChkLen);
}
}
int EntropyMeter::execute(QStringList arguments)
{
printf("KeePassXC Entropy Meter, based on zxcvbn-c.\nEnter your password below or pass it as argv\n");
printf(" Usage: entropy-meter [-a] [pwd1 pwd2 ...]\n> ");
int i, advanced = 0;
if (arguments.size() > 1 && arguments.at(1) == "-a")
{
advanced = 1;
arguments.removeAt(1);
}
i = 1;
if (i >= arguments.size())
{
/* No test passwords on command line, so get them from stdin */
char line[500];
while(fgets(line, sizeof line, stdin))
{
/* Drop the trailing newline character */
for(i = 0; i < static_cast<int>(sizeof line - 1); ++i)
{
if (line[i] < ' ')
{
line[i] = 0;
break;
}
}
if (line[0]) {
calculate(line,advanced);
printf("> ");
}
}
}
else
{
/* Do the test passwords on the command line */
for(; i < arguments.size(); ++i)
{
calculate(arguments.at(i).toLatin1(), advanced);
}
}
return 0;
}

170
src/cli/Estimate.cpp Normal file
View File

@ -0,0 +1,170 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* 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 or (at your option)
* version 3 of the License.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "Estimate.h"
#include <QCommandLineParser>
#include <QTextStream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zxcvbn.h>
/* For pre-compiled headers under windows */
#ifdef _WIN32
#ifndef __MINGW32__
#include "stdafx.h"
#endif
#endif
Estimate::Estimate()
{
this->name = QString("estimate");
this->description = QObject::tr("Estimate the entropy of a password.");
}
Estimate::~Estimate()
{
}
static void estimate(const char* pwd, bool advanced)
{
double e;
int len = strlen(pwd);
if (!advanced) {
e = ZxcvbnMatch(pwd, 0, 0);
printf("Length %d\tEntropy %.3f\tLog10 %.3f\n", len, e, e * 0.301029996);
} else {
int ChkLen;
ZxcMatch_t *info, *p;
double m = 0.0;
e = ZxcvbnMatch(pwd, 0, &info);
for (p = info; p; p = p->Next) {
m += p->Entrpy;
}
m = e - m;
printf("Length %d\tEntropy %.3f\tLog10 %.3f\n Multi-word extra bits %.1f\n", len, e, e * 0.301029996, m);
p = info;
ChkLen = 0;
while (p) {
int n;
switch (static_cast<int>(p->Type)) {
case BRUTE_MATCH:
printf(" Type: Bruteforce ");
break;
case DICTIONARY_MATCH:
printf(" Type: Dictionary ");
break;
case DICT_LEET_MATCH:
printf(" Type: Dict+Leet ");
break;
case USER_MATCH:
printf(" Type: User Words ");
break;
case USER_LEET_MATCH:
printf(" Type: User+Leet ");
break;
case REPEATS_MATCH:
printf(" Type: Repeated ");
break;
case SEQUENCE_MATCH:
printf(" Type: Sequence ");
break;
case SPATIAL_MATCH:
printf(" Type: Spatial ");
break;
case DATE_MATCH:
printf(" Type: Date ");
break;
case BRUTE_MATCH + MULTIPLE_MATCH:
printf(" Type: Bruteforce(Rep)");
break;
case DICTIONARY_MATCH + MULTIPLE_MATCH:
printf(" Type: Dictionary(Rep)");
break;
case DICT_LEET_MATCH + MULTIPLE_MATCH:
printf(" Type: Dict+Leet(Rep) ");
break;
case USER_MATCH + MULTIPLE_MATCH:
printf(" Type: User Words(Rep)");
break;
case USER_LEET_MATCH + MULTIPLE_MATCH:
printf(" Type: User+Leet(Rep) ");
break;
case REPEATS_MATCH + MULTIPLE_MATCH:
printf(" Type: Repeated(Rep) ");
break;
case SEQUENCE_MATCH + MULTIPLE_MATCH:
printf(" Type: Sequence(Rep) ");
break;
case SPATIAL_MATCH + MULTIPLE_MATCH:
printf(" Type: Spatial(Rep) ");
break;
case DATE_MATCH + MULTIPLE_MATCH:
printf(" Type: Date(Rep) ");
break;
default:
printf(" Type: Unknown%d ", p->Type);
break;
}
ChkLen += p->Length;
printf(" Length %d Entropy %6.3f (%.2f) ", p->Length, p->Entrpy, p->Entrpy * 0.301029996);
for (n = 0; n < p->Length; ++n, ++pwd) {
printf("%c", *pwd);
}
printf("\n");
p = p->Next;
}
ZxcvbnFreeInfo(info);
if (ChkLen != len) {
printf("*** Password length (%d) != sum of length of parts (%d) ***\n", len, ChkLen);
}
}
}
int Estimate::execute(QStringList arguments)
{
QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
QCommandLineParser parser;
parser.setApplicationDescription(this->description);
parser.addPositionalArgument("password", QObject::tr("Password for which to estimate the entropy."), "[password]");
QCommandLineOption advancedOption(QStringList() << "a"
<< "advanced",
QObject::tr("Perform advanced analysis on the password."));
parser.addOption(advancedOption);
parser.process(arguments);
const QStringList args = parser.positionalArguments();
if (args.size() > 1) {
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli estimate");
return EXIT_FAILURE;
}
QString password;
if (args.size() == 1) {
password = args.at(0);
} else {
password = inputTextStream.readLine();
}
estimate(password.toLatin1(), parser.isSet(advancedOption));
return EXIT_SUCCESS;
}

View File

@ -15,17 +15,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_ENTROPYMETER_H
#define KEEPASSXC_ENTROPYMETER_H
#ifndef KEEPASSXC_ESTIMATE_H
#define KEEPASSXC_ESTIMATE_H
#include "Command.h"
class EntropyMeter : public Command
class Estimate : public Command
{
public:
EntropyMeter();
~EntropyMeter();
Estimate();
~Estimate();
int execute(QStringList arguments);
};
#endif // KEEPASSXC_ENTROPYMETER_H
#endif // KEEPASSXC_ESTIMATE_H

View File

@ -85,7 +85,6 @@ int Extract::execute(QStringList arguments)
compositeKey.addKey(fileKey);
}
QString databaseFilename = args.at(0);
QFile dbFile(databaseFilename);
if (!dbFile.exists()) {

View File

@ -44,8 +44,7 @@ int List::execute(QStringList arguments)
QCommandLineParser parser;
parser.setApplicationDescription(this->description);
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
parser.addPositionalArgument(
"group", QObject::tr("Path of the group to list. Default is /"), QString("[group]"));
parser.addPositionalArgument("group", QObject::tr("Path of the group to list. Default is /"), QString("[group]"));
QCommandLineOption keyFile(QStringList() << "k"
<< "key-file",
QObject::tr("Key file of the database."),

View File

@ -83,5 +83,4 @@ int Locate::locateEntry(Database* database, QString searchTerm)
outputTextStream << result << endl;
}
return EXIT_SUCCESS;
}

View File

@ -43,10 +43,9 @@ int Merge::execute(QStringList arguments)
parser.addPositionalArgument("database1", QObject::tr("Path of the database to merge into."));
parser.addPositionalArgument("database2", QObject::tr("Path of the database to merge from."));
QCommandLineOption samePasswordOption(
QStringList() << "s"
<< "same-credentials",
QObject::tr("Use the same credentials for both database files."));
QCommandLineOption samePasswordOption(QStringList() << "s"
<< "same-credentials",
QObject::tr("Use the same credentials for both database files."));
QCommandLineOption keyFile(QStringList() << "k"
<< "key-file",
@ -55,8 +54,8 @@ int Merge::execute(QStringList arguments)
parser.addOption(keyFile);
QCommandLineOption keyFileFrom(QStringList() << "f"
<< "key-file-from",
QObject::tr("Key file of the database to merge from."),
QObject::tr("path"));
QObject::tr("Key file of the database to merge from."),
QObject::tr("path"));
parser.addOption(keyFileFrom);
parser.addOption(samePasswordOption);
@ -68,7 +67,6 @@ int Merge::execute(QStringList arguments)
return EXIT_FAILURE;
}
Database* db1 = Database::unlockFromStdin(args.at(0), parser.value(keyFile));
if (db1 == nullptr) {
return EXIT_FAILURE;

View File

@ -49,6 +49,13 @@ int Show::execute(QStringList arguments)
QObject::tr("Key file of the database."),
QObject::tr("path"));
parser.addOption(keyFile);
QCommandLineOption attributes(QStringList() << "a"
<< "attributes",
QObject::tr("Names of the attributes to show. "
"This option can be specified more than once, with each attribute shown one-per-line in the given order. "
"If no attributes are specified, a summary of the default attributes is given."),
QObject::tr("attribute"));
parser.addOption(attributes);
parser.addPositionalArgument("entry", QObject::tr("Name of the entry to show."));
parser.process(arguments);
@ -63,10 +70,10 @@ int Show::execute(QStringList arguments)
return EXIT_FAILURE;
}
return this->showEntry(db, args.at(1));
return this->showEntry(db, parser.values(attributes), args.at(1));
}
int Show::showEntry(Database* database, QString entryPath)
int Show::showEntry(Database* database, QStringList attributes, QString entryPath)
{
QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
@ -78,10 +85,24 @@ int Show::showEntry(Database* database, QString entryPath)
return EXIT_FAILURE;
}
outputTextStream << " title: " << entry->title() << endl;
outputTextStream << "username: " << entry->username() << endl;
outputTextStream << "password: " << entry->password() << endl;
outputTextStream << " URL: " << entry->url() << endl;
outputTextStream << " Notes: " << entry->notes() << endl;
return EXIT_SUCCESS;
// If no attributes specified, output the default attribute set.
bool showAttributeNames = attributes.isEmpty();
if (attributes.isEmpty()) {
attributes = EntryAttributes::DefaultAttributes;
}
// Iterate over the attributes and output them line-by-line.
bool sawUnknownAttribute = false;
for (QString attribute : attributes) {
if (!entry->attributes()->contains(attribute)) {
sawUnknownAttribute = true;
qCritical("ERROR: unknown attribute '%s'.", qPrintable(attribute));
continue;
}
if (showAttributeNames) {
outputTextStream << attribute << ": ";
}
outputTextStream << entry->resolveMultiplePlaceholders(entry->attributes()->value(attribute)) << endl;
}
return sawUnknownAttribute ? EXIT_FAILURE : EXIT_SUCCESS;
}

View File

@ -26,7 +26,7 @@ public:
Show();
~Show();
int execute(QStringList arguments);
int showEntry(Database* database, QString entryPath);
int showEntry(Database* database, QStringList attributes, QString entryPath);
};
#endif // KEEPASSXC_SHOW_H

View File

@ -27,7 +27,6 @@
#include <QProcess>
#include <QTextStream>
void Utils::setStdinEcho(bool enable = true)
{
#ifdef Q_OS_WIN

View File

@ -22,8 +22,8 @@ Copies the password of a database entry to the clipboard. If multiple entries wi
.IP "edit [options] <database> <entry>"
Edits a database entry. A password can be generated (\fI-g\fP option), or a prompt can be displayed to input the password (\fI-p\fP option).
.IP "entropy-meter [-a pwd1 pwd2 ...]"
Calculates the entropy of a single, or multiple passwords specified using the \fI-a\fP option. If no passwords are specified, the program will run in interactive mode and prompt the user to enter a password.
.IP "estimate [options] [password]"
Estimates the entropy of a password. The password to estimate can be provided as a positional argument, or using the standard input.
.IP "extract [options] <database>"
Extracts and prints the contents of a database to standard output in XML format.
@ -90,6 +90,20 @@ Specify the length of the password to generate.
Specify the title of the entry.
.SS "Estimate options"
.IP "-a, --advanced"
Perform advanced analysis on the password.
.SS "Show options"
.IP "-a, --attributes <attribute>..."
Names of the attributes to show. This option can be specified more than once,
with each attribute shown one-per-line in the given order. If no attributes are
specified, a summary of the default attributes is given.
.SH REPORTING BUGS
Bugs and feature requests can be reported on GitHub at https://github.com/keepassxreboot/keepassxc/issues.

View File

@ -15,6 +15,7 @@
#cmakedefine WITH_XC_HTTP
#cmakedefine WITH_XC_AUTOTYPE
#cmakedefine WITH_XC_YUBIKEY
#cmakedefine WITH_XC_SSHAGENT
#cmakedefine KEEPASSXC_DIST
#cmakedefine KEEPASSXC_DIST_TYPE "@KEEPASSXC_DIST_TYPE@"

View File

@ -52,8 +52,9 @@ QVariant Base32::decode(const QByteArray& encodedData)
int nPads = 0;
for (int i = -1; i > -7; --i) {
if ('=' == encodedData[encodedData.size() + i])
if ('=' == encodedData[encodedData.size() + i]) {
++nPads;
}
}
int specialOffset;
@ -95,11 +96,12 @@ QVariant Base32::decode(const QByteArray& encodedData)
int nQuantumBytes = 5;
for (int n = 0; n < 8; ++n) {
quint8 ch = static_cast<quint8>(encodedData[i++]);
auto ch = static_cast<quint8>(encodedData[i++]);
if ((ASCII_A <= ch && ch <= ASCII_Z) || (ASCII_a <= ch && ch <= ASCII_z)) {
ch -= ASCII_A;
if (ch >= ALPH_POS_2)
if (ch >= ALPH_POS_2) {
ch -= ASCII_a - ASCII_A;
}
} else {
if (ASCII_2 <= ch && ch <= ASCII_7) {
ch -= ASCII_2;
@ -126,8 +128,8 @@ QVariant Base32::decode(const QByteArray& encodedData)
const int offset = (nQuantumBytes - 1) * 8;
quint64 mask = quint64(0xFF) << offset;
for (int n = offset; n >= 0 && o < nBytes; n -= 8) {
data[o++] = static_cast<char>((quantum & mask) >> n);
mask >>= 8;
data[o++] = static_cast<char>((quantum & mask) >> n);
mask >>= 8;
}
}
@ -147,7 +149,7 @@ QByteArray Base32::encode(const QByteArray& data)
const int rBits = nBits % 40; // in {0, 8, 16, 24, 32}
const int nQuanta = nBits / 40 + (rBits > 0 ? 1 : 0);
const int nBytes = nQuanta * 8;
QByteArray encodedData(nQuanta * 8, Qt::Uninitialized);
QByteArray encodedData(nBytes, Qt::Uninitialized);
int i = 0;
int o = 0;
@ -166,6 +168,7 @@ QByteArray Base32::encode(const QByteArray& data)
int index;
for (n = 35; n >= 0; n -= 5) {
index = (quantum & mask) >> n;
Q_ASSERT(0 <= index && index <= 31);
encodedData[o++] = alphabet[index];
mask >>= 5;
}
@ -173,10 +176,11 @@ QByteArray Base32::encode(const QByteArray& data)
// < 40-bits of input at final input group
if (i < data.size()) {
Q_ASSERT(rBits > 0);
Q_ASSERT(8 <= rBits && rBits <= 32);
quantum = 0;
for (n = rBits - 8; n >= 0; n -= 8)
for (n = rBits - 8; n >= 0; n -= 8) {
quantum |= static_cast<quint64>(data[i++]) << n;
}
switch (rBits) {
case 8: // expand to 10 bits
@ -195,7 +199,7 @@ QByteArray Base32::encode(const QByteArray& data)
n = 20;
break;
default: // expand to 35 bits
Q_ASSERT(rBits == 32);
Q_ASSERT(32 == rBits);
quantum <<= 3;
mask = MASK_35BIT;
n = 30;
@ -203,6 +207,7 @@ QByteArray Base32::encode(const QByteArray& data)
while (n >= 0) {
int index = (quantum & mask) >> n;
Q_ASSERT(0 <= index && index <= 31);
encodedData[o++] = alphabet[index];
mask >>= 5;
n -= 5;

View File

@ -114,7 +114,6 @@ void Config::init(const QString& fileName)
m_defaults.insert("AutoSaveAfterEveryChange", false);
m_defaults.insert("AutoReloadOnChange", true);
m_defaults.insert("AutoSaveOnExit", false);
m_defaults.insert("ShowToolbar", true);
m_defaults.insert("SearchLimitGroup", false);
m_defaults.insert("MinimizeOnCopy", false);
m_defaults.insert("UseGroupIconOnEntryCreation", false);

View File

@ -95,10 +95,15 @@ const Metadata* Database::metadata() const
Entry* Database::resolveEntry(const Uuid& uuid)
{
return recFindEntry(uuid, m_rootGroup);
return findEntryRecursive(uuid, m_rootGroup);
}
Entry* Database::recFindEntry(const Uuid& uuid, Group* group)
Entry* Database::resolveEntry(const QString& text, EntryReferenceType referenceType)
{
return findEntryRecursive(text, referenceType, m_rootGroup);
}
Entry* Database::findEntryRecursive(const Uuid& uuid, Group* group)
{
const QList<Entry*> entryList = group->entries();
for (Entry* entry : entryList) {
@ -109,7 +114,57 @@ Entry* Database::recFindEntry(const Uuid& uuid, Group* group)
const QList<Group*> children = group->children();
for (Group* child : children) {
Entry* result = recFindEntry(uuid, child);
Entry* result = findEntryRecursive(uuid, child);
if (result) {
return result;
}
}
return nullptr;
}
Entry* Database::findEntryRecursive(const QString& text, EntryReferenceType referenceType, Group* group)
{
Q_ASSERT_X(referenceType != EntryReferenceType::Unknown, "Database::findEntryRecursive",
"Can't search entry with \"referenceType\" parameter equal to \"Unknown\"");
bool found = false;
const QList<Entry*> entryList = group->entries();
for (Entry* entry : entryList) {
switch (referenceType) {
case EntryReferenceType::Unknown:
return nullptr;
case EntryReferenceType::Title:
found = entry->title() == text;
break;
case EntryReferenceType::UserName:
found = entry->username() == text;
break;
case EntryReferenceType::Password:
found = entry->password() == text;
break;
case EntryReferenceType::Url:
found = entry->url() == text;
break;
case EntryReferenceType::Notes:
found = entry->notes() == text;
break;
case EntryReferenceType::Uuid:
found = entry->uuid() == Uuid::fromHex(text);
break;
case EntryReferenceType::CustomAttributes:
found = entry->attributes()->containsValue(text);
break;
}
if (found) {
return entry;
}
}
const QList<Group*> children = group->children();
for (Group* child : children) {
Entry* result = findEntryRecursive(text, referenceType, child);
if (result) {
return result;
}
@ -120,10 +175,10 @@ Entry* Database::recFindEntry(const Uuid& uuid, Group* group)
Group* Database::resolveGroup(const Uuid& uuid)
{
return recFindGroup(uuid, m_rootGroup);
return findGroupRecursive(uuid, m_rootGroup);
}
Group* Database::recFindGroup(const Uuid& uuid, Group* group)
Group* Database::findGroupRecursive(const Uuid& uuid, Group* group)
{
if (group->uuid() == uuid) {
return group;
@ -131,7 +186,7 @@ Group* Database::recFindGroup(const Uuid& uuid, Group* group)
const QList<Group*> children = group->children();
for (Group* child : children) {
Group* result = recFindGroup(uuid, child);
Group* result = findGroupRecursive(uuid, child);
if (result) {
return result;
}

View File

@ -27,6 +27,7 @@
#include "keys/CompositeKey.h"
class Entry;
enum class EntryReferenceType;
class Group;
class Metadata;
class QTimer;
@ -81,6 +82,7 @@ public:
Metadata* metadata();
const Metadata* metadata() const;
Entry* resolveEntry(const Uuid& uuid);
Entry* resolveEntry(const QString& text, EntryReferenceType referenceType);
Group* resolveGroup(const Uuid& uuid);
QList<DeletedObject> deletedObjects();
void addDeletedObject(const DeletedObject& delObj);
@ -141,8 +143,9 @@ private slots:
void startModifiedTimer();
private:
Entry* recFindEntry(const Uuid& uuid, Group* group);
Group* recFindGroup(const Uuid& uuid, Group* group);
Entry* findEntryRecursive(const Uuid& uuid, Group* group);
Entry* findEntryRecursive(const QString& text, EntryReferenceType referenceType, Group* group);
Group* findGroupRecursive(const Uuid& uuid, Group* group);
void createRecycleBin();

View File

@ -25,6 +25,8 @@
#include "core/Metadata.h"
#include "totp/totp.h"
#include <QRegularExpression>
const int Entry::DefaultIconNumber = 0;
const int Entry::ResolveMaximumDepth = 10;
@ -40,8 +42,8 @@ Entry::Entry()
m_data.iconNumber = DefaultIconNumber;
m_data.autoTypeEnabled = true;
m_data.autoTypeObfuscation = 0;
m_data.totpStep = QTotp::defaultStep;
m_data.totpDigits = QTotp::defaultDigits;
m_data.totpStep = Totp::defaultStep;
m_data.totpDigits = Totp::defaultDigits;
connect(m_attributes, SIGNAL(modified()), this, SIGNAL(modified()));
connect(m_attributes, SIGNAL(defaultKeyModified()), SLOT(emitDataChanged()));
@ -90,6 +92,29 @@ void Entry::setUpdateTimeinfo(bool value)
m_updateTimeinfo = value;
}
EntryReferenceType Entry::referenceType(const QString& referenceStr)
{
const QString referenceLowerStr = referenceStr.toLower();
EntryReferenceType result = EntryReferenceType::Unknown;
if (referenceLowerStr == QLatin1String("t")) {
result = EntryReferenceType::Title;
} else if (referenceLowerStr == QLatin1String("u")) {
result = EntryReferenceType::UserName;
} else if (referenceLowerStr == QLatin1String("p")) {
result = EntryReferenceType::Password;
} else if (referenceLowerStr == QLatin1String("a")) {
result = EntryReferenceType::Url;
} else if (referenceLowerStr == QLatin1String("n")) {
result = EntryReferenceType::Notes;
} else if (referenceLowerStr == QLatin1String("i")) {
result = EntryReferenceType::Uuid;
} else if (referenceLowerStr == QLatin1String("o")) {
result = EntryReferenceType::CustomAttributes;
}
return result;
}
Uuid Entry::uuid() const
{
return m_uuid;
@ -315,7 +340,7 @@ QString Entry::totp() const
if (hasTotp()) {
QString seed = totpSeed();
quint64 time = QDateTime::currentDateTime().toTime_t();
QString output = QTotp::generateTotp(seed.toLatin1(), time, m_data.totpDigits, m_data.totpStep);
QString output = Totp::generateTotp(seed.toLatin1(), time, m_data.totpDigits, m_data.totpStep);
return QString(output);
} else {
@ -326,18 +351,30 @@ QString Entry::totp() const
void Entry::setTotp(const QString& seed, quint8& step, quint8& digits)
{
if (step == 0) {
step = QTotp::defaultStep;
step = Totp::defaultStep;
}
if (digits == 0) {
digits = QTotp::defaultDigits;
digits = Totp::defaultDigits;
}
QString data;
const Totp::Encoder & enc = Totp::encoders.value(digits, Totp::defaultEncoder);
if (m_attributes->hasKey("otp")) {
m_attributes->set("otp", QString("key=%1&step=%2&size=%3").arg(seed).arg(step).arg(digits), true);
data = QString("key=%1&step=%2&size=%3").arg(seed).arg(step).arg(enc.digits == 0 ? digits : enc.digits);
if (!enc.name.isEmpty()) {
data.append("&enocder=").append(enc.name);
}
m_attributes->set("otp", data, true);
} else {
m_attributes->set("TOTP Seed", seed, true);
m_attributes->set("TOTP Settings", QString("%1;%2").arg(step).arg(digits));
if (!enc.shortName.isEmpty()) {
data = QString("%1;%2").arg(step).arg(enc.shortName);
} else {
data = QString("%1;%2").arg(step).arg(digits);
}
m_attributes->set("TOTP Settings", data);
}
}
@ -351,19 +388,24 @@ QString Entry::totpSeed() const
secret = m_attributes->value("TOTP Seed");
}
m_data.totpDigits = QTotp::defaultDigits;
m_data.totpStep = QTotp::defaultStep;
m_data.totpDigits = Totp::defaultDigits;
m_data.totpStep = Totp::defaultStep;
if (m_attributes->hasKey("TOTP Settings")) {
QRegExp rx("(\\d+);(\\d)", Qt::CaseInsensitive, QRegExp::RegExp);
int pos = rx.indexIn(m_attributes->value("TOTP Settings"));
if (pos > -1) {
m_data.totpStep = rx.cap(1).toUInt();
m_data.totpDigits = rx.cap(2).toUInt();
// this regex must be kept in sync with the set of allowed short names Totp::shortNameToEncoder
QRegularExpression rx(QString("(\\d+);((?:\\d+)|S)"));
QRegularExpressionMatch m = rx.match(m_attributes->value("TOTP Settings"));
if (m.hasMatch()) {
m_data.totpStep = m.captured(1).toUInt();
if (Totp::shortNameToEncoder.contains(m.captured(2))) {
m_data.totpDigits = Totp::shortNameToEncoder[m.captured(2)];
} else {
m_data.totpDigits = m.captured(2).toUInt();
}
}
}
return QTotp::parseOtpString(secret, m_data.totpDigits, m_data.totpStep);
return Totp::parseOtpString(secret, m_data.totpDigits, m_data.totpStep);
}
quint8 Entry::totpStep() const
@ -680,7 +722,7 @@ void Entry::updateModifiedSinceBegin()
m_modifiedSinceBegin = true;
}
QString Entry::resolveMultiplePlaceholdersRecursive(const QString &str, int maxDepth) const
QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxDepth) const
{
if (maxDepth <= 0) {
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", qPrintable(uuid().toHex()));
@ -704,15 +746,12 @@ QString Entry::resolveMultiplePlaceholdersRecursive(const QString &str, int maxD
return result;
}
QString Entry::resolvePlaceholderRecursive(const QString &placeholder, int maxDepth) const
QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDepth) const
{
const PlaceholderType typeOfPlaceholder = placeholderType(placeholder);
switch (typeOfPlaceholder) {
case PlaceholderType::NotPlaceholder:
return placeholder;
case PlaceholderType::Unknown:
qWarning("Can't resolve placeholder %s for entry with uuid %s", qPrintable(placeholder),
qPrintable(uuid().toHex()));
return placeholder;
case PlaceholderType::Title:
return title();
@ -743,45 +782,64 @@ QString Entry::resolvePlaceholderRecursive(const QString &placeholder, int maxDe
const QString key = placeholder.mid(3, placeholder.length() - 4); // {S:attr} => mid(3, len - 4)
return attributes()->hasKey(key) ? attributes()->value(key) : QString();
}
case PlaceholderType::Reference: {
// resolving references in format: {REF:<WantedField>@I:<uuid of referenced entry>}
// using format from http://keepass.info/help/base/fieldrefs.html at the time of writing,
// but supporting lookups of standard fields and references by UUID only
QString result;
QRegExp* referenceRegExp = m_attributes->referenceRegExp();
if (referenceRegExp->indexIn(placeholder) != -1) {
constexpr int wantedFieldIndex = 1;
constexpr int referencedUuidIndex = 2;
const Uuid referencedUuid(QByteArray::fromHex(referenceRegExp->cap(referencedUuidIndex).toLatin1()));
const Entry* refEntry = m_group->database()->resolveEntry(referencedUuid);
if (refEntry) {
const QString wantedField = referenceRegExp->cap(wantedFieldIndex).toLower();
if (wantedField == "t") {
result = refEntry->title();
} else if (wantedField == "u") {
result = refEntry->username();
} else if (wantedField == "p") {
result = refEntry->password();
} else if (wantedField == "a") {
result = refEntry->url();
} else if (wantedField == "n") {
result = refEntry->notes();
}
// Referencing fields of other entries only works with standard fields, not with custom user strings.
// If you want to reference a custom user string, you need to place a redirection in a standard field
// of the entry with the custom string, using {S:<Name>}, and reference the standard field.
result = refEntry->resolveMultiplePlaceholdersRecursive(result, maxDepth - 1);
}
}
return result;
}
case PlaceholderType::Reference:
return resolveReferencePlaceholderRecursive(placeholder, maxDepth);
}
return placeholder;
}
QString Entry::resolveReferencePlaceholderRecursive(const QString& placeholder, int maxDepth) const
{
// resolving references in format: {REF:<WantedField>@<SearchIn>:<SearchText>}
// using format from http://keepass.info/help/base/fieldrefs.html at the time of writing
QRegularExpressionMatch match = EntryAttributes::matchReference(placeholder);
if (!match.hasMatch()) {
return placeholder;
}
QString result;
const QString searchIn = match.captured(EntryAttributes::SearchInGroupName);
const QString searchText = match.captured(EntryAttributes::SearchTextGroupName);
const EntryReferenceType searchInType = Entry::referenceType(searchIn);
const Entry* refEntry = m_group->database()->resolveEntry(searchText, searchInType);
if (refEntry) {
const QString wantedField = match.captured(EntryAttributes::WantedFieldGroupName);
result = refEntry->referenceFieldValue(Entry::referenceType(wantedField));
// Referencing fields of other entries only works with standard fields, not with custom user strings.
// If you want to reference a custom user string, you need to place a redirection in a standard field
// of the entry with the custom string, using {S:<Name>}, and reference the standard field.
result = refEntry->resolveMultiplePlaceholdersRecursive(result, maxDepth - 1);
}
return result;
}
QString Entry::referenceFieldValue(EntryReferenceType referenceType) const
{
switch (referenceType) {
case EntryReferenceType::Title:
return title();
case EntryReferenceType::UserName:
return username();
case EntryReferenceType::Password:
return password();
case EntryReferenceType::Url:
return url();
case EntryReferenceType::Notes:
return notes();
case EntryReferenceType::Uuid:
return uuid().toHex();
default:
break;
}
return QString();
}
Group* Entry::group()
{
return m_group;
@ -839,7 +897,7 @@ const Database* Entry::database() const
}
}
QString Entry::maskPasswordPlaceholders(const QString &str) const
QString Entry::maskPasswordPlaceholders(const QString& str) const
{
QString result = str;
result.replace(QRegExp("(\\{PASSWORD\\})", Qt::CaseInsensitive, QRegExp::RegExp2), "******");
@ -856,7 +914,7 @@ QString Entry::resolvePlaceholder(const QString& placeholder) const
return resolvePlaceholderRecursive(placeholder, ResolveMaximumDepth);
}
QString Entry::resolveUrlPlaceholder(const QString &str, Entry::PlaceholderType placeholderType) const
QString Entry::resolveUrlPlaceholder(const QString& str, Entry::PlaceholderType placeholderType) const
{
if (str.isEmpty())
return QString();
@ -892,7 +950,7 @@ QString Entry::resolveUrlPlaceholder(const QString &str, Entry::PlaceholderType
return QString();
}
Entry::PlaceholderType Entry::placeholderType(const QString &placeholder) const
Entry::PlaceholderType Entry::placeholderType(const QString& placeholder) const
{
if (!placeholder.startsWith(QLatin1Char('{')) || !placeholder.endsWith(QLatin1Char('}'))) {
return PlaceholderType::NotPlaceholder;

View File

@ -36,6 +36,17 @@
class Database;
class Group;
enum class EntryReferenceType {
Unknown,
Title,
UserName,
Password,
Url,
Notes,
Uuid,
CustomAttributes
};
struct EntryData
{
int iconNumber;
@ -170,7 +181,7 @@ public:
QString maskPasswordPlaceholders(const QString& str) const;
QString resolveMultiplePlaceholders(const QString& str) const;
QString resolvePlaceholder(const QString& str) const;
QString resolveUrlPlaceholder(const QString &str, PlaceholderType placeholderType) const;
QString resolveUrlPlaceholder(const QString& str, PlaceholderType placeholderType) const;
PlaceholderType placeholderType(const QString& placeholder) const;
QString resolveUrl(const QString& url) const;
@ -203,6 +214,10 @@ private slots:
private:
QString resolveMultiplePlaceholdersRecursive(const QString& str, int maxDepth) const;
QString resolvePlaceholderRecursive(const QString& placeholder, int maxDepth) const;
QString resolveReferencePlaceholderRecursive(const QString& placeholder, int maxDepth) const;
QString referenceFieldValue(EntryReferenceType referenceType) const;
static EntryReferenceType referenceType(const QString& referenceStr);
const Database* database() const;
template <class T> bool set(T& property, const T& value);

View File

@ -17,6 +17,8 @@
#include "EntryAttachments.h"
#include <QStringList>
EntryAttachments::EntryAttachments(QObject* parent)
: QObject(parent)
{
@ -71,7 +73,8 @@ void EntryAttachments::set(const QString& key, const QByteArray& value)
void EntryAttachments::remove(const QString& key)
{
if (!m_attachments.contains(key)) {
Q_ASSERT(false);
Q_ASSERT_X(false, "EntryAttachments::remove",
qPrintable(QString("Can't find attachment for key %1").arg(key)));
return;
}
@ -83,6 +86,31 @@ void EntryAttachments::remove(const QString& key)
emit modified();
}
void EntryAttachments::remove(const QStringList& keys)
{
if (keys.isEmpty()) {
return;
}
bool isModified = false;
for (const QString &key: keys) {
if (!m_attachments.contains(key)) {
Q_ASSERT_X(false, "EntryAttachments::remove",
qPrintable(QString("Can't find attachment for key %1").arg(key)));
continue;
}
isModified = true;
emit aboutToBeRemoved(key);
m_attachments.remove(key);
emit removed(key);
}
if (isModified) {
emit modified();
}
}
void EntryAttachments::clear()
{
if (m_attachments.isEmpty()) {

View File

@ -21,6 +21,8 @@
#include <QMap>
#include <QObject>
class QStringList;
class EntryAttachments : public QObject
{
Q_OBJECT
@ -33,6 +35,7 @@ public:
QByteArray value(const QString& key) const;
void set(const QString& key, const QByteArray& value);
void remove(const QString& key);
void remove(const QStringList& keys);
void clear();
void copyDataFrom(const EntryAttachments* other);
bool operator==(const EntryAttachments& other) const;

View File

@ -25,11 +25,15 @@ const QString EntryAttributes::URLKey = "URL";
const QString EntryAttributes::NotesKey = "Notes";
const QStringList EntryAttributes::DefaultAttributes(QStringList() << TitleKey << UserNameKey
<< PasswordKey << URLKey << NotesKey);
const QString EntryAttributes::WantedFieldGroupName = "WantedField";
const QString EntryAttributes::SearchInGroupName = "SearchIn";
const QString EntryAttributes::SearchTextGroupName = "SearchText";
const QString EntryAttributes::RememberCmdExecAttr = "_EXEC_CMD";
EntryAttributes::EntryAttributes(QObject* parent)
: QObject(parent)
, m_referenceRegExp("\\{REF:([TUPAN])@I:([^}]+)\\}", Qt::CaseInsensitive, QRegExp::RegExp2)
{
clear();
}
@ -61,11 +65,16 @@ QString EntryAttributes::value(const QString& key) const
return m_attributes.value(key);
}
bool EntryAttributes::contains(const QString &key) const
bool EntryAttributes::contains(const QString& key) const
{
return m_attributes.contains(key);
}
bool EntryAttributes::containsValue(const QString& value) const
{
return m_attributes.values().contains(value);
}
bool EntryAttributes::isProtected(const QString& key) const
{
return m_protectedAttributes.contains(key);
@ -78,16 +87,8 @@ bool EntryAttributes::isReference(const QString& key) const
return false;
}
QString data = value(key);
if (m_referenceRegExp.indexIn(data) != -1) {
return true;
}
return false;
}
QRegExp* EntryAttributes::referenceRegExp()
{
return &m_referenceRegExp;
const QString data = value(key);
return matchReference(data).hasMatch();
}
void EntryAttributes::set(const QString& key, const QString& value, bool protect)
@ -258,6 +259,15 @@ bool EntryAttributes::operator!=(const EntryAttributes& other) const
|| m_protectedAttributes != other.m_protectedAttributes);
}
QRegularExpressionMatch EntryAttributes::matchReference(const QString& text)
{
static QRegularExpression referenceRegExp(
"\\{REF:(?<WantedField>[TUPANI])@(?<SearchIn>[TUPANIO]):(?<SearchText>[^}]+)\\}",
QRegularExpression::CaseInsensitiveOption);
return referenceRegExp.match(text);
}
void EntryAttributes::clear()
{
emit aboutToBeReset();

View File

@ -21,6 +21,7 @@
#include <QMap>
#include <QObject>
#include <QRegularExpression>
#include <QSet>
#include <QStringList>
@ -35,9 +36,9 @@ public:
QList<QString> customKeys();
QString value(const QString& key) const;
bool contains(const QString& key) const;
bool containsValue(const QString& value) const;
bool isProtected(const QString& key) const;
bool isReference(const QString& key) const;
QRegExp* referenceRegExp();
void set(const QString& key, const QString& value, bool protect = false);
void remove(const QString& key);
void rename(const QString& oldKey, const QString& newKey);
@ -49,6 +50,8 @@ public:
bool operator==(const EntryAttributes& other) const;
bool operator!=(const EntryAttributes& other) const;
static QRegularExpressionMatch matchReference(const QString& text);
static const QString TitleKey;
static const QString UserNameKey;
static const QString PasswordKey;
@ -58,6 +61,10 @@ public:
static const QString RememberCmdExecAttr;
static bool isDefaultAttribute(const QString& key);
static const QString WantedFieldGroupName;
static const QString SearchInGroupName;
static const QString SearchTextGroupName;
signals:
void modified();
void defaultKeyModified();
@ -74,7 +81,6 @@ signals:
private:
QMap<QString, QString> m_attributes;
QSet<QString> m_protectedAttributes;
QRegExp m_referenceRegExp;
};
#endif // KEEPASSX_ENTRYATTRIBUTES_H

View File

@ -26,6 +26,11 @@
const int Group::DefaultIconNumber = 48;
const int Group::RecycleBinIconNumber = 43;
Group::CloneFlags Group::DefaultCloneFlags = static_cast<Group::CloneFlags>(
Group::CloneNewUuid | Group::CloneResetTimeInfo | Group::CloneIncludeEntries);
Entry::CloneFlags Group::DefaultEntryCloneFlags = static_cast<Entry::CloneFlags>(
Entry::CloneNewUuid | Entry::CloneResetTimeInfo);
Group::Group()
: m_updateTimeinfo(true)
{
@ -77,8 +82,7 @@ template <class P, class V> inline bool Group::set(P& property, const V& value)
updateTimeinfo();
emit modified();
return true;
}
else {
} else {
return false;
}
}
@ -682,38 +686,59 @@ void Group::merge(const Group* other)
Entry* existingEntry = rootGroup->findEntryByUuid(entry->uuid());
// This entry does not exist at all. Create it.
if (!existingEntry) {
// This entry does not exist at all. Create it.
qDebug("New entry %s detected. Creating it.", qPrintable(entry->title()));
entry->clone(Entry::CloneIncludeHistory)->setGroup(this);
// Entry is already present in the database. Update it.
} else {
// Entry is already present in the database. Update it.
bool locationChanged = existingEntry->timeInfo().locationChanged() < entry->timeInfo().locationChanged();
if (locationChanged && existingEntry->group() != this) {
existingEntry->setGroup(this);
qDebug("Location changed for entry %s. Updating it", qPrintable(existingEntry->title()));
}
resolveConflict(existingEntry, entry);
resolveEntryConflict(existingEntry, entry);
}
}
// merge groups recursively
const QList<Group*> dbChildren = other->children();
for (Group* group : dbChildren) {
// groups are searched by name instead of uuid
if (findChildByName(group->name())) {
findChildByName(group->name())->merge(group);
} else {
Group* existingGroup = rootGroup->findChildByUuid(group->uuid());
if (!existingGroup) {
qDebug("New group %s detected. Creating it.", qPrintable(group->name()));
Group* newGroup = group->clone(Entry::CloneNoFlags, true);
Group* newGroup = group->clone(Entry::CloneNoFlags, Group::CloneNoFlags);
newGroup->setParent(this);
newGroup->merge(group);
} else {
bool locationChanged = existingGroup->timeInfo().locationChanged() < group->timeInfo().locationChanged();
if (locationChanged && existingGroup->parent() != this) {
existingGroup->setParent(this);
qDebug("Location changed for group %s. Updating it", qPrintable(existingGroup->name()));
}
resolveGroupConflict(existingGroup, group);
existingGroup->merge(group);
}
}
emit modified();
}
Group* Group::findChildByUuid(const Uuid& uuid)
{
Q_ASSERT(!uuid.isNull());
for (Group* group : groupsRecursive(true)) {
if (group->uuid() == uuid) {
return group;
}
}
return nullptr;
}
Group* Group::findChildByName(const QString& name)
{
for (Group* group : asConst(m_children)) {
@ -725,16 +750,21 @@ Group* Group::findChildByName(const QString& name)
return nullptr;
}
Group* Group::clone(Entry::CloneFlags entryFlags, bool shallow) const
Group* Group::clone(Entry::CloneFlags entryFlags, Group::CloneFlags groupFlags) const
{
Group* clonedGroup = new Group();
clonedGroup->setUpdateTimeinfo(false);
clonedGroup->setUuid(Uuid::random());
if (groupFlags & Group::CloneNewUuid) {
clonedGroup->setUuid(Uuid::random());
} else {
clonedGroup->setUuid(this->uuid());
}
clonedGroup->m_data = m_data;
if (!shallow) {
if (groupFlags & Group::CloneIncludeEntries) {
const QList<Entry*> entryList = entries();
for (Entry* entry : entryList) {
Entry* clonedEntry = entry->clone(entryFlags);
@ -743,18 +773,20 @@ Group* Group::clone(Entry::CloneFlags entryFlags, bool shallow) const
const QList<Group*> childrenGroups = children();
for (Group* groupChild : childrenGroups) {
Group* clonedGroupChild = groupChild->clone(entryFlags);
Group* clonedGroupChild = groupChild->clone(entryFlags, groupFlags);
clonedGroupChild->setParent(clonedGroup);
}
}
clonedGroup->setUpdateTimeinfo(true);
if (groupFlags & Group::CloneResetTimeInfo) {
QDateTime now = QDateTime::currentDateTimeUtc();
clonedGroup->m_data.timeInfo.setCreationTime(now);
clonedGroup->m_data.timeInfo.setLastModificationTime(now);
clonedGroup->m_data.timeInfo.setLastAccessTime(now);
clonedGroup->m_data.timeInfo.setLocationChanged(now);
QDateTime now = QDateTime::currentDateTimeUtc();
clonedGroup->m_data.timeInfo.setCreationTime(now);
clonedGroup->m_data.timeInfo.setLastModificationTime(now);
clonedGroup->m_data.timeInfo.setLastAccessTime(now);
clonedGroup->m_data.timeInfo.setLocationChanged(now);
}
return clonedGroup;
}
@ -908,7 +940,7 @@ bool Group::resolveAutoTypeEnabled() const
}
}
void Group::resolveConflict(Entry* existingEntry, Entry* otherEntry)
void Group::resolveEntryConflict(Entry* existingEntry, Entry* otherEntry)
{
const QDateTime timeExisting = existingEntry->timeInfo().lastModificationTime();
const QDateTime timeOther = otherEntry->timeInfo().lastModificationTime();
@ -946,6 +978,26 @@ void Group::resolveConflict(Entry* existingEntry, Entry* otherEntry)
}
}
void Group::resolveGroupConflict(Group* existingGroup, Group* otherGroup)
{
const QDateTime timeExisting = existingGroup->timeInfo().lastModificationTime();
const QDateTime timeOther = otherGroup->timeInfo().lastModificationTime();
// only if the other group is newer, update the existing one.
if (timeExisting < timeOther) {
qDebug("Updating group %s.", qPrintable(existingGroup->name()));
existingGroup->setName(otherGroup->name());
existingGroup->setNotes(otherGroup->notes());
if (otherGroup->iconNumber() == 0) {
existingGroup->setIcon(otherGroup->iconUuid());
} else {
existingGroup->setIcon(otherGroup->iconNumber());
}
existingGroup->setExpiryTime(otherGroup->timeInfo().expiryTime());
}
}
QStringList Group::locate(QString locateTerm, QString currentPath)
{
Q_ASSERT(!locateTerm.isNull());

View File

@ -37,6 +37,14 @@ public:
enum TriState { Inherit, Enable, Disable };
enum MergeMode { ModeInherit, KeepBoth, KeepNewer, KeepExisting };
enum CloneFlag {
CloneNoFlags = 0,
CloneNewUuid = 1, // generate a random uuid for the clone
CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time
CloneIncludeEntries = 4, // clone the group entries
};
Q_DECLARE_FLAGS(CloneFlags, CloneFlag)
struct GroupData
{
QString name;
@ -78,8 +86,11 @@ public:
static const int DefaultIconNumber;
static const int RecycleBinIconNumber;
static CloneFlags DefaultCloneFlags;
static Entry::CloneFlags DefaultEntryCloneFlags;
Group* findChildByName(const QString& name);
Group* findChildByUuid(const Uuid& uuid);
Entry* findEntry(QString entryId);
Entry* findEntryByUuid(const Uuid& uuid);
Entry* findEntryByPath(QString entryPath, QString basePath = QString(""));
@ -119,14 +130,13 @@ public:
QList<Group*> groupsRecursive(bool includeSelf);
QSet<Uuid> customIconsRecursive() const;
/**
* Creates a duplicate of this group including all child entries and groups (if not shallow).
* The exceptions are that the returned group doesn't have a parent group
* and all TimeInfo attributes are set to the current time.
* Creates a duplicate of this group.
* Note that you need to copy the custom icons manually when inserting the
* new group into another database.
*/
Group* clone(Entry::CloneFlags entryFlags = Entry::CloneNewUuid | Entry::CloneResetTimeInfo,
bool shallow = false) const;
Group* clone(Entry::CloneFlags entryFlags = DefaultEntryCloneFlags,
CloneFlags groupFlags = DefaultCloneFlags) const;
void copyDataFrom(const Group* other);
void merge(const Group* other);
QString print(bool recursive = false, int depth = 0);
@ -160,7 +170,8 @@ private:
void removeEntry(Entry* entry);
void setParent(Database* db);
void markOlderEntry(Entry* entry);
void resolveConflict(Entry* existingEntry, Entry* otherEntry);
void resolveEntryConflict(Entry* existingEntry, Entry* otherEntry);
void resolveGroupConflict(Group* existingGroup, Group* otherGroup);
void recSetDatabase(Database* db);
void cleanupParent();
@ -183,4 +194,6 @@ private:
friend void Entry::setGroup(Group* group);
};
Q_DECLARE_OPERATORS_FOR_FLAGS(Group::CloneFlags)
#endif // KEEPASSX_GROUP_H

View File

@ -0,0 +1,94 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* 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 or (at your option)
* version 3 of the License.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "MacPasteboard.h"
QString MacPasteboard::convertorName() { return QLatin1String("MacPasteboard"); }
QString MacPasteboard::flavorFor(const QString& mimetype) {
if (mimetype == QLatin1String("text/plain")) {
return QLatin1String("public.utf8-plain-text");
} else if (mimetype == QLatin1String("application/x-nspasteboard-concealed-type")) {
return QLatin1String("org.nspasteboard.ConcealedType");
}
int i = mimetype.indexOf(QLatin1String("charset="));
if (i >= 0) {
QString cs(mimetype.mid(i + 8).toLower());
i = cs.indexOf(QLatin1Char(';'));
if (i >= 0) {
cs = cs.left(i);
}
if (cs == QLatin1String("system")) {
return QLatin1String("public.utf8-plain-text");
} else if (cs == QLatin1String("iso-10646-ucs-2") ||
cs == QLatin1String("utf16")) {
return QLatin1String("public.utf16-plain-text");
}
}
return QString();
}
QString MacPasteboard::mimeFor(QString flavor) {
if (flavor == QLatin1String("public.utf8-plain-text"))
return QLatin1String("text/plain");
if (flavor == QLatin1String("org.nspasteboard.ConcealedType"))
return QLatin1String("application/x-nspasteboard-concealed-type");
if (flavor == QLatin1String("public.utf16-plain-text"))
return QLatin1String("text/plain;charset=utf16");
return QString();
}
bool MacPasteboard::canConvert(const QString& mimetype, QString flavor) {
Q_UNUSED(mimetype);
Q_UNUSED(flavor);
return true;
}
QVariant MacPasteboard::convertToMime(const QString& mimetype, QList<QByteArray> data, QString flavor) {
if (data.count() > 1)
qWarning("QMime::convertToMime: Cannot handle multiple member data");
const QByteArray& firstData = data.first();
QVariant ret;
if (flavor == QLatin1String("public.utf8-plain-text")) {
ret = QString::fromUtf8(firstData);
} else if (flavor == QLatin1String("org.nspasteboard.ConcealedType")) {
ret = QString::fromUtf8(firstData);
} else if (flavor == QLatin1String("public.utf16-plain-text")) {
ret = QTextCodec::codecForName("UTF-16")->toUnicode(firstData);
} else {
qWarning("QMime::convertToMime: unhandled mimetype: %s",
qPrintable(mimetype));
}
return ret;
}
QList<QByteArray> MacPasteboard::convertFromMime(const QString&, QVariant data, QString flavor) {
QList<QByteArray> ret;
QString string = data.toString();
if (flavor == QLatin1String("public.utf8-plain-text"))
ret.append(string.toUtf8());
else if (flavor == QLatin1String("org.nspasteboard.ConcealedType"))
ret.append(string.toUtf8());
else if (flavor == QLatin1String("public.utf16-plain-text"))
ret.append(QTextCodec::codecForName("UTF-16")->fromUnicode(string));
return ret;
}

37
src/core/MacPasteboard.h Normal file
View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* 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 or (at your option)
* version 3 of the License.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_MACPASTEBOARD_H
#define KEEPASSXC_MACPASTEBOARD_H
#include <QMacPasteboardMime>
#include <QTextCodec>
class MacPasteboard : public QMacPasteboardMime
{
public:
explicit MacPasteboard() : QMacPasteboardMime(MIME_ALL) {}
QString convertorName() override;
bool canConvert(const QString &mime, QString flav) override;
QString mimeFor(QString flav) override;
QString flavorFor(const QString &mime) override;
QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav) override;
QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav) override;
};
#endif // KEEPASSXC_MACPASTEBOARD_H

View File

@ -1,93 +0,0 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* 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 or (at your option)
* version 3 of the License.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPTIONAL_H
#define OPTIONAL_H
/*
* This utility class is for providing basic support for an option type.
* It can be replaced by std::optional (C++17) or
* std::experimental::optional (C++11) when they become fully supported
* by all the compilers.
*/
template <typename T>
class Optional
{
public:
// None
Optional() :
m_hasValue(false),
m_value()
{ };
// Some T
Optional(const T& value) :
m_hasValue(true),
m_value(value)
{ };
// Copy
Optional(const Optional& other) :
m_hasValue(other.m_hasValue),
m_value(other.m_value)
{ };
const Optional& operator=(const Optional& other)
{
m_hasValue = other.m_hasValue;
m_value = other.m_value;
return *this;
}
bool operator==(const Optional& other) const
{
if(m_hasValue)
return other.m_hasValue && m_value == other.m_value;
else
return !other.m_hasValue;
}
bool operator!=(const Optional& other) const
{
return !(*this == other);
}
bool hasValue() const
{
return m_hasValue;
}
T valueOr(const T& other) const
{
return m_hasValue ? m_value : other;
}
Optional static makeOptional(const T& value)
{
return Optional(value);
}
private:
bool m_hasValue;
T m_value;
};
#endif // OPTIONAL_H

View File

@ -36,6 +36,14 @@ ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent):
this, //receiver
SLOT(freedesktopScreenSaver(bool)));
sessionBus.connect(
"org.gnome.ScreenSaver", // service
"/org/gnome/ScreenSaver", // path
"org.gnome.ScreenSaver", // interface
"ActiveChanged", // signal name
this, //receiver
SLOT(freedesktopScreenSaver(bool)));
sessionBus.connect(
"org.gnome.SessionManager", // service
"/org/gnome/SessionManager/Presence", // path

View File

@ -74,6 +74,11 @@ bool SymmetricCipher::reset()
return m_backend->reset();
}
int SymmetricCipher::keySize() const
{
return m_backend->keySize();
}
int SymmetricCipher::blockSize() const
{
return m_backend->blockSize();

View File

@ -38,6 +38,7 @@ public:
enum Mode
{
Cbc,
Ctr,
Ecb,
Stream
};
@ -69,6 +70,7 @@ public:
}
bool reset();
int keySize() const;
int blockSize() const;
QString errorString() const;

View File

@ -33,6 +33,7 @@ public:
Q_REQUIRED_RESULT virtual bool processInPlace(QByteArray& data, quint64 rounds) = 0;
virtual bool reset() = 0;
virtual int keySize() const = 0;
virtual int blockSize() const = 0;
virtual QString errorString() const = 0;

View File

@ -26,7 +26,6 @@ SymmetricCipherGcrypt::SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, Sy
, m_algo(gcryptAlgo(algo))
, m_mode(gcryptMode(mode))
, m_direction(direction)
, m_blockSize(-1)
{
}
@ -62,6 +61,9 @@ int SymmetricCipherGcrypt::gcryptMode(SymmetricCipher::Mode mode)
case SymmetricCipher::Cbc:
return GCRY_CIPHER_MODE_CBC;
case SymmetricCipher::Ctr:
return GCRY_CIPHER_MODE_CTR;
case SymmetricCipher::Stream:
return GCRY_CIPHER_MODE_STREAM;
@ -86,20 +88,14 @@ bool SymmetricCipherGcrypt::init()
gcry_error_t error;
if(m_ctx != nullptr)
gcry_cipher_close(m_ctx);
error = gcry_cipher_open(&m_ctx, m_algo, m_mode, 0);
if (error != 0) {
setErrorString(error);
return false;
}
size_t blockSizeT;
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_BLKLEN, nullptr, &blockSizeT);
if (error != 0) {
setErrorString(error);
return false;
}
m_blockSize = blockSizeT;
return true;
}
@ -119,7 +115,13 @@ bool SymmetricCipherGcrypt::setKey(const QByteArray& key)
bool SymmetricCipherGcrypt::setIv(const QByteArray& iv)
{
m_iv = iv;
gcry_error_t error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
gcry_error_t error;
if (m_mode == GCRY_CIPHER_MODE_CTR) {
error = gcry_cipher_setctr(m_ctx, m_iv.constData(), m_iv.size());
} else {
error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
}
if (error != 0) {
setErrorString(error);
@ -228,9 +230,28 @@ bool SymmetricCipherGcrypt::reset()
return true;
}
int SymmetricCipherGcrypt::keySize() const
{
gcry_error_t error;
size_t keySizeT;
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_KEYLEN, nullptr, &keySizeT);
if (error != 0)
return -1;
return keySizeT;
}
int SymmetricCipherGcrypt::blockSize() const
{
return m_blockSize;
gcry_error_t error;
size_t blockSizeT;
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_BLKLEN, nullptr, &blockSizeT);
if (error != 0)
return -1;
return blockSizeT;
}
QString SymmetricCipherGcrypt::errorString() const

View File

@ -39,6 +39,7 @@ public:
Q_REQUIRED_RESULT bool processInPlace(QByteArray& data, quint64 rounds);
bool reset();
int keySize() const;
int blockSize() const;
QString errorString() const;
@ -54,7 +55,6 @@ private:
const SymmetricCipher::Direction m_direction;
QByteArray m_key;
QByteArray m_iv;
int m_blockSize;
QString m_errorString;
};

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2016 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* 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
@ -18,36 +19,29 @@
#include "KeePass2Repair.h"
#include <QBuffer>
#include <QScopedPointer>
#include <QRegExp>
#include "format/KeePass2RandomStream.h"
#include "format/KeePass2Reader.h"
#include "format/KeePass2XmlReader.h"
KeePass2Repair::KeePass2Repair()
: m_db(nullptr)
KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, const CompositeKey& key)
{
}
KeePass2Repair::RepairResult KeePass2Repair::repairDatabase(QIODevice* device, const CompositeKey& key)
{
m_db = nullptr;
m_errorStr.clear();
KeePass2Reader reader;
reader.setSaveXml(true);
Database* db = reader.readDatabase(device, key, true);
QScopedPointer<Database> db(reader.readDatabase(device, key, true));
if (!reader.hasError()) {
delete db;
return NothingTodo;
return qMakePair(NothingTodo, nullptr);
}
QByteArray xmlData = reader.xmlData();
if (!db || xmlData.isEmpty()) {
delete db;
m_errorStr = reader.errorString();
return UnableToOpen;
return qMakePair(UnableToOpen, nullptr);
}
bool repairAction = false;
@ -59,8 +53,7 @@ KeePass2Repair::RepairResult KeePass2Repair::repairDatabase(QIODevice* device, c
&& encodingRegExp.cap(1).compare("utf8", Qt::CaseInsensitive) != 0)
{
// database is not utf-8 encoded, we don't support repairing that
delete db;
return RepairFailed;
return qMakePair(RepairFailed, nullptr);
}
}
@ -75,8 +68,7 @@ KeePass2Repair::RepairResult KeePass2Repair::repairDatabase(QIODevice* device, c
if (!repairAction) {
// we were unable to find the problem
delete db;
return RepairFailed;
return qMakePair(RepairFailed, nullptr);
}
KeePass2RandomStream randomStream;
@ -84,23 +76,16 @@ KeePass2Repair::RepairResult KeePass2Repair::repairDatabase(QIODevice* device, c
KeePass2XmlReader xmlReader;
QBuffer buffer(&xmlData);
buffer.open(QIODevice::ReadOnly);
xmlReader.readDatabase(&buffer, db, &randomStream);
xmlReader.readDatabase(&buffer, db.data(), &randomStream);
if (xmlReader.hasError()) {
delete db;
return RepairFailed;
return qMakePair(RepairFailed, nullptr);
}
else {
m_db = db;
return RepairSuccess;
return qMakePair(RepairSuccess, db.take());
}
}
Database* KeePass2Repair::database() const
{
return m_db;
}
QString KeePass2Repair::errorString() const
{
return m_errorStr;

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2016 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* 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,6 +21,7 @@
#include <QCoreApplication>
#include <QIODevice>
#include <QPair>
#include "core/Database.h"
#include "keys/CompositeKey.h"
@ -36,14 +38,12 @@ public:
RepairSuccess,
RepairFailed
};
using RepairOutcome = QPair<RepairResult, Database*>;
KeePass2Repair();
RepairResult repairDatabase(QIODevice* device, const CompositeKey& key);
Database* database() const;
RepairOutcome repairDatabase(QIODevice* device, const CompositeKey& key);
QString errorString() const;
private:
Database* m_db;
QString m_errorStr;
};

View File

@ -87,6 +87,9 @@ AboutDialog::AboutDialog(QWidget* parent)
#ifdef WITH_XC_YUBIKEY
extensions += "\n- YubiKey";
#endif
#ifdef WITH_XC_SSHAGENT
extensions += "\n- SSH Agent";
#endif
if (extensions.isEmpty())
extensions = " None";

View File

@ -28,6 +28,9 @@ Clipboard* Clipboard::m_instance(nullptr);
Clipboard::Clipboard(QObject* parent)
: QObject(parent)
, m_timer(new QTimer(this))
#ifdef Q_OS_MAC
, m_pasteboard(new MacPasteboard)
#endif
{
m_timer->setSingleShot(true);
connect(m_timer, SIGNAL(timeout()), SLOT(clearClipboard()));
@ -38,10 +41,17 @@ void Clipboard::setText(const QString& text)
{
QClipboard* clipboard = QApplication::clipboard();
#ifdef Q_OS_MAC
QMimeData* mime = new QMimeData;
mime->setText(text);
mime->setData("application/x-nspasteboard-concealed-type", text.toUtf8());
clipboard->setMimeData(mime, QClipboard::Clipboard);
#else
clipboard->setText(text, QClipboard::Clipboard);
if (clipboard->supportsSelection()) {
clipboard->setText(text, QClipboard::Selection);
}
#endif
if (config()->get("security/clearclipboard").toBool()) {
int timeout = config()->get("security/clearclipboardtimeout").toInt();

View File

@ -19,6 +19,9 @@
#define KEEPASSX_CLIPBOARD_H
#include <QObject>
#ifdef Q_OS_MAC
#include "core/MacPasteboard.h"
#endif
class QTimer;
@ -43,6 +46,9 @@ private:
static Clipboard* m_instance;
QTimer* m_timer;
#ifdef Q_OS_MAC
QScopedPointer<MacPasteboard> m_pasteboard;
#endif
QString m_lastCopied;
};

View File

@ -277,6 +277,9 @@ void DatabaseOpenWidget::activateChallengeResponse()
void DatabaseOpenWidget::browseKeyFile()
{
QString filters = QString("%1 (*);;%2 (*.key)").arg(tr("All files"), tr("Key files"));
if (!config()->get("RememberLastKeyFiles").toBool()) {
fileDialog()->setNextForgetDialog();
}
QString filename = fileDialog()->getOpenFileName(this, tr("Select key file"), QString(), filters);
if (!filename.isEmpty()) {

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2016 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* 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
@ -69,7 +70,8 @@ void DatabaseRepairWidget::openDatabase()
delete m_db;
}
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
KeePass2Repair::RepairResult repairResult = repair.repairDatabase(&file, masterKey);
auto repairOutcome = repair.repairDatabase(&file, masterKey);
KeePass2Repair::RepairResult repairResult = repairOutcome.first;
QApplication::restoreOverrideCursor();
switch (repairResult) {
@ -83,7 +85,7 @@ void DatabaseRepairWidget::openDatabase()
emit editFinished(false);
return;
case KeePass2Repair::RepairSuccess:
m_db = repair.database();
m_db = repairOutcome.second;
MessageBox::warning(this, tr("Success"), tr("The database has been successfully repaired\nYou can now save it."));
emit editFinished(true);
return;

View File

@ -90,12 +90,12 @@ void DatabaseTabWidget::newDatabase()
Database* db = new Database();
db->rootGroup()->setName(tr("Root"));
dbStruct.dbWidget = new DatabaseWidget(db, this);
CompositeKey emptyKey;
db->setKey(emptyKey);
insertDatabase(db, dbStruct);
if (!saveDatabaseAs(db)) {
closeDatabase(db);
return;
@ -298,8 +298,7 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
if (!saveDatabase(db)) {
return false;
}
}
else {
} else if (dbStruct.dbWidget->currentMode() != DatabaseWidget::LockedMode) {
QMessageBox::StandardButton result =
MessageBox::question(
this, tr("Save changes?"),
@ -307,10 +306,9 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
QMessageBox::Yes | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Yes);
if (result == QMessageBox::Yes) {
if (!saveDatabase(db)) {
return false;
return false;
}
}
else if (result == QMessageBox::Cancel) {
} else if (result == QMessageBox::Cancel) {
return false;
}
}
@ -355,8 +353,13 @@ bool DatabaseTabWidget::saveDatabase(Database* db)
{
DatabaseManagerStruct& dbStruct = m_dbList[db];
if (dbStruct.saveToFilename) {
if (dbStruct.dbWidget->currentMode() == DatabaseWidget::LockedMode) {
// Never allow saving a locked database; it causes corruption
// We return true since a save is not required
return true;
}
if (dbStruct.saveToFilename) {
dbStruct.dbWidget->blockAutoReload(true);
QString errorMessage = db->saveToFile(dbStruct.canonicalFilePath);
dbStruct.dbWidget->blockAutoReload(false);
@ -375,7 +378,6 @@ bool DatabaseTabWidget::saveDatabase(Database* db)
MessageWidget::Error);
return false;
}
} else {
return saveDatabaseAs(db);
}
@ -622,6 +624,36 @@ void DatabaseTabWidget::updateTabNameFromDbWidgetSender()
DatabaseWidget* dbWidget = static_cast<DatabaseWidget*>(sender());
updateTabName(databaseFromDatabaseWidget(dbWidget));
Database* db = dbWidget->database();
Group *autoload = db->rootGroup()->findChildByName("AutoOpen");
if (autoload) {
const DatabaseManagerStruct& dbStruct = m_dbList.value(db);
QFileInfo dbpath(dbStruct.canonicalFilePath);
QDir dbFolder(dbpath.canonicalPath());
for (auto entry : autoload->entries()) {
if (entry->url().isEmpty() || entry->password().isEmpty()) {
continue;
}
QFileInfo filepath;
if (entry->url().startsWith("file://")) {
QUrl url(entry->url());
filepath.setFile(url.toLocalFile());
}
else {
filepath.setFile(entry->url());
if (filepath.isRelative()) {
filepath.setFile(dbFolder, entry->url());
}
}
if (!filepath.isFile()) {
continue;
}
openDatabase(filepath.canonicalFilePath(), entry->password(), "");
}
}
}
int DatabaseTabWidget::databaseIndex(Database* db)

View File

@ -57,12 +57,19 @@
#include "gui/group/EditGroupWidget.h"
#include "gui/group/GroupView.h"
#include "config-keepassx.h"
#ifdef WITH_XC_SSHAGENT
#include "sshagent/SSHAgent.h"
#endif
DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
: QStackedWidget(parent)
, m_db(db)
, m_newGroup(nullptr)
, m_newEntry(nullptr)
, m_newParent(nullptr)
, m_importingCsv(false)
{
m_mainWidget = new QWidget(this);
@ -73,15 +80,15 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
QLayout* layout = new QHBoxLayout();
mainLayout->addWidget(m_messageWidget);
mainLayout->addLayout(layout);
m_splitter = new QSplitter(m_mainWidget);
m_splitter->setChildrenCollapsible(false);
m_mainSplitter = new QSplitter(m_mainWidget);
m_mainSplitter->setChildrenCollapsible(false);
m_detailSplitter = new QSplitter(m_mainWidget);
m_detailSplitter->setOrientation(Qt::Vertical);
m_detailSplitter->setChildrenCollapsible(true);
QWidget* rightHandSideWidget = new QWidget(m_splitter);
QWidget* rightHandSideWidget = new QWidget(m_mainSplitter);
m_groupView = new GroupView(db, m_splitter);
m_groupView = new GroupView(db, m_mainSplitter);
m_groupView->setObjectName("groupView");
m_groupView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_groupView, SIGNAL(customContextMenuRequested(QPoint)),
@ -122,13 +129,13 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
setTabOrder(m_entryView, m_groupView);
m_splitter->addWidget(m_groupView);
m_splitter->addWidget(rightHandSideWidget);
m_mainSplitter->addWidget(m_groupView);
m_mainSplitter->addWidget(rightHandSideWidget);
m_splitter->setStretchFactor(0, 30);
m_splitter->setStretchFactor(1, 70);
m_mainSplitter->setStretchFactor(0, 30);
m_mainSplitter->setStretchFactor(1, 70);
layout->addWidget(m_splitter);
layout->addWidget(m_mainSplitter);
m_mainWidget->setLayout(mainLayout);
m_editEntryWidget = new EditEntryWidget();
@ -137,13 +144,14 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
m_editGroupWidget = new EditGroupWidget();
m_editGroupWidget->setObjectName("editGroupWidget");
m_changeMasterKeyWidget = new ChangeMasterKeyWidget();
m_changeMasterKeyWidget->setObjectName("changeMasterKeyWidget");
m_changeMasterKeyWidget->headlineLabel()->setText(tr("Change master key"));
m_csvImportWizard = new CsvImportWizard();
m_csvImportWizard->setObjectName("csvImportWizard");
QFont headlineLabelFont = m_changeMasterKeyWidget->headlineLabel()->font();
headlineLabelFont.setBold(true);
headlineLabelFont.setPointSize(headlineLabelFont.pointSize() + 2);
m_changeMasterKeyWidget->headlineLabel()->setFont(headlineLabelFont);
m_csvImportWizard = new CsvImportWizard();
m_csvImportWizard->setObjectName("csvImportWizard");
m_databaseSettingsWidget = new DatabaseSettingsWidget();
m_databaseSettingsWidget->setObjectName("databaseSettingsWidget");
m_databaseOpenWidget = new DatabaseOpenWidget();
@ -168,7 +176,8 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
addWidget(m_keepass1OpenWidget);
addWidget(m_unlockDatabaseWidget);
connect(m_splitter, SIGNAL(splitterMoved(int,int)), SIGNAL(splitterSizesChanged()));
connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged()));
connect(m_detailSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(detailSplitterSizesChanged()));
connect(m_entryView->header(), SIGNAL(sectionResized(int,int,int)), SIGNAL(entryColumnSizesChanged()));
connect(m_groupView, SIGNAL(groupChanged(Group*)), this, SLOT(onGroupChanged(Group*)));
connect(m_groupView, SIGNAL(groupChanged(Group*)), SIGNAL(groupChanged()));
@ -207,6 +216,13 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
m_searchCaseSensitive = false;
m_searchLimitGroup = config()->get("SearchLimitGroup", false).toBool();
#ifdef WITH_XC_SSHAGENT
if (config()->get("SSHAgent", false).toBool()) {
connect(this, SIGNAL(currentModeChanged(DatabaseWidget::Mode)), SSHAgent::instance(), SLOT(databaseModeChanged(DatabaseWidget::Mode)));
connect(this, SIGNAL(closeRequest()), SSHAgent::instance(), SLOT(databaseModeChanged()));
}
#endif
setCurrentWidget(m_mainWidget);
}
@ -251,14 +267,24 @@ bool DatabaseWidget::isEditWidgetModified() const
}
}
QList<int> DatabaseWidget::splitterSizes() const
QList<int> DatabaseWidget::mainSplitterSizes() const
{
return m_splitter->sizes();
return m_mainSplitter->sizes();
}
void DatabaseWidget::setSplitterSizes(const QList<int>& sizes)
void DatabaseWidget::setMainSplitterSizes(const QList<int>& sizes)
{
m_splitter->setSizes(sizes);
m_mainSplitter->setSizes(sizes);
}
QList<int> DatabaseWidget::detailSplitterSizes() const
{
return m_detailSplitter->sizes();
}
void DatabaseWidget::setDetailSplitterSizes(const QList<int> &sizes)
{
m_detailSplitter->setSizes(sizes);
}
QList<int> DatabaseWidget::entryHeaderViewSizes() const
@ -391,6 +417,8 @@ void DatabaseWidget::setupTotp()
setupTotpDialog->setSeed(currentEntry->totpSeed());
setupTotpDialog->setStep(currentEntry->totpStep());
setupTotpDialog->setDigits(currentEntry->totpDigits());
// now that all settings are set, decide whether it's default, steam or custom
setupTotpDialog->setSettings(currentEntry->totpDigits());
}
setupTotpDialog->open();
@ -771,6 +799,12 @@ void DatabaseWidget::switchToGroupEdit(Group* group, bool create)
void DatabaseWidget::updateMasterKey(bool accepted)
{
if (m_importingCsv) {
setCurrentWidget(m_csvImportWizard);
m_csvImportWizard->keyFinished(accepted, m_changeMasterKeyWidget->newMasterKey());
return;
}
if (accepted) {
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
bool result = m_db->setKey(m_changeMasterKeyWidget->newMasterKey());
@ -904,6 +938,7 @@ void DatabaseWidget::switchToMasterKeyChange(bool disableCancel)
m_changeMasterKeyWidget->clearForms();
m_changeMasterKeyWidget->setCancelEnabled(!disableCancel);
setCurrentWidget(m_changeMasterKeyWidget);
m_importingCsv = false;
}
void DatabaseWidget::switchToDatabaseSettings()
@ -915,8 +950,13 @@ void DatabaseWidget::switchToDatabaseSettings()
void DatabaseWidget::switchToOpenDatabase(const QString& fileName)
{
updateFilename(fileName);
m_databaseOpenWidget->load(fileName);
setCurrentWidget(m_databaseOpenWidget);
if (m_databaseOpenWidget) {
m_databaseOpenWidget->load(fileName);
setCurrentWidget(m_databaseOpenWidget);
} else if (m_unlockDatabaseWidget) {
m_unlockDatabaseWidget->load(fileName);
setCurrentWidget(m_unlockDatabaseWidget);
}
}
void DatabaseWidget::switchToOpenDatabase(const QString& fileName, const QString& password,
@ -924,15 +964,21 @@ void DatabaseWidget::switchToOpenDatabase(const QString& fileName, const QString
{
updateFilename(fileName);
switchToOpenDatabase(fileName);
m_databaseOpenWidget->enterKey(password, keyFile);
if (m_databaseOpenWidget) {
m_databaseOpenWidget->enterKey(password, keyFile);
} else if (m_unlockDatabaseWidget) {
m_unlockDatabaseWidget->enterKey(password, keyFile);
}
}
void DatabaseWidget::switchToImportCsv(const QString& fileName)
{
updateFilename(fileName);
switchToMasterKeyChange();
m_csvImportWizard->load(fileName, m_db);
setCurrentWidget(m_csvImportWizard);
m_changeMasterKeyWidget->clearForms();
m_changeMasterKeyWidget->setCancelEnabled(false);
setCurrentWidget(m_changeMasterKeyWidget);
m_importingCsv = true;
}
void DatabaseWidget::switchToOpenMergeDatabase(const QString& fileName)
@ -1169,7 +1215,7 @@ void DatabaseWidget::onWatchedFileChanged()
void DatabaseWidget::reloadDatabaseFile()
{
if (m_db == nullptr) {
if (!m_db || currentMode() == DatabaseWidget::LockedMode) {
return;
}
@ -1201,7 +1247,7 @@ void DatabaseWidget::reloadDatabaseFile()
if (m_databaseModified) {
// Ask if we want to merge changes into new database
QMessageBox::StandardButton mb = MessageBox::question(this, tr("Merge Request"),
tr("The database file has changed and you have unsaved changes."
tr("The database file has changed and you have unsaved changes.\n"
"Do you want to merge your changes?"),
QMessageBox::Yes | QMessageBox::No);
@ -1365,6 +1411,12 @@ void DatabaseWidget::showUnlockDialog()
{
m_unlockDatabaseDialog->clearForms();
m_unlockDatabaseDialog->setDBFilename(m_filename);
#if defined(Q_OS_MAC)
autoType()->raiseWindow();
Tools::wait(500);
#endif
m_unlockDatabaseDialog->show();
m_unlockDatabaseDialog->activateWindow();
}

View File

@ -88,8 +88,10 @@ public:
bool isGroupSelected() const;
bool isInEditMode() const;
bool isEditWidgetModified() const;
QList<int> splitterSizes() const;
void setSplitterSizes(const QList<int>& sizes);
QList<int> mainSplitterSizes() const;
void setMainSplitterSizes(const QList<int>& sizes);
QList<int> detailSplitterSizes() const;
void setDetailSplitterSizes(const QList<int>& sizes);
QList<int> entryHeaderViewSizes() const;
void setEntryViewHeaderSizes(const QList<int>& sizes);
void clearAllWidgets();
@ -123,7 +125,8 @@ signals:
void listModeActivated();
void searchModeAboutToActivate();
void searchModeActivated();
void splitterSizesChanged();
void mainSplitterSizesChanged();
void detailSplitterSizesChanged();
void entryColumnSizesChanged();
void updateSearch(QString text);
@ -214,7 +217,7 @@ private:
KeePass1OpenWidget* m_keepass1OpenWidget;
UnlockDatabaseWidget* m_unlockDatabaseWidget;
UnlockDatabaseDialog* m_unlockDatabaseDialog;
QSplitter* m_splitter;
QSplitter* m_mainSplitter;
QSplitter* m_detailSplitter;
GroupView* m_groupView;
EntryView* m_entryView;
@ -233,6 +236,9 @@ private:
bool m_searchCaseSensitive;
bool m_searchLimitGroup;
// CSV import state
bool m_importingCsv;
// Autoreload
QFileSystemWatcher m_fileWatcher;
QTimer m_fileWatchTimer;

View File

@ -25,14 +25,16 @@ DatabaseWidgetStateSync::DatabaseWidgetStateSync(QObject* parent)
, m_activeDbWidget(nullptr)
, m_blockUpdates(false)
{
m_splitterSizes = variantToIntList(config()->get("GUI/SplitterState"));
m_mainSplitterSizes = variantToIntList(config()->get("GUI/SplitterState"));
m_detailSplitterSizes = variantToIntList(config()->get("GUI/DetailSplitterState"));
m_columnSizesList = variantToIntList(config()->get("GUI/EntryListColumnSizes"));
m_columnSizesSearch = variantToIntList(config()->get("GUI/EntrySearchColumnSizes"));
}
DatabaseWidgetStateSync::~DatabaseWidgetStateSync()
{
config()->set("GUI/SplitterState", intListToVariant(m_splitterSizes));
config()->set("GUI/SplitterState", intListToVariant(m_mainSplitterSizes));
config()->set("GUI/DetailSplitterState", intListToVariant(m_detailSplitterSizes));
config()->set("GUI/EntryListColumnSizes", intListToVariant(m_columnSizesList));
config()->set("GUI/EntrySearchColumnSizes", intListToVariant(m_columnSizesSearch));
}
@ -48,17 +50,25 @@ void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget)
if (m_activeDbWidget) {
m_blockUpdates = true;
if (!m_splitterSizes.isEmpty())
m_activeDbWidget->setSplitterSizes(m_splitterSizes);
if (!m_mainSplitterSizes.isEmpty()) {
m_activeDbWidget->setMainSplitterSizes(m_mainSplitterSizes);
}
if (m_activeDbWidget->isInSearchMode())
if (!m_detailSplitterSizes.isEmpty()) {
m_activeDbWidget->setDetailSplitterSizes(m_detailSplitterSizes);
}
if (m_activeDbWidget->isInSearchMode()) {
restoreSearchView();
else
} else {
restoreListView();
}
m_blockUpdates = false;
connect(m_activeDbWidget, SIGNAL(splitterSizesChanged()),
connect(m_activeDbWidget, SIGNAL(mainSplitterSizesChanged()),
SLOT(updateSplitterSizes()));
connect(m_activeDbWidget, SIGNAL(detailSplitterSizesChanged()),
SLOT(updateSplitterSizes()));
connect(m_activeDbWidget, SIGNAL(entryColumnSizesChanged()),
SLOT(updateColumnSizes()));
@ -102,7 +112,8 @@ void DatabaseWidgetStateSync::updateSplitterSizes()
return;
}
m_splitterSizes = m_activeDbWidget->splitterSizes();
m_mainSplitterSizes = m_activeDbWidget->mainSplitterSizes();
m_detailSplitterSizes = m_activeDbWidget->detailSplitterSizes();
}
void DatabaseWidgetStateSync::updateColumnSizes()

View File

@ -46,7 +46,8 @@ private:
DatabaseWidget* m_activeDbWidget;
bool m_blockUpdates;
QList<int> m_splitterSizes;
QList<int> m_mainSplitterSizes;
QList<int> m_detailSplitterSizes;
QList<int> m_columnSizesList;
QList<int> m_columnSizesSearch;
};

View File

@ -20,6 +20,7 @@
#include "ui_DetailsWidget.h"
#include <QDebug>
#include <QTimer>
#include "core/Config.h"
#include "core/FilePath.h"
@ -33,6 +34,7 @@ DetailsWidget::DetailsWidget(QWidget* parent)
, m_locked(false)
, m_currentEntry(nullptr)
, m_currentGroup(nullptr)
, m_timer(nullptr)
, m_attributesWidget(nullptr)
, m_autotypeWidget(nullptr)
, m_selectedTabEntry(0)
@ -58,7 +60,7 @@ DetailsWidget::~DetailsWidget()
{
}
void DetailsWidget::getSelectedEntry(Entry* selectedEntry)
void DetailsWidget::getSelectedEntry(Entry* selectedEntry)
{
if (!selectedEntry) {
hideDetails();
@ -108,7 +110,8 @@ void DetailsWidget::getSelectedEntry(Entry* selectedEntry)
m_ui->usernameLabel->setText(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->username()));
if (!config()->get("security/hidepassworddetails").toBool()) {
m_ui->passwordLabel->setText(shortPassword(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password())));
m_ui->passwordLabel->setText(
shortPassword(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password())));
m_ui->passwordLabel->setToolTip(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password()));
} else {
m_ui->passwordLabel->setText("****");
@ -136,14 +139,16 @@ void DetailsWidget::getSelectedEntry(Entry* selectedEntry)
}
if (m_currentEntry->hasTotp()) {
m_ui->totpButton->show();
updateTotp();
m_step = m_currentEntry->totpStep();
if (nullptr != m_timer) {
m_timer->stop();
}
m_timer = new QTimer(this);
connect(m_timer, SIGNAL(timeout()), this, SLOT(updateTotp()));
updateTotp();
m_timer->start(m_step * 10);
m_ui->totpButton->show();
}
QString notes = m_currentEntry->notes();
@ -188,7 +193,7 @@ void DetailsWidget::getSelectedEntry(Entry* selectedEntry)
}
}
void DetailsWidget::getSelectedGroup(Group* selectedGroup)
void DetailsWidget::getSelectedGroup(Group* selectedGroup)
{
if (!selectedGroup) {
hideDetails();
@ -212,7 +217,6 @@ void DetailsWidget::getSelectedGroup(Group* selectedGroup)
m_ui->tabWidget->setTabEnabled(GroupNotesTab, false);
m_ui->totpButton->hide();
m_ui->totpWidget->hide();
@ -248,7 +252,7 @@ void DetailsWidget::getSelectedGroup(Group* selectedGroup)
autotype = tr("Enabled");
}
m_ui->autotypeLabel->setText(autotype);
TimeInfo groupTime = m_currentGroup->timeInfo();
if (groupTime.expires()) {
m_ui->groupExpirationLabel->setText(groupTime.expiryTime().toString(Qt::DefaultLocaleShortDate));
@ -263,23 +267,23 @@ void DetailsWidget::getSelectedGroup(Group* selectedGroup)
void DetailsWidget::updateTotp()
{
if (m_locked) {
if (!m_locked) {
QString totpCode = m_currentEntry->totp();
QString firstHalf = totpCode.left(totpCode.size() / 2);
QString secondHalf = totpCode.mid(totpCode.size() / 2);
m_ui->totpLabel->setText(firstHalf + " " + secondHalf);
} else if (nullptr != m_timer) {
m_timer->stop();
return;
}
QString totpCode = m_currentEntry->totp();
QString firstHalf = totpCode.left(totpCode.size()/2);
QString secondHalf = totpCode.right(totpCode.size()/2);
m_ui->totpLabel->setText(firstHalf + " " + secondHalf);
}
void DetailsWidget::showTotp(bool visible)
{
if (visible){
{
if (visible) {
m_ui->totpWidget->show();
} else {
m_ui->totpWidget->hide();
}
}
}
QString DetailsWidget::shortUrl(QString url)
@ -326,7 +330,8 @@ void DetailsWidget::setDatabaseMode(DatabaseWidget::Mode mode)
}
}
void DetailsWidget::updateTabIndex(int index) {
void DetailsWidget::updateTabIndex(int index)
{
if (m_ui->stackedWidget->currentIndex() == GroupPreview) {
m_selectedTabGroup = index;
} else {

View File

@ -18,9 +18,8 @@
#ifndef KEEPASSX_DETAILSWIDGET_H
#define KEEPASSX_DETAILSWIDGET_H
#include <QWidget>
#include "gui/DatabaseWidget.h"
#include <QWidget>
namespace Ui {
class DetailsWidget;

View File

@ -6,22 +6,10 @@
<rect>
<x>0</x>
<y>0</y>
<width>630</width>
<width>600</width>
<height>200</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>200</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0,0,0">
@ -36,12 +24,6 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string/>
</property>
@ -123,7 +105,7 @@
<item>
<widget class="QToolButton" name="closeButton">
<property name="toolTip">
<string>Generate TOTP Token</string>
<string>Close</string>
</property>
<property name="text">
<string/>
@ -166,85 +148,19 @@
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="entryPage">
<layout class="QGridLayout" name="gridLayout" columnstretch="1,3">
<layout class="QGridLayout" name="gridLayout" columnstretch="0,0,1">
<item row="0" column="2">
<widget class="QLabel" name="usernameLabel">
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="passwordLabel"/>
</item>
<item row="2" column="1">
<widget class="QLabel" name="urlLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="expirationLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Expiration</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="urlLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>URL</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="expirationLabel"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="passwordLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
@ -267,6 +183,102 @@
</widget>
</item>
<item row="0" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="2">
<widget class="QLabel" name="expirationLabel"/>
</item>
<item row="4" column="2">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1">
<widget class="QLabel" name="urlLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>URL</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="urlLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="expirationLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Expiration</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="passwordLabel"/>
</item>
<item row="0" column="1">
<widget class="QLabel" name="usernameLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
@ -291,41 +303,11 @@
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="usernameLabel">
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="4" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="groupPage">
<layout class="QGridLayout" name="gridLayout_4" columnstretch="1,3">
<item row="2" column="1">
<widget class="QLabel" name="groupExpirationLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,1">
<item row="0" column="1">
<widget class="QLabel" name="autotypeLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
@ -347,7 +329,30 @@
</property>
</widget>
</item>
<item row="1" column="1">
<item row="2" column="2">
<widget class="QLabel" name="groupExpirationLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="3" column="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="2">
<widget class="QLabel" name="searchingLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
@ -357,7 +362,7 @@
</property>
</widget>
</item>
<item row="1" column="0">
<item row="1" column="1">
<widget class="QLabel" name="searchingLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
@ -379,7 +384,7 @@
</property>
</widget>
</item>
<item row="0" column="1">
<item row="0" column="2">
<widget class="QLabel" name="autotypeLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
@ -389,7 +394,7 @@
</property>
</widget>
</item>
<item row="2" column="0">
<item row="2" column="1">
<widget class="QLabel" name="groupExpirationLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
@ -411,15 +416,18 @@
</property>
</widget>
</item>
<item row="3" column="1">
<spacer name="verticalSpacer">
<item row="0" column="0">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
<height>20</height>
</size>
</property>
</spacer>
@ -434,27 +442,9 @@
<attribute name="title">
<string>Attributes</string>
</attribute>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTextEdit" name="attributesEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>80</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>80</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
@ -469,27 +459,9 @@
<attribute name="title">
<string>Notes</string>
</attribute>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTextEdit" name="notesEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>80</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>80</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
@ -504,8 +476,8 @@
<attribute name="title">
<string>Autotype</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QTreeWidget" name="autotypeTree">
<property name="frameShadow">
<enum>QFrame::Sunken</enum>

View File

@ -27,7 +27,7 @@ QString FileDialog::getOpenFileName(QWidget* parent, const QString& caption, QSt
{
if (!m_nextFileName.isEmpty()) {
QString result = m_nextFileName;
m_nextFileName = "";
m_nextFileName.clear();
return result;
}
else {
@ -43,11 +43,37 @@ QString FileDialog::getOpenFileName(QWidget* parent, const QString& caption, QSt
parent->activateWindow();
}
if (!result.isEmpty()) {
config()->set("LastDir", QFileInfo(result).absolutePath());
saveLastDir(result);
return result;
}
}
QStringList FileDialog::getOpenFileNames(QWidget *parent, const QString &caption, QString dir,
const QString &filter, QString *selectedFilter,
QFileDialog::Options options)
{
if (!m_nextFileNames.isEmpty()) {
QStringList results = m_nextFileNames;
m_nextFileNames.clear();
return results;
}
else {
if (dir.isEmpty()) {
dir = config()->get("LastDir").toString();
}
return result;
QStringList results = QFileDialog::getOpenFileNames(parent, caption, dir, filter,
selectedFilter, options);
// on Mac OS X the focus is lost after closing the native dialog
if (parent) {
parent->activateWindow();
}
if (!results.isEmpty()) {
saveLastDir(results[0]);
}
return results;
}
}
@ -57,7 +83,7 @@ QString FileDialog::getSaveFileName(QWidget* parent, const QString& caption, QSt
{
if (!m_nextFileName.isEmpty()) {
QString result = m_nextFileName;
m_nextFileName = "";
m_nextFileName.clear();
return result;
}
else {
@ -95,11 +121,33 @@ QString FileDialog::getSaveFileName(QWidget* parent, const QString& caption, QSt
parent->activateWindow();
}
if (!result.isEmpty()) {
config()->set("LastDir", QFileInfo(result).absolutePath());
saveLastDir(result);
return result;
}
}
QString FileDialog::getExistingDirectory(QWidget *parent, const QString &caption, QString dir,
QFileDialog::Options options)
{
if (!m_nextDirName.isEmpty()) {
QString result = m_nextDirName;
m_nextDirName.clear();
return result;
}
else {
if (dir.isEmpty()) {
dir = config()->get("LastDir").toString();
}
return result;
dir = QFileDialog::getExistingDirectory(parent, caption, dir, options);
// on Mac OS X the focus is lost after closing the native dialog
if (parent) {
parent->activateWindow();
}
saveLastDir(dir);
return dir;
}
}
@ -108,10 +156,33 @@ void FileDialog::setNextFileName(const QString& fileName)
m_nextFileName = fileName;
}
void FileDialog::setNextFileNames(const QStringList &fileNames)
{
m_nextFileNames = fileNames;
}
void FileDialog::setNextDirName(const QString &dirName)
{
m_nextDirName = dirName;
}
void FileDialog::setNextForgetDialog()
{
m_forgetLastDir = true;
}
FileDialog::FileDialog()
{
}
void FileDialog::saveLastDir(QString dir) {
if (!dir.isEmpty() && !m_forgetLastDir) {
config()->set("LastDir", QFileInfo(dir).absolutePath());
}
m_forgetLastDir = false;
}
FileDialog* FileDialog::instance()
{
if (!m_instance) {

View File

@ -26,22 +26,35 @@ public:
QString getOpenFileName(QWidget* parent = nullptr, const QString& caption = QString(),
QString dir = QString(), const QString& filter = QString(),
QString* selectedFilter = nullptr, QFileDialog::Options options = 0);
QStringList getOpenFileNames(QWidget* parent = nullptr, const QString& caption = QString(),
QString dir = QString(), const QString& filter = QString(),
QString* selectedFilter = nullptr, QFileDialog::Options options = 0);
QString getSaveFileName(QWidget* parent = nullptr, const QString& caption = QString(),
QString dir = QString(), const QString& filter = QString(),
QString* selectedFilter = nullptr, QFileDialog::Options options = 0,
const QString& defaultExtension = QString());
QString getExistingDirectory(QWidget* parent = nullptr, const QString& caption = QString(),
QString dir = QString(), QFileDialog::Options options = QFileDialog::ShowDirsOnly);
void setNextForgetDialog();
/**
* Sets the result of the next get* method call.
* Use only for testing.
*/
void setNextFileName(const QString& fileName);
void setNextFileNames(const QStringList& fileNames);
void setNextDirName(const QString& dirName);
static FileDialog* instance();
private:
FileDialog();
QString m_nextFileName;
QStringList m_nextFileNames;
QString m_nextDirName;
bool m_forgetLastDir = false;
void saveLastDir(QString);
static FileDialog* m_instance;

36
src/gui/Font.cpp Normal file
View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* 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 or (at your option)
* version 3 of the License.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "Font.h"
#include <QFontDatabase>
QFont Font::fixedFont()
{
QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
#ifdef Q_OS_WIN
// try to use Consolas on Windows, because the default Courier New has too many similar characters
QFont consolasFont = QFontDatabase().font("Consolas", fixedFont.styleName(), fixedFont.pointSize());
const QFont defaultFont;
if (fixedFont != defaultFont) {
fixedFont = consolasFont;
}
#endif
return fixedFont;
}

31
src/gui/Font.h Normal file
View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* 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 or (at your option)
* version 3 of the License.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_FONT_H
#define KEEPASSX_FONT_H
#include <QFont>
class Font
{
public:
static QFont fixedFont();
private:
Font() {}
};
#endif // KEEPASSX_FONT_H

View File

@ -49,6 +49,11 @@
#include "http/OptionDialog.h"
#endif
#ifdef WITH_XC_SSHAGENT
#include "sshagent/AgentSettingsPage.h"
#include "sshagent/SSHAgent.h"
#endif
#include "gui/SettingsWidget.h"
#include "gui/PasswordGeneratorWidget.h"
@ -109,6 +114,8 @@ MainWindow::MainWindow()
{
m_ui->setupUi(this);
m_ui->toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
// Setup the search widget in the toolbar
SearchWidget *search = new SearchWidget();
search->connectSignals(m_actionMultiplexer);
@ -121,6 +128,10 @@ MainWindow::MainWindow()
#ifdef WITH_XC_HTTP
m_ui->settingsWidget->addSettingsPage(new HttpPlugin(m_ui->tabWidget));
#endif
#ifdef WITH_XC_SSHAGENT
SSHAgent::init(this);
m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage(m_ui->tabWidget));
#endif
setWindowIcon(filePath()->applicationIcon());
m_ui->globalMessageWidget->setHidden(true);
@ -727,7 +738,9 @@ void MainWindow::changeEvent(QEvent* event)
void MainWindow::saveWindowInformation()
{
config()->set("GUI/MainWindowGeometry", saveGeometry());
if (isVisible()) {
config()->set("GUI/MainWindowGeometry", saveGeometry());
}
}
bool MainWindow::saveLastDatabases()
@ -811,11 +824,6 @@ void MainWindow::showGroupContextMenu(const QPoint& globalPos)
m_ui->menuGroups->popup(globalPos);
}
void MainWindow::saveToolbarState(bool value)
{
config()->set("ShowToolbar", value);
}
void MainWindow::setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback)
{
if (!QKeySequence::keyBindings(standard).isEmpty()) {
@ -858,6 +866,7 @@ void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason)
void MainWindow::hideWindow()
{
saveWindowInformation();
#ifndef Q_OS_MAC
setWindowState(windowState() | Qt::WindowMinimized);
#endif

View File

@ -87,7 +87,6 @@ private slots:
void updateCopyAttributesMenu();
void showEntryContextMenu(const QPoint& globalPos);
void showGroupContextMenu(const QPoint& globalPos);
void saveToolbarState(bool value);
void rememberOpenDatabases(const QString& filePath);
void applySettingsChanges();
void trayIconTriggered(QSystemTrayIcon::ActivationReason reason);

View File

@ -557,7 +557,7 @@
</action>
<action name="actionGroupEmptyRecycleBin">
<property name="text">
<string>Empty recycle bin</string>
<string>E&amp;mpty recycle bin</string>
</property>
<property name="visible">
<bool>false</bool>

View File

@ -19,8 +19,7 @@
#include "PasswordEdit.h"
#include "core/Config.h"
#include <QFontDatabase>
#include "gui/Font.h"
const QColor PasswordEdit::CorrectSoFarColor = QColor(255, 205, 15);
const QColor PasswordEdit::ErrorColor = QColor(255, 125, 125);
@ -31,9 +30,9 @@ PasswordEdit::PasswordEdit(QWidget* parent)
{
setEchoMode(QLineEdit::Password);
updateStylesheet();
// set font to system monospace font and increase letter spacing
QFont passwordFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
// use a monospace font for the password field
QFont passwordFont = Font::fixedFont();
passwordFont.setLetterSpacing(QFont::PercentageSpacing, 110);
setFont(passwordFont);
}

View File

@ -21,6 +21,7 @@
#include <QLineEdit>
#include <QDir>
#include <QKeyEvent>
#include "core/Config.h"
#include "core/PasswordGenerator.h"
@ -149,6 +150,19 @@ void PasswordGeneratorWidget::setStandaloneMode(bool standalone)
}
}
void PasswordGeneratorWidget::keyPressEvent(QKeyEvent* e)
{
if (!e->modifiers() || (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) {
if (e->key() == Qt::Key_Escape && m_standalone == true) {
emit dialogTerminated();
} else {
e->ignore();
}
} else {
e->ignore();
}
}
void PasswordGeneratorWidget::regeneratePassword()
{
if (m_ui->tabWidget->currentIndex() == Password) {

View File

@ -81,6 +81,9 @@ private:
const QScopedPointer<PasswordGenerator> m_passwordGenerator;
const QScopedPointer<PassphraseGenerator> m_dicewareGenerator;
const QScopedPointer<Ui::PasswordGeneratorWidget> m_ui;
protected:
void keyPressEvent(QKeyEvent* e) override;
};
#endif // KEEPASSX_PASSWORDGENERATORWIDGET_H

View File

@ -318,7 +318,7 @@ QProgressBar::chunk {
<string>Extended ASCII</string>
</property>
<property name="text">
<string notr="true">Extended ASCII</string>
<string>Extended ASCII</string>
</property>
<property name="checkable">
<bool>true</bool>

View File

@ -241,6 +241,7 @@ void SettingsWidget::saveSettings()
if (!config()->get("RememberLastKeyFiles").toBool()) {
config()->set("LastKeyFiles", QVariant());
config()->set("LastDir", "");
}
for (const ExtraPage& page: asConst(m_extraPages)) {

View File

@ -35,7 +35,9 @@ SetupTotpDialog::SetupTotpDialog(DatabaseWidget* parent, Entry* entry)
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close()));
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(setupTotp()));
connect(m_ui->customSettingsCheckBox, SIGNAL(toggled(bool)), SLOT(toggleCustom(bool)));
connect(m_ui->radioDefault, SIGNAL(toggled(bool)), SLOT(toggleDefault(bool)));
connect(m_ui->radioSteam, SIGNAL(toggled(bool)), SLOT(toggleSteam(bool)));
connect(m_ui->radioCustom, SIGNAL(toggled(bool)), SLOT(toggleCustom(bool)));
}
@ -43,19 +45,37 @@ void SetupTotpDialog::setupTotp()
{
quint8 digits;
if (m_ui->radio8Digits->isChecked()) {
if (m_ui->radioSteam->isChecked()) {
digits = Totp::ENCODER_STEAM;
} else if (m_ui->radio8Digits->isChecked()) {
digits = 8;
} else {
digits = 6;
}
quint8 step = m_ui->stepSpinBox->value();
QString seed = QTotp::parseOtpString(m_ui->seedEdit->text(), digits, step);
QString seed = Totp::parseOtpString(m_ui->seedEdit->text(), digits, step);
m_entry->setTotp(seed, step, digits);
emit m_parent->entrySelectionChanged();
close();
}
void SetupTotpDialog::toggleDefault(bool status)
{
if (status) {
setStep(Totp::defaultStep);
setDigits(Totp::defaultDigits);
}
}
void SetupTotpDialog::toggleSteam(bool status)
{
if (status) {
setStep(Totp::defaultStep);
setDigits(Totp::ENCODER_STEAM);
}
}
void SetupTotpDialog::toggleCustom(bool status)
{
m_ui->digitsLabel->setEnabled(status);
@ -72,13 +92,25 @@ void SetupTotpDialog::setSeed(QString value)
m_ui->seedEdit->setText(value);
}
void SetupTotpDialog::setSettings(quint8 digits) {
quint8 step = m_ui->stepSpinBox->value();
bool isDefault = ((step == Totp::defaultStep) &&
(digits == Totp::defaultDigits));
bool isSteam = (digits == Totp::ENCODER_STEAM);
if (isSteam) {
m_ui->radioSteam->setChecked(true);
} else if (isDefault) {
m_ui->radioDefault->setChecked(true);
} else {
m_ui->radioCustom->setChecked(true);
}
}
void SetupTotpDialog::setStep(quint8 step)
{
m_ui->stepSpinBox->setValue(step);
if (step != QTotp::defaultStep) {
m_ui->customSettingsCheckBox->setChecked(true);
}
}
void SetupTotpDialog::setDigits(quint8 digits)
@ -90,13 +122,8 @@ void SetupTotpDialog::setDigits(quint8 digits)
m_ui->radio6Digits->setChecked(true);
m_ui->radio8Digits->setChecked(false);
}
if (digits != QTotp::defaultDigits) {
m_ui->customSettingsCheckBox->setChecked(true);
}
}
SetupTotpDialog::~SetupTotpDialog()
{
}

View File

@ -39,8 +39,11 @@ public:
void setSeed(QString value);
void setStep(quint8 step);
void setDigits(quint8 digits);
void setSettings(quint8 digits);
private Q_SLOTS:
void toggleDefault(bool status);
void toggleSteam(bool status);
void toggleCustom(bool status);
void setupTotp();

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>282</width>
<height>257</height>
<height>364</height>
</rect>
</property>
<property name="windowTitle">
@ -29,11 +29,38 @@
</layout>
</item>
<item>
<widget class="QCheckBox" name="customSettingsCheckBox">
<property name="text">
<string>Use custom settings</string>
</property>
</widget>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="radioDefault">
<property name="text">
<string>Default RFC 6238 token settings</string>
</property>
<attribute name="buttonGroup">
<string notr="true">settingsButtonGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioSteam">
<property name="text">
<string>Steam token settings</string>
</property>
<attribute name="buttonGroup">
<string notr="true">settingsButtonGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioCustom">
<property name="text">
<string>Use custom settings</string>
</property>
<attribute name="buttonGroup">
<string notr="true">settingsButtonGroup</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_4">
@ -134,4 +161,7 @@
</widget>
<resources/>
<connections/>
<buttongroups>
<buttongroup name="settingsButtonGroup"/>
</buttongroups>
</ui>

Some files were not shown because too many files have changed in this diff Show More