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 - gcc
env: env:
- CONFIG=Release 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:leak_check_at_exit=0 - CONFIG=Debug ASAN_OPTIONS=detect_odr_violation=1
git: git:
depth: 3 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 .. - 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 - make -j2
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then make test ARGS+="-E testgui --output-on-failure"; fi - 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 - if [ "$TRAVIS_OS_NAME" = "osx" ]; then make test ARGS+="--output-on-failure"; fi
# Generate snapcraft build when merging into master/develop branches # Generate snapcraft build when merging into master/develop branches

View File

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

View File

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

View File

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

View File

@ -24,7 +24,8 @@ RUN set -x \
&& apt-get -y install software-properties-common && apt-get -y install software-properties-common
RUN set -x \ 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 \ RUN set -x \
&& apt-get update -y \ && apt-get update -y \
@ -42,7 +43,9 @@ RUN set -x \
zlib1g-dev \ zlib1g-dev \
libxi-dev \ libxi-dev \
libxtst-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 CMAKE_PREFIX_PATH=/opt/qt${QT5_VERSION}/lib/cmake
ENV LD_LIBRARY_PATH=/opt/qt${QT5_VERSION}/lib ENV LD_LIBRARY_PATH=/opt/qt${QT5_VERSION}/lib
@ -52,34 +55,8 @@ RUN set -x \
# AppImage dependencies # AppImage dependencies
RUN set -x \ RUN set -x \
&& apt-get install -y \ && apt-get install -y \
wget \ libfuse2 \
libfuse2 wget
# 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}*
VOLUME /keepassxc/src VOLUME /keepassxc/src
VOLUME /keepassxc/out 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. 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 You can visit the online version of this document at the following link:
https://github.com/keepassxreboot/keepassx/wiki/Install-Instruction-from-Source 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 Build Dependencies
================== ==================
@ -28,10 +30,9 @@ The following libraries are required:
Prepare the Building Environment Prepare the Building Environment
================================ ================================
* Building Environment on Linux ==> https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-Linux * [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 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 MacOS](https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-OS-X)
Build Steps 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/> 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 **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 mkdir build
cd build cd build
cmake -DWITH_TESTS=OFF cmake -DWITH_TESTS=OFF ...and other options - see below...
make 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_INSTALL_PREFIX=/usr/local -DCMAKE_VERBOSE_MAKEFILE=ON
-DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=<RelWithDebInfo/Debug/Release>
-DCMAKE_BUILD_TYPE=<RelWithDebInfo/Debug/Release> -DWITH_GUI_TESTS=ON
-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 Installation
============ ============
To install this binary execute the following: After you have successfully built KeePassXC, install the binary by executing the following:
```bash ```bash
sudo make install 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
[KeePassXC](https://keepassxc.org) is a cross-platform community fork of
## About [KeePassX](https://www.keepassx.org/).
[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. 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 ## Additional features compared to KeePassX
- Auto-Type on all three major platforms (Linux, Windows, macOS) - 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 - Using website favicons as entry icons
- Merging of databases - Merging of databases
- Automatic reload when the database changed on disk - 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 - Many bug fixes
For a full list of features and changes, read the [CHANGELOG](CHANGELOG) document. For a full list of features and changes, read the [CHANGELOG](CHANGELOG) document.
### Note about KeePassHTTP ## Building KeePassXC
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!
### Installation Detailed instructions are available in the [Build and Install](./INSTALL.md)
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. 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).* 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
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). 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.
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.
You can of course also directly contribute your own code. We are happy to accept your pull requests. 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. 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 local cmd
if [ "" == "$1" ] || [ "help" == "$1" ]; then if [ "" == "$1" ] || [ "help" == "$1" ]; then
cmd="COMMAND" 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" cmd="$1"
else else
logError "Unknown command: '$1'\n" logError "Unknown command: '$1'\n"
cmd="COMMAND" cmd="COMMAND"
fi 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 if [ "COMMAND" == "$cmd" ]; then
cat << EOF cat << EOF
@ -66,7 +66,8 @@ Commands:
check Perform a dry-run check, nothing is changed check Perform a dry-run check, nothing is changed
merge Merge release branch into main branch and create release tags merge Merge release branch into main branch and create release tags
build Build and package binary release from sources 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 help Show help for the given command
EOF EOF
elif [ "merge" == "$cmd" ]; then elif [ "merge" == "$cmd" ]; then
@ -113,16 +114,25 @@ Options:
-n, --no-source-tarball Don't build source tarball -n, --no-source-tarball Don't build source tarball
-h, --help Show this help -h, --help Show this help
EOF EOF
elif [ "sign" == "$cmd" ]; then elif [ "gpgsign" == "$cmd" ]; then
cat << EOF cat << EOF
Sign previously compiled release packages Sign previously compiled release packages with GPG
Options: Options:
-f, --files Files to sign (required) -f, --files Files to sign (required)
-g, --gpg-key GPG key used to sign the files (default: '${GPG_KEY}') -g, --gpg-key GPG key used to sign the files (default: '${GPG_KEY}')
--signtool Specify the signtool executable (default: 'signtool') -h, --help Show this help
--signtool-key Provide a key to be used with signtool (for Windows EXE) 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 -h, --help Show this help
EOF EOF
fi fi
@ -264,13 +274,13 @@ checkChangeLog() {
} }
checkAppStreamInfo() { 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!" exitError "No AppStream info file found!"
fi 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 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 fi
} }
@ -289,7 +299,28 @@ checkSnapcraft() {
checkTransifexCommandExists() { checkTransifexCommandExists() {
command -v tx > /dev/null command -v tx > /dev/null
if [ 0 -ne $? ]; then 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 fi
} }
@ -665,81 +696,54 @@ build() {
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# sign command # gpgsign command
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
sign() { gpgsign() {
SIGN_FILES=() local sign_files=()
SIGNTOOL="signtool"
SIGNTOOL_KEY=""
while [ $# -ge 1 ]; do while [ $# -ge 1 ]; do
local arg="$1" local arg="$1"
case "$arg" in case "$arg" in
-f|--files) -f|--files)
while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
SIGN_FILES+=("$2") sign_files+=("$2")
shift shift
done ;; done ;;
-g|--gpg-key) -g|--gpg-key)
GPG_KEY="$2" GPG_KEY="$2"
shift ;; shift ;;
--signtool)
SIGNTOOL="$2"
shift ;;
--signtool-key)
SIGNTOOL_KEY="$2"
shift ;;
-h|--help) -h|--help)
printUsage "sign" printUsage "gpgsign"
exit ;; exit ;;
*) *)
logError "Unknown option '$arg'\n" logError "Unknown option '$arg'\n"
printUsage "sign" printUsage "gpgsign"
exit 1 ;; exit 1 ;;
esac esac
shift shift
done done
if [ -z "$SIGN_FILES" ]; then if [ -z "${sign_files}" ]; then
logError "Missing arguments, --files is required!\n" logError "Missing arguments, --files is required!\n"
printUsage "sign" printUsage "gpgsign"
exit 1 exit 1
fi fi
if [[ -n "$SIGNTOOL_KEY" && ! -f "$SIGNTOOL_KEY" ]]; then for f in "${sign_files[@]}"; do
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
if [ ! -f "$f" ]; then if [ ! -f "$f" ]; then
exitError "File '${f}' does not exist!" exitError "File '${f}' does not exist or is not a file!"
fi 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..." logInfo "Signing file '${f}' using release key..."
gpg --output "${f}.sig" --armor --local-user "$GPG_KEY" --detach-sig "$f" gpg --output "${f}.sig" --armor --local-user "$GPG_KEY" --detach-sig "$f"
if [ 0 -ne $? ]; then if [ 0 -ne $? ]; then
exitError "Signing failed!" exitError "Signing failed!"
fi fi
logInfo "Creating digest for file '${f}'..." logInfo "Creating digest for file '${f}'..."
local rp="$(realpath "$f")" local rp="$(realpath "$f")"
local bname="$(basename "$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 # parse global command line
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@ -762,8 +903,8 @@ if [ "" == "$MODE" ]; then
elif [ "help" == "$MODE" ]; then elif [ "help" == "$MODE" ]; then
printUsage "$1" printUsage "$1"
exit exit
elif [ "check" == "$MODE" ] || [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] || [ "sign" == "$MODE" ]; then elif [ "check" == "$MODE" ] || [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] || [ "gpgsign" == "$MODE" ] || [ "appsign" == "$MODE" ]; then
$MODE "$@" ${MODE} "$@"
else else
printUsage "$MODE" printUsage "$MODE"
fi fi

View File

@ -30,8 +30,8 @@ if(UNIX AND NOT APPLE)
install(DIRECTORY icons/application/ DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor install(DIRECTORY icons/application/ DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor
FILES_MATCHING PATTERN "application-x-keepassxc.png" PATTERN "application-x-keepassxc.svgz" FILES_MATCHING PATTERN "application-x-keepassxc.png" PATTERN "application-x-keepassxc.svgz"
PATTERN "status" EXCLUDE PATTERN "actions" EXCLUDE PATTERN "categories" EXCLUDE) 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.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.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
install(FILES linux/keepassxc.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages) install(FILES linux/keepassxc.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages)
endif(UNIX AND NOT APPLE) 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] [General]
ShowToolbar=true
RememberLastDatabases=true RememberLastDatabases=true
RememberLastKeyFiles=true RememberLastKeyFiles=true
OpenPreviousDatabasesOnStartup=true OpenPreviousDatabasesOnStartup=true

View File

@ -1,16 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?> <?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"> <component type="desktop-application">
<id>org.keepassxc</id> <id>org.keepassxc.KeePassXC.desktop</id>
<name>KeePassXC</name> <name>KeePassXC</name>
<metadata_license>CC-BY-3.0</metadata_license> <metadata_license>CC-BY-3.0</metadata_license>
<project_license>GPL-3.0+</project_license> <project_license>GPL-3.0+</project_license>
<icon type="stock">keepassxc</icon>
<url type="homepage">https://keepassxc.org</url>
<mimetypes> <mimetypes>
<mimetype>application/x-keepass2</mimetype> <mimetype>application/x-keepass2</mimetype>
</mimetypes> </mimetypes>
<summary>Community-driven port of the Windows application “KeePass Password Safe”</summary> <summary>Community-driven port of the Windows application “KeePass Password Safe”</summary>
<developer_name>KeePassXC Team</developer_name>
<description> <description>
<p> <p>
KeePassXC is an application for people with extremely high demands on secure KeePassXC is an application for people with extremely high demands on secure
@ -19,56 +18,53 @@
</p> </p>
</description> </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> <screenshots>
<screenshot type="default"> <screenshot type="default">
<image>https://keepassxc.org/images/screenshots/linux/screen_001.png</image> <image>https://keepassxc.org/images/screenshots/linux/screen_001.png</image>
<caption>Create, Import or Open Databases</caption>
</screenshot> </screenshot>
<screenshot> <screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_002.png</image> <image>https://keepassxc.org/images/screenshots/linux/screen_002.png</image>
<caption>Organize with Groups and Entries</caption>
</screenshot> </screenshot>
<screenshot> <screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_003.png</image> <image>https://keepassxc.org/images/screenshots/linux/screen_003.png</image>
<caption>Database Entry</caption>
</screenshot> </screenshot>
<screenshot> <screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_004.png</image> <image>https://keepassxc.org/images/screenshots/linux/screen_004.png</image>
</screenshot> <caption>Icon Selection for Entry</caption>
<screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_005.png</image>
</screenshot> </screenshot>
<screenshot> <screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_006.png</image> <image>https://keepassxc.org/images/screenshots/linux/screen_006.png</image>
</screenshot> <caption>Password Generator</caption>
<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>
</screenshot> </screenshot>
</screenshots> </screenshots>
<releases> <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"> <release version="2.2.2" date="2017-10-22">
<description> <description>
<p>Changes included in this release:</p>
<ul> <ul>
<li>Fixed entries with empty URLs being reported to KeePassHTTP clients [#1031]</li> <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> <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> <li>Fixed screen lock and Google fallback settings [#1029]</li>
</ul> </ul>
</description> </description>
</release><release version="2.2.1" date="2017-10-01"> </release>
<release version="2.2.1" date="2017-10-01">
<description> <description>
<p>Changes included in this release:</p>
<ul> <ul>
<li>Corrected multiple snap issues [#934, #1011]</li> <li>Corrected multiple snap issues [#934, #1011]</li>
<li>Corrected multiple custom icon issues [#708, #719, #994]</li> <li>Corrected multiple custom icon issues [#708, #719, #994]</li>
@ -103,6 +101,7 @@
</release> </release>
<release version="2.2.0" date="2017-06-23"> <release version="2.2.0" date="2017-06-23">
<description> <description>
<p>Changes included in this release:</p>
<ul> <ul>
<li>Added YubiKey 2FA integration for unlocking databases [#127]</li> <li>Added YubiKey 2FA integration for unlocking databases [#127]</li>
<li>Added TOTP support [#519]</li> <li>Added TOTP support [#519]</li>
@ -138,6 +137,7 @@
</release> </release>
<release version="2.1.4" date="2017-04-09"> <release version="2.1.4" date="2017-04-09">
<description> <description>
<p>Changes included in this release:</p>
<ul> <ul>
<li>Bumped KeePassHTTP version to 1.8.4.2</li> <li>Bumped KeePassHTTP version to 1.8.4.2</li>
<li>KeePassHTTP confirmation window comes to foreground [#466]</li> <li>KeePassHTTP confirmation window comes to foreground [#466]</li>
@ -146,6 +146,7 @@
</release> </release>
<release version="2.1.3" date="2017-03-03"> <release version="2.1.3" date="2017-03-03">
<description> <description>
<p>Changes included in this release:</p>
<ul> <ul>
<li>Fix possible overflow in zxcvbn library [#363]</li> <li>Fix possible overflow in zxcvbn library [#363]</li>
<li>Revert HiDPI setting to avoid problems on laptop screens [#332]</li> <li>Revert HiDPI setting to avoid problems on laptop screens [#332]</li>
@ -160,6 +161,7 @@
</release> </release>
<release version="2.1.2" date="2017-02-17"> <release version="2.1.2" date="2017-02-17">
<description> <description>
<p>Changes included in this release:</p>
<ul> <ul>
<li>Ask for save location when creating a new database [#302]</li> <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> <li>Remove Libmicrohttpd dependency to clean up the code and ensure better OS X compatibility [#317, #265]</li>
@ -177,6 +179,7 @@
</release> </release>
<release version="2.1.1" date="2017-02-06"> <release version="2.1.1" date="2017-02-06">
<description> <description>
<p>Changes included in this release:</p>
<ul> <ul>
<li>Enabled HTTP plugin build; plugin is disabled by default and limited to localhost [#147]</li> <li>Enabled HTTP plugin build; plugin is disabled by default and limited to localhost [#147]</li>
<li>Escape HTML in dialog boxes [#247]</li> <li>Escape HTML in dialog boxes [#247]</li>
@ -189,6 +192,7 @@
</release> </release>
<release version="2.1.0" date="2017-01-22"> <release version="2.1.0" date="2017-01-22">
<description> <description>
<p>Changes included in this release:</p>
<ul> <ul>
<li>Show unlock dialog when using autotype on a closed database [#10, #89]</li> <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> <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 Exec=keepassxc %f
TryExec=keepassxc TryExec=keepassxc
Icon=keepassxc Icon=keepassxc
StartupWMClass=keepassxc
Terminal=false Terminal=false
Type=Application Type=Application
Version=1.0 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? Do you want to save the database before locking it?
Otherwise your changes are lost.</source> Otherwise your changes are lost.</source>
<translation>Dieses Datenbank wurde geändert. <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> Anderenfalls gehen Ihre Änderungen verloren.</translation>
</message> </message>
<message> <message>

View File

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

View File

@ -29,27 +29,27 @@
</message> </message>
<message> <message>
<source>Revision: %1</source> <source>Revision: %1</source>
<translation>Revision: %1</translation> <translation>Révision : %1</translation>
</message> </message>
<message> <message>
<source>Libraries:</source> <source>Libraries:</source>
<translation>Bibliothèques:</translation> <translation>Bibliothèques :</translation>
</message> </message>
<message> <message>
<source>Operating system: %1 <source>Operating system: %1
CPU architecture: %2 CPU architecture: %2
Kernel: %3 %4</source> Kernel: %3 %4</source>
<translation>Système d&apos;exploitation: %1 <translation>Système d&apos;exploitation : %1
Architecture CPU: %2 Architecture CPU : %2
Kernel: %3 %4</translation> Kernel : %3 %4</translation>
</message> </message>
<message> <message>
<source>Enabled extensions:</source> <source>Enabled extensions:</source>
<translation>Extensions activées:</translation> <translation>Extensions activées :</translation>
</message> </message>
<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> <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>
<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> <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>
<message> <message>
<source>Project Maintainers:</source> <source>Project Maintainers:</source>
<translation>Mainteneurs du projet:</translation> <translation>Mainteneurs du projet :</translation>
</message> </message>
<message> <message>
<source>&lt;a href=&quot;https://github.com/keepassxreboot/keepassxc/graphs/contributors&quot;&gt;See Contributions on GitHub&lt;/a&gt;</source> <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>
<message> <message>
<source>Include the following information whenever you report a bug:</source> <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>
<message> <message>
<source>Distribution: %1</source> <source>Distribution: %1</source>
<translation type="unfinished"/> <translation>Distribution : %1</translation>
</message> </message>
</context> </context>
<context> <context>
@ -302,7 +302,7 @@ Veuillez sélectionner si vous souhaitez autoriser laccès.</translation>
</message> </message>
<message> <message>
<source>Original data: </source> <source>Original data: </source>
<translation>Données originales:</translation> <translation>Données originales :</translation>
</message> </message>
<message> <message>
<source>Error(s) detected in CSV file !</source> <source>Error(s) detected in CSV file !</source>
@ -397,7 +397,7 @@ Veuillez sélectionner si vous souhaitez autoriser laccès.</translation>
</message> </message>
<message> <message>
<source>Challenge Response:</source> <source>Challenge Response:</source>
<translation>Challenge-réponse:</translation> <translation>Challenge-réponse :</translation>
</message> </message>
</context> </context>
<context> <context>
@ -477,15 +477,15 @@ Vous pouvez maintenant la sauvegarder.</translation>
</message> </message>
<message> <message>
<source>AES: 256 Bit (default)</source> <source>AES: 256 Bit (default)</source>
<translation>AES: 256 Bits (par défault)</translation> <translation>AES : 256 Bits (par défaut)</translation>
</message> </message>
<message> <message>
<source>Twofish: 256 Bit</source> <source>Twofish: 256 Bit</source>
<translation>Twofish: 256 bits</translation> <translation>Twofish : 256 bits</translation>
</message> </message>
<message> <message>
<source>Algorithm:</source> <source>Algorithm:</source>
<translation>Algorithme:</translation> <translation>Algorithme :</translation>
</message> </message>
</context> </context>
<context> <context>
@ -737,7 +737,7 @@ Voulez vous l&apos;ouvrir quand même ?</translation>
</message> </message>
<message> <message>
<source>Could not open the new database file while attempting to autoreload this database.</source> <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>
<message> <message>
<source>Empty recycle bin?</source> <source>Empty recycle bin?</source>
@ -1095,11 +1095,11 @@ Voulez vous l&apos;ouvrir quand même ?</translation>
</message> </message>
<message> <message>
<source>Hint: You can enable Google as a fallback under Tools&gt;Settings&gt;Security</source> <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>
<message> <message>
<source>Custom icon already exists</source> <source>Custom icon already exists</source>
<translation type="unfinished"/> <translation>L&apos;icône personnalisée existe déjà</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1175,7 +1175,7 @@ Voulez vous l&apos;ouvrir quand même ?</translation>
<message> <message>
<source>Ref: </source> <source>Ref: </source>
<comment>Reference abbreviation</comment> <comment>Reference abbreviation</comment>
<translation>Réf:</translation> <translation>Réf : </translation>
</message> </message>
</context> </context>
<context> <context>
@ -1353,7 +1353,7 @@ Il s&apos;agit d&apos;une migration à sens unique. Vous ne pourrez pas ouvrir l
</message> </message>
<message> <message>
<source>Existing single-instance lock file is invalid. Launching new instance.</source> <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> </message>
</context> </context>
<context> <context>
@ -1647,11 +1647,11 @@ Il s&apos;agit d&apos;une migration à sens unique. Vous ne pourrez pas ouvrir l
</message> </message>
<message> <message>
<source>HTTP Port:</source> <source>HTTP Port:</source>
<translation>Port HTTP:</translation> <translation>Port HTTP :</translation>
</message> </message>
<message> <message>
<source>Default port: 19455</source> <source>Default port: 19455</source>
<translation>Port par défaut: 19455</translation> <translation>Port par défaut : 19455</translation>
</message> </message>
<message> <message>
<source>Re&amp;quest to unlock the database if it is locked</source> <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>
<message> <message>
<source>&amp;Return advanced string fields which start with &quot;KPH: &quot;</source> <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>
<message> <message>
<source>Automatically creating or updating string fields is not supported.</source> <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>
<message> <message>
<source>&amp;Length:</source> <source>&amp;Length:</source>
<translation>&amp;Longueur:</translation> <translation>&amp;Longueur :</translation>
</message> </message>
<message> <message>
<source>Pick characters from every group</source> <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>
<message> <message>
<source>Entropy: %1 bit</source> <source>Entropy: %1 bit</source>
<translation>Entropie: %1 bit</translation> <translation>Entropie : %1 bit</translation>
</message> </message>
<message> <message>
<source>Password Quality: %1</source> <source>Password Quality: %1</source>
<translation>Qualité du mot de passe: %1</translation> <translation>Qualité du mot de passe : %1</translation>
</message> </message>
<message> <message>
<source>Poor</source> <source>Poor</source>
@ -1830,15 +1830,15 @@ Ne les changez que si vous savez ce que vous faites.</translation>
</message> </message>
<message> <message>
<source>Wordlist:</source> <source>Wordlist:</source>
<translation>Liste de mots:</translation> <translation>Liste de mots :</translation>
</message> </message>
<message> <message>
<source>Word Count:</source> <source>Word Count:</source>
<translation>Nombre de mots:</translation> <translation>Nombre de mots :</translation>
</message> </message>
<message> <message>
<source>Word Separator:</source> <source>Word Separator:</source>
<translation>Séparateur de mot:</translation> <translation>Séparateur de mot :</translation>
</message> </message>
<message> <message>
<source>Copy</source> <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>
<message> <message>
<source>KeePassXC: New key association request</source> <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>
<message> <message>
<source>You have received an association request for the above key. <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>
<message> <message>
<source>KeePassXC: Overwrite existing key?</source> <source>KeePassXC: Overwrite existing key?</source>
<translation>KeePassXC: Écraser la clé existante ?</translation> <translation>KeePassXC : Écraser la clé existante ?</translation>
</message> </message>
<message> <message>
<source>KeePassXC: Update Entry</source> <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>
<message> <message>
<source>KeePassXC: Database locked!</source> <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>
<message> <message>
<source>KeePassXC: Removed keys from database</source> <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>
<message> <message>
<source>KeePassXC: No keys found</source> <source>KeePassXC: No keys found</source>
<translation>KeePassXC: Aucune clé trouvée</translation> <translation>KeePassXC : Aucune clé trouvée</translation>
</message> </message>
<message> <message>
<source>KeePassXC: Settings not available!</source> <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>
<message> <message>
<source>KeePassXC: Removed permissions</source> <source>KeePassXC: Removed permissions</source>
<translation>KeePassXC: Permissions retirées</translation> <translation>KeePassXC : Permissions retirées</translation>
</message> </message>
<message> <message>
<source>KeePassXC: No entry with permissions found!</source> <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> </message>
</context> </context>
<context> <context>
@ -2145,7 +2145,7 @@ attribuez lui un nom unique pour l&apos;identifier et acceptez la.</translation>
</message> </message>
<message> <message>
<source>Don&apos;t mark database as modified for non-data changes (e.g., expanding groups)</source> <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>
<message> <message>
<source>Auto-Type</source> <source>Auto-Type</source>
@ -2227,7 +2227,7 @@ attribuez lui un nom unique pour l&apos;identifier et acceptez la.</translation>
</message> </message>
<message> <message>
<source>Key:</source> <source>Key:</source>
<translation>Clé:</translation> <translation>Clé :</translation>
</message> </message>
<message> <message>
<source>Use custom settings</source> <source>Use custom settings</source>
@ -2235,11 +2235,11 @@ attribuez lui un nom unique pour l&apos;identifier et acceptez la.</translation>
</message> </message>
<message> <message>
<source>Note: Change these settings only if you know what you are doing.</source> <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>
<message> <message>
<source>Time step:</source> <source>Time step:</source>
<translation>Période de temps:</translation> <translation>Période de temps :</translation>
</message> </message>
<message> <message>
<source>8 digits</source> <source>8 digits</source>
@ -2251,7 +2251,7 @@ attribuez lui un nom unique pour l&apos;identifier et acceptez la.</translation>
</message> </message>
<message> <message>
<source>Code size:</source> <source>Code size:</source>
<translation>Taille du code:</translation> <translation>Taille du code :</translation>
</message> </message>
<message> <message>
<source> sec</source> <source> sec</source>
@ -2375,7 +2375,7 @@ attribuez lui un nom unique pour l&apos;identifier et acceptez la.</translation>
</message> </message>
<message> <message>
<source>Path of the group to list. Default is /</source> <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>
<message> <message>
<source>Print the UUIDs of the entries and groups.</source> <source>Print the UUIDs of the entries and groups.</source>

View File

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

View File

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

View File

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

View File

@ -69,7 +69,7 @@ CPU 아키텍처: %2
</message> </message>
<message> <message>
<source>Distribution: %1</source> <source>Distribution: %1</source>
<translation type="unfinished"/> <translation>: %1</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1096,7 +1096,7 @@ Do you want to open it anyway?</source>
</message> </message>
<message> <message>
<source>Custom icon already exists</source> <source>Custom icon already exists</source>
<translation type="unfinished"/> <translation> </translation>
</message> </message>
</context> </context>
<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>
<message> <message>
<source>Existing single-instance lock file is invalid. Launching new instance.</source> <source>Existing single-instance lock file is invalid. Launching new instance.</source>
<translation type="unfinished"/> <translation> . .</translation>
</message> </message>
</context> </context>
<context> <context>

View File

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

View File

@ -69,7 +69,7 @@ Kernel: %3 %4</source>
</message> </message>
<message> <message>
<source>Distribution: %1</source> <source>Distribution: %1</source>
<translation type="unfinished"/> <translation>%1</translation>
</message> </message>
</context> </context>
<context> <context>
@ -196,7 +196,7 @@ Please select whether you want to allow access.</source>
</message> </message>
<message> <message>
<source>Cha&amp;llenge Response</source> <source>Cha&amp;llenge Response</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Refresh</source> <source>Refresh</source>
@ -208,7 +208,7 @@ Please select whether you want to allow access.</source>
</message> </message>
<message> <message>
<source>Changing master key failed: no YubiKey inserted.</source> <source>Changing master key failed: no YubiKey inserted.</source>
<translation type="unfinished"/> <translation> YubiKey</translation>
</message> </message>
</context> </context>
<context> <context>
@ -397,7 +397,7 @@ Please select whether you want to allow access.</source>
</message> </message>
<message> <message>
<source>Challenge Response:</source> <source>Challenge Response:</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
</context> </context>
<context> <context>
@ -1098,7 +1098,7 @@ Do you want to open it anyway?</source>
</message> </message>
<message> <message>
<source>Custom icon already exists</source> <source>Custom icon already exists</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
</context> </context>
<context> <context>
@ -2140,7 +2140,7 @@ give it a unique name to identify and accept it.</source>
</message> </message>
<message> <message>
<source>Remember last key files and security dongles</source> <source>Remember last key files and security dongles</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Don&apos;t mark database as modified for non-data changes (e.g., expanding groups)</source> <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 name: keepassxc
version: 2.2.2 version: 2.2.4
grade: stable grade: stable
summary: Community-driven port of the Windows application “KeePass Password Safe” summary: Community-driven port of the Windows application “KeePass Password Safe”
description: | description: |
@ -12,7 +12,7 @@ apps:
keepassxc: keepassxc:
command: desktop-launch keepassxc command: desktop-launch keepassxc
plugs: [unity7, x11, opengl, gsettings, home, network, network-bind, removable-media, raw-usb] 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: cli:
command: keepassxc-cli command: keepassxc-cli
plugs: [gsettings, home, removable-media, raw-usb] plugs: [gsettings, home, removable-media, raw-usb]
@ -41,8 +41,10 @@ parts:
- libxtst-dev - libxtst-dev
- libyubikey-dev - libyubikey-dev
- libykpers-1-dev - libykpers-1-dev
stage-packages:
- dbus
install: | 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] after: [desktop-qt5]
# Redefine desktop-qt5 stage packages to work with Ubuntu 17.04 # Redefine desktop-qt5 stage packages to work with Ubuntu 17.04
@ -52,19 +54,12 @@ parts:
- ttf-ubuntu-font-family - ttf-ubuntu-font-family
- dmz-cursor-theme - dmz-cursor-theme
- light-themes - light-themes
- adwaita-icon-theme
- gnome-themes-standard
- shared-mime-info - shared-mime-info
- libqt5gui5 - libqt5gui5
- libgdk-pixbuf2.0-0 - libgdk-pixbuf2.0-0
- libqt5svg5 # for loading icon themes which are svg - libqt5svg5 # for loading icon themes which are svg
- locales-all - 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/EditWidgetIcons.cpp
gui/EditWidgetProperties.cpp gui/EditWidgetProperties.cpp
gui/FileDialog.cpp gui/FileDialog.cpp
gui/Font.cpp
gui/IconModels.cpp gui/IconModels.cpp
gui/KeePass1OpenWidget.cpp gui/KeePass1OpenWidget.cpp
gui/KMessageWidget.cpp gui/KMessageWidget.cpp
@ -155,6 +156,8 @@ if(APPLE)
set(keepassx_SOURCES ${keepassx_SOURCES} set(keepassx_SOURCES ${keepassx_SOURCES}
core/ScreenLockListenerMac.h core/ScreenLockListenerMac.h
core/ScreenLockListenerMac.cpp core/ScreenLockListenerMac.cpp
core/MacPasteboard.h
core/MacPasteboard.cpp
) )
endif() endif()
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") 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(AutoType WITH_XC_AUTOTYPE "Automatic password typing")
add_feature_info(KeePassHTTP WITH_XC_HTTP "Browser integration compatible with ChromeIPass and PassIFox") 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(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) add_subdirectory(http)
if(WITH_XC_HTTP) if(WITH_XC_HTTP)
@ -186,6 +190,11 @@ endif()
add_subdirectory(autotype) add_subdirectory(autotype)
add_subdirectory(cli) add_subdirectory(cli)
add_subdirectory(sshagent)
if(WITH_XC_SSHAGENT)
set(sshagent_LIB sshagent)
endif()
set(autotype_SOURCES set(autotype_SOURCES
core/Tools.cpp core/Tools.cpp
autotype/AutoType.cpp autotype/AutoType.cpp
@ -222,6 +231,7 @@ set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUIL
target_link_libraries(keepassx_core target_link_libraries(keepassx_core
${keepasshttp_LIB} ${keepasshttp_LIB}
${autotype_LIB} ${autotype_LIB}
${sshagent_LIB}
${YUBIKEY_LIBRARIES} ${YUBIKEY_LIBRARIES}
${ZXCVBN_LIBRARIES} ${ZXCVBN_LIBRARIES}
Qt5::Core Qt5::Core

View File

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

View File

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

View File

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

View File

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

View File

@ -23,9 +23,9 @@
#include "Command.h" #include "Command.h"
#include "Add.h" #include "Add.h"
#include "Edit.h"
#include "Clip.h" #include "Clip.h"
#include "EntropyMeter.h" #include "Edit.h"
#include "Estimate.h"
#include "Extract.h" #include "Extract.h"
#include "List.h" #include "List.h"
#include "Locate.h" #include "Locate.h"
@ -62,7 +62,7 @@ void populateCommands()
commands.insert(QString("add"), new Add()); commands.insert(QString("add"), new Add());
commands.insert(QString("clip"), new Clip()); commands.insert(QString("clip"), new Clip());
commands.insert(QString("edit"), new Edit()); 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("extract"), new Extract());
commands.insert(QString("locate"), new Locate()); commands.insert(QString("locate"), new Locate());
commands.insert(QString("ls"), new List()); 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef KEEPASSXC_ENTROPYMETER_H #ifndef KEEPASSXC_ESTIMATE_H
#define KEEPASSXC_ENTROPYMETER_H #define KEEPASSXC_ESTIMATE_H
#include "Command.h" #include "Command.h"
class EntropyMeter : public Command class Estimate : public Command
{ {
public: public:
EntropyMeter(); Estimate();
~EntropyMeter(); ~Estimate();
int execute(QStringList arguments); 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); compositeKey.addKey(fileKey);
} }
QString databaseFilename = args.at(0); QString databaseFilename = args.at(0);
QFile dbFile(databaseFilename); QFile dbFile(databaseFilename);
if (!dbFile.exists()) { if (!dbFile.exists()) {

View File

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

View File

@ -83,5 +83,4 @@ int Locate::locateEntry(Database* database, QString searchTerm)
outputTextStream << result << endl; outputTextStream << result << endl;
} }
return EXIT_SUCCESS; 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("database1", QObject::tr("Path of the database to merge into."));
parser.addPositionalArgument("database2", QObject::tr("Path of the database to merge from.")); parser.addPositionalArgument("database2", QObject::tr("Path of the database to merge from."));
QCommandLineOption samePasswordOption( QCommandLineOption samePasswordOption(QStringList() << "s"
QStringList() << "s" << "same-credentials",
<< "same-credentials", QObject::tr("Use the same credentials for both database files."));
QObject::tr("Use the same credentials for both database files."));
QCommandLineOption keyFile(QStringList() << "k" QCommandLineOption keyFile(QStringList() << "k"
<< "key-file", << "key-file",
@ -55,8 +54,8 @@ int Merge::execute(QStringList arguments)
parser.addOption(keyFile); parser.addOption(keyFile);
QCommandLineOption keyFileFrom(QStringList() << "f" QCommandLineOption keyFileFrom(QStringList() << "f"
<< "key-file-from", << "key-file-from",
QObject::tr("Key file of the database to merge from."), QObject::tr("Key file of the database to merge from."),
QObject::tr("path")); QObject::tr("path"));
parser.addOption(keyFileFrom); parser.addOption(keyFileFrom);
parser.addOption(samePasswordOption); parser.addOption(samePasswordOption);
@ -68,7 +67,6 @@ int Merge::execute(QStringList arguments)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
Database* db1 = Database::unlockFromStdin(args.at(0), parser.value(keyFile)); Database* db1 = Database::unlockFromStdin(args.at(0), parser.value(keyFile));
if (db1 == nullptr) { if (db1 == nullptr) {
return EXIT_FAILURE; return EXIT_FAILURE;

View File

@ -49,6 +49,13 @@ int Show::execute(QStringList arguments)
QObject::tr("Key file of the database."), QObject::tr("Key file of the database."),
QObject::tr("path")); QObject::tr("path"));
parser.addOption(keyFile); 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.addPositionalArgument("entry", QObject::tr("Name of the entry to show."));
parser.process(arguments); parser.process(arguments);
@ -63,10 +70,10 @@ int Show::execute(QStringList arguments)
return EXIT_FAILURE; 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); QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
@ -78,10 +85,24 @@ int Show::showEntry(Database* database, QString entryPath)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
outputTextStream << " title: " << entry->title() << endl; // If no attributes specified, output the default attribute set.
outputTextStream << "username: " << entry->username() << endl; bool showAttributeNames = attributes.isEmpty();
outputTextStream << "password: " << entry->password() << endl; if (attributes.isEmpty()) {
outputTextStream << " URL: " << entry->url() << endl; attributes = EntryAttributes::DefaultAttributes;
outputTextStream << " Notes: " << entry->notes() << endl; }
return EXIT_SUCCESS;
// 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();
~Show(); ~Show();
int execute(QStringList arguments); int execute(QStringList arguments);
int showEntry(Database* database, QString entryPath); int showEntry(Database* database, QStringList attributes, QString entryPath);
}; };
#endif // KEEPASSXC_SHOW_H #endif // KEEPASSXC_SHOW_H

View File

@ -27,7 +27,6 @@
#include <QProcess> #include <QProcess>
#include <QTextStream> #include <QTextStream>
void Utils::setStdinEcho(bool enable = true) void Utils::setStdinEcho(bool enable = true)
{ {
#ifdef Q_OS_WIN #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>" .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). 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 ...]" .IP "estimate [options] [password]"
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. 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>" .IP "extract [options] <database>"
Extracts and prints the contents of a database to standard output in XML format. 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. 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 .SH REPORTING BUGS
Bugs and feature requests can be reported on GitHub at https://github.com/keepassxreboot/keepassxc/issues. 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_HTTP
#cmakedefine WITH_XC_AUTOTYPE #cmakedefine WITH_XC_AUTOTYPE
#cmakedefine WITH_XC_YUBIKEY #cmakedefine WITH_XC_YUBIKEY
#cmakedefine WITH_XC_SSHAGENT
#cmakedefine KEEPASSXC_DIST #cmakedefine KEEPASSXC_DIST
#cmakedefine KEEPASSXC_DIST_TYPE "@KEEPASSXC_DIST_TYPE@" #cmakedefine KEEPASSXC_DIST_TYPE "@KEEPASSXC_DIST_TYPE@"

View File

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

View File

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

View File

@ -95,10 +95,15 @@ const Metadata* Database::metadata() const
Entry* Database::resolveEntry(const Uuid& uuid) 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(); const QList<Entry*> entryList = group->entries();
for (Entry* entry : entryList) { for (Entry* entry : entryList) {
@ -109,7 +114,57 @@ Entry* Database::recFindEntry(const Uuid& uuid, Group* group)
const QList<Group*> children = group->children(); const QList<Group*> children = group->children();
for (Group* child : 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) { if (result) {
return result; return result;
} }
@ -120,10 +175,10 @@ Entry* Database::recFindEntry(const Uuid& uuid, Group* group)
Group* Database::resolveGroup(const Uuid& uuid) 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) { if (group->uuid() == uuid) {
return group; return group;
@ -131,7 +186,7 @@ Group* Database::recFindGroup(const Uuid& uuid, Group* group)
const QList<Group*> children = group->children(); const QList<Group*> children = group->children();
for (Group* child : children) { for (Group* child : children) {
Group* result = recFindGroup(uuid, child); Group* result = findGroupRecursive(uuid, child);
if (result) { if (result) {
return result; return result;
} }

View File

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

View File

@ -25,6 +25,8 @@
#include "core/Metadata.h" #include "core/Metadata.h"
#include "totp/totp.h" #include "totp/totp.h"
#include <QRegularExpression>
const int Entry::DefaultIconNumber = 0; const int Entry::DefaultIconNumber = 0;
const int Entry::ResolveMaximumDepth = 10; const int Entry::ResolveMaximumDepth = 10;
@ -40,8 +42,8 @@ Entry::Entry()
m_data.iconNumber = DefaultIconNumber; m_data.iconNumber = DefaultIconNumber;
m_data.autoTypeEnabled = true; m_data.autoTypeEnabled = true;
m_data.autoTypeObfuscation = 0; m_data.autoTypeObfuscation = 0;
m_data.totpStep = QTotp::defaultStep; m_data.totpStep = Totp::defaultStep;
m_data.totpDigits = QTotp::defaultDigits; m_data.totpDigits = Totp::defaultDigits;
connect(m_attributes, SIGNAL(modified()), this, SIGNAL(modified())); connect(m_attributes, SIGNAL(modified()), this, SIGNAL(modified()));
connect(m_attributes, SIGNAL(defaultKeyModified()), SLOT(emitDataChanged())); connect(m_attributes, SIGNAL(defaultKeyModified()), SLOT(emitDataChanged()));
@ -90,6 +92,29 @@ void Entry::setUpdateTimeinfo(bool value)
m_updateTimeinfo = 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 Uuid Entry::uuid() const
{ {
return m_uuid; return m_uuid;
@ -315,7 +340,7 @@ QString Entry::totp() const
if (hasTotp()) { if (hasTotp()) {
QString seed = totpSeed(); QString seed = totpSeed();
quint64 time = QDateTime::currentDateTime().toTime_t(); 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); return QString(output);
} else { } else {
@ -326,18 +351,30 @@ QString Entry::totp() const
void Entry::setTotp(const QString& seed, quint8& step, quint8& digits) void Entry::setTotp(const QString& seed, quint8& step, quint8& digits)
{ {
if (step == 0) { if (step == 0) {
step = QTotp::defaultStep; step = Totp::defaultStep;
} }
if (digits == 0) { 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")) { 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 { } else {
m_attributes->set("TOTP Seed", seed, true); 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"); secret = m_attributes->value("TOTP Seed");
} }
m_data.totpDigits = QTotp::defaultDigits; m_data.totpDigits = Totp::defaultDigits;
m_data.totpStep = QTotp::defaultStep; m_data.totpStep = Totp::defaultStep;
if (m_attributes->hasKey("TOTP Settings")) { if (m_attributes->hasKey("TOTP Settings")) {
QRegExp rx("(\\d+);(\\d)", Qt::CaseInsensitive, QRegExp::RegExp); // this regex must be kept in sync with the set of allowed short names Totp::shortNameToEncoder
int pos = rx.indexIn(m_attributes->value("TOTP Settings")); QRegularExpression rx(QString("(\\d+);((?:\\d+)|S)"));
if (pos > -1) { QRegularExpressionMatch m = rx.match(m_attributes->value("TOTP Settings"));
m_data.totpStep = rx.cap(1).toUInt(); if (m.hasMatch()) {
m_data.totpDigits = rx.cap(2).toUInt(); 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 quint8 Entry::totpStep() const
@ -680,7 +722,7 @@ void Entry::updateModifiedSinceBegin()
m_modifiedSinceBegin = true; m_modifiedSinceBegin = true;
} }
QString Entry::resolveMultiplePlaceholdersRecursive(const QString &str, int maxDepth) const QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxDepth) const
{ {
if (maxDepth <= 0) { if (maxDepth <= 0) {
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", qPrintable(uuid().toHex())); 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; 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); const PlaceholderType typeOfPlaceholder = placeholderType(placeholder);
switch (typeOfPlaceholder) { switch (typeOfPlaceholder) {
case PlaceholderType::NotPlaceholder: case PlaceholderType::NotPlaceholder:
return placeholder;
case PlaceholderType::Unknown: case PlaceholderType::Unknown:
qWarning("Can't resolve placeholder %s for entry with uuid %s", qPrintable(placeholder),
qPrintable(uuid().toHex()));
return placeholder; return placeholder;
case PlaceholderType::Title: case PlaceholderType::Title:
return 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) const QString key = placeholder.mid(3, placeholder.length() - 4); // {S:attr} => mid(3, len - 4)
return attributes()->hasKey(key) ? attributes()->value(key) : QString(); return attributes()->hasKey(key) ? attributes()->value(key) : QString();
} }
case PlaceholderType::Reference: { case PlaceholderType::Reference:
// resolving references in format: {REF:<WantedField>@I:<uuid of referenced entry>} return resolveReferencePlaceholderRecursive(placeholder, maxDepth);
// 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;
}
} }
return placeholder; 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() Group* Entry::group()
{ {
return m_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; QString result = str;
result.replace(QRegExp("(\\{PASSWORD\\})", Qt::CaseInsensitive, QRegExp::RegExp2), "******"); result.replace(QRegExp("(\\{PASSWORD\\})", Qt::CaseInsensitive, QRegExp::RegExp2), "******");
@ -856,7 +914,7 @@ QString Entry::resolvePlaceholder(const QString& placeholder) const
return resolvePlaceholderRecursive(placeholder, ResolveMaximumDepth); 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()) if (str.isEmpty())
return QString(); return QString();
@ -892,7 +950,7 @@ QString Entry::resolveUrlPlaceholder(const QString &str, Entry::PlaceholderType
return QString(); 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('}'))) { if (!placeholder.startsWith(QLatin1Char('{')) || !placeholder.endsWith(QLatin1Char('}'))) {
return PlaceholderType::NotPlaceholder; return PlaceholderType::NotPlaceholder;

View File

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

View File

@ -17,6 +17,8 @@
#include "EntryAttachments.h" #include "EntryAttachments.h"
#include <QStringList>
EntryAttachments::EntryAttachments(QObject* parent) EntryAttachments::EntryAttachments(QObject* parent)
: QObject(parent) : QObject(parent)
{ {
@ -71,7 +73,8 @@ void EntryAttachments::set(const QString& key, const QByteArray& value)
void EntryAttachments::remove(const QString& key) void EntryAttachments::remove(const QString& key)
{ {
if (!m_attachments.contains(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; return;
} }
@ -83,6 +86,31 @@ void EntryAttachments::remove(const QString& key)
emit modified(); 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() void EntryAttachments::clear()
{ {
if (m_attachments.isEmpty()) { if (m_attachments.isEmpty()) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,7 +26,6 @@ SymmetricCipherGcrypt::SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, Sy
, m_algo(gcryptAlgo(algo)) , m_algo(gcryptAlgo(algo))
, m_mode(gcryptMode(mode)) , m_mode(gcryptMode(mode))
, m_direction(direction) , m_direction(direction)
, m_blockSize(-1)
{ {
} }
@ -62,6 +61,9 @@ int SymmetricCipherGcrypt::gcryptMode(SymmetricCipher::Mode mode)
case SymmetricCipher::Cbc: case SymmetricCipher::Cbc:
return GCRY_CIPHER_MODE_CBC; return GCRY_CIPHER_MODE_CBC;
case SymmetricCipher::Ctr:
return GCRY_CIPHER_MODE_CTR;
case SymmetricCipher::Stream: case SymmetricCipher::Stream:
return GCRY_CIPHER_MODE_STREAM; return GCRY_CIPHER_MODE_STREAM;
@ -86,20 +88,14 @@ bool SymmetricCipherGcrypt::init()
gcry_error_t error; gcry_error_t error;
if(m_ctx != nullptr)
gcry_cipher_close(m_ctx);
error = gcry_cipher_open(&m_ctx, m_algo, m_mode, 0); error = gcry_cipher_open(&m_ctx, m_algo, m_mode, 0);
if (error != 0) { if (error != 0) {
setErrorString(error); setErrorString(error);
return false; 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; return true;
} }
@ -119,7 +115,13 @@ bool SymmetricCipherGcrypt::setKey(const QByteArray& key)
bool SymmetricCipherGcrypt::setIv(const QByteArray& iv) bool SymmetricCipherGcrypt::setIv(const QByteArray& iv)
{ {
m_iv = 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) { if (error != 0) {
setErrorString(error); setErrorString(error);
@ -228,9 +230,28 @@ bool SymmetricCipherGcrypt::reset()
return true; 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 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 QString SymmetricCipherGcrypt::errorString() const

View File

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

View File

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

View File

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2016 Felix Geyer <debfx@fobos.de> * 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 * 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 * it under the terms of the GNU General Public License as published by
@ -20,6 +21,7 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QIODevice> #include <QIODevice>
#include <QPair>
#include "core/Database.h" #include "core/Database.h"
#include "keys/CompositeKey.h" #include "keys/CompositeKey.h"
@ -36,14 +38,12 @@ public:
RepairSuccess, RepairSuccess,
RepairFailed RepairFailed
}; };
using RepairOutcome = QPair<RepairResult, Database*>;
KeePass2Repair(); RepairOutcome repairDatabase(QIODevice* device, const CompositeKey& key);
RepairResult repairDatabase(QIODevice* device, const CompositeKey& key);
Database* database() const;
QString errorString() const; QString errorString() const;
private: private:
Database* m_db;
QString m_errorStr; QString m_errorStr;
}; };

View File

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

View File

@ -28,6 +28,9 @@ Clipboard* Clipboard::m_instance(nullptr);
Clipboard::Clipboard(QObject* parent) Clipboard::Clipboard(QObject* parent)
: QObject(parent) : QObject(parent)
, m_timer(new QTimer(this)) , m_timer(new QTimer(this))
#ifdef Q_OS_MAC
, m_pasteboard(new MacPasteboard)
#endif
{ {
m_timer->setSingleShot(true); m_timer->setSingleShot(true);
connect(m_timer, SIGNAL(timeout()), SLOT(clearClipboard())); connect(m_timer, SIGNAL(timeout()), SLOT(clearClipboard()));
@ -38,10 +41,17 @@ void Clipboard::setText(const QString& text)
{ {
QClipboard* clipboard = QApplication::clipboard(); 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); clipboard->setText(text, QClipboard::Clipboard);
if (clipboard->supportsSelection()) { if (clipboard->supportsSelection()) {
clipboard->setText(text, QClipboard::Selection); clipboard->setText(text, QClipboard::Selection);
} }
#endif
if (config()->get("security/clearclipboard").toBool()) { if (config()->get("security/clearclipboard").toBool()) {
int timeout = config()->get("security/clearclipboardtimeout").toInt(); int timeout = config()->get("security/clearclipboardtimeout").toInt();

View File

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

View File

@ -277,6 +277,9 @@ void DatabaseOpenWidget::activateChallengeResponse()
void DatabaseOpenWidget::browseKeyFile() void DatabaseOpenWidget::browseKeyFile()
{ {
QString filters = QString("%1 (*);;%2 (*.key)").arg(tr("All files"), tr("Key files")); 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); QString filename = fileDialog()->getOpenFileName(this, tr("Select key file"), QString(), filters);
if (!filename.isEmpty()) { if (!filename.isEmpty()) {

View File

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2016 Felix Geyer <debfx@fobos.de> * 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 * 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 * it under the terms of the GNU General Public License as published by
@ -69,7 +70,8 @@ void DatabaseRepairWidget::openDatabase()
delete m_db; delete m_db;
} }
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); 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(); QApplication::restoreOverrideCursor();
switch (repairResult) { switch (repairResult) {
@ -83,7 +85,7 @@ void DatabaseRepairWidget::openDatabase()
emit editFinished(false); emit editFinished(false);
return; return;
case KeePass2Repair::RepairSuccess: 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.")); MessageBox::warning(this, tr("Success"), tr("The database has been successfully repaired\nYou can now save it."));
emit editFinished(true); emit editFinished(true);
return; return;

View File

@ -90,12 +90,12 @@ void DatabaseTabWidget::newDatabase()
Database* db = new Database(); Database* db = new Database();
db->rootGroup()->setName(tr("Root")); db->rootGroup()->setName(tr("Root"));
dbStruct.dbWidget = new DatabaseWidget(db, this); dbStruct.dbWidget = new DatabaseWidget(db, this);
CompositeKey emptyKey; CompositeKey emptyKey;
db->setKey(emptyKey); db->setKey(emptyKey);
insertDatabase(db, dbStruct); insertDatabase(db, dbStruct);
if (!saveDatabaseAs(db)) { if (!saveDatabaseAs(db)) {
closeDatabase(db); closeDatabase(db);
return; return;
@ -298,8 +298,7 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
if (!saveDatabase(db)) { if (!saveDatabase(db)) {
return false; return false;
} }
} } else if (dbStruct.dbWidget->currentMode() != DatabaseWidget::LockedMode) {
else {
QMessageBox::StandardButton result = QMessageBox::StandardButton result =
MessageBox::question( MessageBox::question(
this, tr("Save changes?"), this, tr("Save changes?"),
@ -307,10 +306,9 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
QMessageBox::Yes | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Yes); QMessageBox::Yes | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Yes);
if (result == QMessageBox::Yes) { if (result == QMessageBox::Yes) {
if (!saveDatabase(db)) { if (!saveDatabase(db)) {
return false; return false;
} }
} } else if (result == QMessageBox::Cancel) {
else if (result == QMessageBox::Cancel) {
return false; return false;
} }
} }
@ -355,8 +353,13 @@ bool DatabaseTabWidget::saveDatabase(Database* db)
{ {
DatabaseManagerStruct& dbStruct = m_dbList[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); dbStruct.dbWidget->blockAutoReload(true);
QString errorMessage = db->saveToFile(dbStruct.canonicalFilePath); QString errorMessage = db->saveToFile(dbStruct.canonicalFilePath);
dbStruct.dbWidget->blockAutoReload(false); dbStruct.dbWidget->blockAutoReload(false);
@ -375,7 +378,6 @@ bool DatabaseTabWidget::saveDatabase(Database* db)
MessageWidget::Error); MessageWidget::Error);
return false; return false;
} }
} else { } else {
return saveDatabaseAs(db); return saveDatabaseAs(db);
} }
@ -622,6 +624,36 @@ void DatabaseTabWidget::updateTabNameFromDbWidgetSender()
DatabaseWidget* dbWidget = static_cast<DatabaseWidget*>(sender()); DatabaseWidget* dbWidget = static_cast<DatabaseWidget*>(sender());
updateTabName(databaseFromDatabaseWidget(dbWidget)); 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) int DatabaseTabWidget::databaseIndex(Database* db)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,7 +27,7 @@ QString FileDialog::getOpenFileName(QWidget* parent, const QString& caption, QSt
{ {
if (!m_nextFileName.isEmpty()) { if (!m_nextFileName.isEmpty()) {
QString result = m_nextFileName; QString result = m_nextFileName;
m_nextFileName = ""; m_nextFileName.clear();
return result; return result;
} }
else { else {
@ -43,11 +43,37 @@ QString FileDialog::getOpenFileName(QWidget* parent, const QString& caption, QSt
parent->activateWindow(); parent->activateWindow();
} }
if (!result.isEmpty()) { saveLastDir(result);
config()->set("LastDir", QFileInfo(result).absolutePath()); 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()) { if (!m_nextFileName.isEmpty()) {
QString result = m_nextFileName; QString result = m_nextFileName;
m_nextFileName = ""; m_nextFileName.clear();
return result; return result;
} }
else { else {
@ -95,11 +121,33 @@ QString FileDialog::getSaveFileName(QWidget* parent, const QString& caption, QSt
parent->activateWindow(); parent->activateWindow();
} }
if (!result.isEmpty()) { saveLastDir(result);
config()->set("LastDir", QFileInfo(result).absolutePath()); 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; 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() FileDialog::FileDialog()
{ {
} }
void FileDialog::saveLastDir(QString dir) {
if (!dir.isEmpty() && !m_forgetLastDir) {
config()->set("LastDir", QFileInfo(dir).absolutePath());
}
m_forgetLastDir = false;
}
FileDialog* FileDialog::instance() FileDialog* FileDialog::instance()
{ {
if (!m_instance) { if (!m_instance) {

View File

@ -26,22 +26,35 @@ public:
QString getOpenFileName(QWidget* parent = nullptr, const QString& caption = QString(), QString getOpenFileName(QWidget* parent = nullptr, const QString& caption = QString(),
QString dir = QString(), const QString& filter = QString(), QString dir = QString(), const QString& filter = QString(),
QString* selectedFilter = nullptr, QFileDialog::Options options = 0); 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 getSaveFileName(QWidget* parent = nullptr, const QString& caption = QString(),
QString dir = QString(), const QString& filter = QString(), QString dir = QString(), const QString& filter = QString(),
QString* selectedFilter = nullptr, QFileDialog::Options options = 0, QString* selectedFilter = nullptr, QFileDialog::Options options = 0,
const QString& defaultExtension = QString()); 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. * Sets the result of the next get* method call.
* Use only for testing. * Use only for testing.
*/ */
void setNextFileName(const QString& fileName); void setNextFileName(const QString& fileName);
void setNextFileNames(const QStringList& fileNames);
void setNextDirName(const QString& dirName);
static FileDialog* instance(); static FileDialog* instance();
private: private:
FileDialog(); FileDialog();
QString m_nextFileName; QString m_nextFileName;
QStringList m_nextFileNames;
QString m_nextDirName;
bool m_forgetLastDir = false;
void saveLastDir(QString);
static FileDialog* m_instance; 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" #include "http/OptionDialog.h"
#endif #endif
#ifdef WITH_XC_SSHAGENT
#include "sshagent/AgentSettingsPage.h"
#include "sshagent/SSHAgent.h"
#endif
#include "gui/SettingsWidget.h" #include "gui/SettingsWidget.h"
#include "gui/PasswordGeneratorWidget.h" #include "gui/PasswordGeneratorWidget.h"
@ -109,6 +114,8 @@ MainWindow::MainWindow()
{ {
m_ui->setupUi(this); m_ui->setupUi(this);
m_ui->toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
// Setup the search widget in the toolbar // Setup the search widget in the toolbar
SearchWidget *search = new SearchWidget(); SearchWidget *search = new SearchWidget();
search->connectSignals(m_actionMultiplexer); search->connectSignals(m_actionMultiplexer);
@ -121,6 +128,10 @@ MainWindow::MainWindow()
#ifdef WITH_XC_HTTP #ifdef WITH_XC_HTTP
m_ui->settingsWidget->addSettingsPage(new HttpPlugin(m_ui->tabWidget)); m_ui->settingsWidget->addSettingsPage(new HttpPlugin(m_ui->tabWidget));
#endif #endif
#ifdef WITH_XC_SSHAGENT
SSHAgent::init(this);
m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage(m_ui->tabWidget));
#endif
setWindowIcon(filePath()->applicationIcon()); setWindowIcon(filePath()->applicationIcon());
m_ui->globalMessageWidget->setHidden(true); m_ui->globalMessageWidget->setHidden(true);
@ -727,7 +738,9 @@ void MainWindow::changeEvent(QEvent* event)
void MainWindow::saveWindowInformation() void MainWindow::saveWindowInformation()
{ {
config()->set("GUI/MainWindowGeometry", saveGeometry()); if (isVisible()) {
config()->set("GUI/MainWindowGeometry", saveGeometry());
}
} }
bool MainWindow::saveLastDatabases() bool MainWindow::saveLastDatabases()
@ -811,11 +824,6 @@ void MainWindow::showGroupContextMenu(const QPoint& globalPos)
m_ui->menuGroups->popup(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) void MainWindow::setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback)
{ {
if (!QKeySequence::keyBindings(standard).isEmpty()) { if (!QKeySequence::keyBindings(standard).isEmpty()) {
@ -858,6 +866,7 @@ void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason)
void MainWindow::hideWindow() void MainWindow::hideWindow()
{ {
saveWindowInformation();
#ifndef Q_OS_MAC #ifndef Q_OS_MAC
setWindowState(windowState() | Qt::WindowMinimized); setWindowState(windowState() | Qt::WindowMinimized);
#endif #endif

View File

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

View File

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

View File

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

View File

@ -21,6 +21,7 @@
#include <QLineEdit> #include <QLineEdit>
#include <QDir> #include <QDir>
#include <QKeyEvent>
#include "core/Config.h" #include "core/Config.h"
#include "core/PasswordGenerator.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() void PasswordGeneratorWidget::regeneratePassword()
{ {
if (m_ui->tabWidget->currentIndex() == Password) { if (m_ui->tabWidget->currentIndex() == Password) {

View File

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

View File

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

View File

@ -241,6 +241,7 @@ void SettingsWidget::saveSettings()
if (!config()->get("RememberLastKeyFiles").toBool()) { if (!config()->get("RememberLastKeyFiles").toBool()) {
config()->set("LastKeyFiles", QVariant()); config()->set("LastKeyFiles", QVariant());
config()->set("LastDir", "");
} }
for (const ExtraPage& page: asConst(m_extraPages)) { 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(rejected()), SLOT(close()));
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(setupTotp())); 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; quint8 digits;
if (m_ui->radio8Digits->isChecked()) { if (m_ui->radioSteam->isChecked()) {
digits = Totp::ENCODER_STEAM;
} else if (m_ui->radio8Digits->isChecked()) {
digits = 8; digits = 8;
} else { } else {
digits = 6; digits = 6;
} }
quint8 step = m_ui->stepSpinBox->value(); 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); m_entry->setTotp(seed, step, digits);
emit m_parent->entrySelectionChanged(); emit m_parent->entrySelectionChanged();
close(); 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) void SetupTotpDialog::toggleCustom(bool status)
{ {
m_ui->digitsLabel->setEnabled(status); m_ui->digitsLabel->setEnabled(status);
@ -72,13 +92,25 @@ void SetupTotpDialog::setSeed(QString value)
m_ui->seedEdit->setText(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) void SetupTotpDialog::setStep(quint8 step)
{ {
m_ui->stepSpinBox->setValue(step); m_ui->stepSpinBox->setValue(step);
if (step != QTotp::defaultStep) {
m_ui->customSettingsCheckBox->setChecked(true);
}
} }
void SetupTotpDialog::setDigits(quint8 digits) void SetupTotpDialog::setDigits(quint8 digits)
@ -90,13 +122,8 @@ void SetupTotpDialog::setDigits(quint8 digits)
m_ui->radio6Digits->setChecked(true); m_ui->radio6Digits->setChecked(true);
m_ui->radio8Digits->setChecked(false); m_ui->radio8Digits->setChecked(false);
} }
if (digits != QTotp::defaultDigits) {
m_ui->customSettingsCheckBox->setChecked(true);
}
} }
SetupTotpDialog::~SetupTotpDialog() SetupTotpDialog::~SetupTotpDialog()
{ {
} }

View File

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

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>282</width> <width>282</width>
<height>257</height> <height>364</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -29,11 +29,38 @@
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="customSettingsCheckBox"> <layout class="QVBoxLayout" name="verticalLayout_2">
<property name="text"> <item>
<string>Use custom settings</string> <widget class="QRadioButton" name="radioDefault">
</property> <property name="text">
</widget> <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>
<item> <item>
<widget class="QLabel" name="label_4"> <widget class="QLabel" name="label_4">
@ -134,4 +161,7 @@
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>
<buttongroups>
<buttongroup name="settingsButtonGroup"/>
</buttongroups>
</ui> </ui>

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