diff --git a/.travis.yml b/.travis.yml
index 15af85bf..6e26860f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,6 +14,6 @@ before_script: mkdir build && pushd build
script:
- cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_GUI_TESTS=ON ..
- make
- - if [ "$TRAVIS_OS_NAME" = "linux" ]; then make test ARGS+="-E testgui"; fi
- - if [ "$TRAVIS_OS_NAME" = "linux" ]; then xvfb-run -a --server-args="-screen 0 800x600x24" make test ARGS+="-R testgui"; 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" = "osx" ]; then make test; fi
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8c901045..13b7b10f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -149,9 +149,9 @@ elseif(APPLE)
else()
include(GNUInstallDirs)
- set(BIN_INSTALL_DIR "${CMAKE_INSTALL_FULL_BINDIR}")
- set(PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/keepassx")
- set(DATA_INSTALL_DIR "${CMAKE_INSTALL_FULL_DATADIR}/keepassx")
+ set(BIN_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}")
+ set(PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/keepassx")
+ set(DATA_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/keepassx")
endif()
if(WITH_TESTS)
diff --git a/share/translations/keepassx_cs.ts b/share/translations/keepassx_cs.ts
new file mode 100644
index 00000000..0ac580fe
--- /dev/null
+++ b/share/translations/keepassx_cs.ts
@@ -0,0 +1,1205 @@
+
+
+ AboutDialog
+
+ About KeePassX
+ O aplikaci KeePassX
+
+
+ KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3.
+ KeePassX je šířeno za podmínek licence GNU General Public License (GPL) verze 2 a (případně) 3.
+
+
+
+ AutoType
+
+ Auto-Type - KeePassX
+ Samočinné vyplňování – KeePassX
+
+
+ Couldn't find an entry that matches the window title:
+ Nelze nalézt položku, která by se shodovala s titulkem okna:
+
+
+
+ AutoTypeAssociationsModel
+
+ Window
+ Okno
+
+
+ Sequence
+ Posloupnost
+
+
+ Default sequence
+ Výchozí posloupnost
+
+
+
+ AutoTypeSelectDialog
+
+ Auto-Type - KeePassX
+ Samočinné vyplňování – KeePassX
+
+
+ Select entry to Auto-Type:
+ Vyberte položku pro samočinné vyplňování:
+
+
+
+ ChangeMasterKeyWidget
+
+ Password
+ Heslo
+
+
+ Enter password:
+ Zadejte heslo:
+
+
+ Repeat password:
+ Zopakujte heslo:
+
+
+ Key file
+ Soubor s klíčem
+
+
+ Browse
+ Procházet
+
+
+ Create
+ Vytvořit
+
+
+ Key files
+ Soubory s klíči
+
+
+ All files
+ Veškeré soubory
+
+
+ Create Key File...
+ Vytvořit soubor s klíčem…
+
+
+ Error
+ Chyba
+
+
+ Unable to create Key File :
+ Nelze vytvořit soubor s klíčem :
+
+
+ Select a key file
+ Vyberte soubor s klíčem
+
+
+ Question
+ Otázka
+
+
+ Do you really want to use an empty string as password?
+ Opravdu chcete heslo ponechat nevyplněné?
+
+
+ Different passwords supplied.
+ Zadání hesla nejsou shodná.
+
+
+
+ DatabaseOpenWidget
+
+ Enter master key
+ Zadejte hlavní klíč
+
+
+ Key File:
+ Soubor s klíčem:
+
+
+ Password:
+ Heslo:
+
+
+ Browse
+ Procházet
+
+
+ Error
+ Chyba
+
+
+ Unable to open the database.
+ Databázi nebylo možné otevřít.
+
+
+ Can't open key file
+ Soubor s klíčem nelze otevřít.
+
+
+ All files
+ Veškeré soubory
+
+
+ Key files
+ Soubory s klíči
+
+
+ Select key file
+ Vyberte soubor s klíčem
+
+
+
+ DatabaseSettingsWidget
+
+ Database name:
+ Název databáze:
+
+
+ Database description:
+ Popis databáze:
+
+
+ Transform rounds:
+ Počet průchodů:
+
+
+ Default username:
+ Výchozí uživatelské jméno:
+
+
+ Use recycle bin:
+ Použít Koš:
+
+
+ MiB
+
+
+
+ Benchmark
+ Test výkonu
+
+
+ Max. history items:
+ Nejvyšší umožněný položek historie:
+
+
+ Max. history size:
+ Nejvyšší umožněná velikost historie:
+
+
+
+ DatabaseTabWidget
+
+ Root
+ Kořen
+
+
+ KeePass 2 Database
+ Databáze aplikace KeePass 2
+
+
+ All files
+ Veškeré soubory
+
+
+ Open database
+ Otevřít databázi
+
+
+ Warning
+ Varování
+
+
+ File not found!
+ Soubor nebyl nalezen!
+
+
+ Open KeePass 1 database
+ Otevřít databázi aplikace KeePass 1
+
+
+ KeePass 1 database
+ Databáze aplikace KeePass 1
+
+
+ All files (*)
+ Veškeré soubory (*)
+
+
+ Close?
+ Zavřít?
+
+
+ "%1" is in edit mode.
+Close anyway?
+ %1 je upravováno.
+Přesto zavřít?
+
+
+ Save changes?
+ Uložit změny?
+
+
+ "%1" was modified.
+Save changes?
+ %1 bylo změněno.
+Uložit změny?
+
+
+ Error
+ Chyba
+
+
+ Writing the database failed.
+ Zápis do databáze se nezdařil.
+
+
+ Save database as
+ Uložit databázi jako
+
+
+ New database
+ Nová databáze
+
+
+ locked
+ zamčeno
+
+
+
+ DatabaseWidget
+
+ Change master key
+ Změnit hlavní klíč
+
+
+ Delete entry?
+ Smazat položku?
+
+
+ Do you really want to delete the entry "%1" for good?
+ Opravdu chcete smazat položku %1?
+
+
+ Delete entries?
+ Smazat položky?
+
+
+ Do you really want to delete %1 entries for good?
+ Opravdu chcete smazat %1 položek?
+
+
+ Move entries to recycle bin?
+ Přesunout položky do Koše?
+
+
+ Do you really want to move %n entry(s) to the recycle bin?
+
+
+
+ Delete group?
+ Smazat skupinu?
+
+
+ Do you really want to delete the group "%1" for good?
+ Opravdu chcete smazat skupinu %1?
+
+
+ Current group
+ Stávající skupina
+
+
+
+ EditEntryWidget
+
+ Entry
+ Položka
+
+
+ Advanced
+ Pokročilé
+
+
+ Icon
+ Ikona
+
+
+ Auto-Type
+ Samočinné vyplňování
+
+
+ Properties
+ Vlastnosti
+
+
+ History
+ Historie
+
+
+ Entry history
+ Historie položky
+
+
+ Add entry
+ Přidat položku
+
+
+ Edit entry
+ Upravit položku
+
+
+ Error
+ Chyba
+
+
+ Different passwords supplied.
+ Zadání hesla se neshodují.
+
+
+ New attribute
+ Nový atribut
+
+
+ Select file
+ Vyberte soubor
+
+
+ Unable to open file
+ Soubor nelze otevřít
+
+
+ Save attachment
+ Uložit přílohu
+
+
+ Unable to save the attachment:
+
+ Přílohu nelze uložit:
+
+
+
+ Tomorrow
+ Zítra
+
+
+ %n week(s)
+
+
+
+ %n month(s)
+
+
+
+ 1 year
+ 1 rok
+
+
+
+ EditEntryWidgetAdvanced
+
+ Additional attributes
+ Další atributy
+
+
+ Add
+ Přidat
+
+
+ Edit
+ Upravit
+
+
+ Remove
+ Odebrat
+
+
+ Attachments
+ Přílohy
+
+
+ Save
+ Uložit
+
+
+
+ EditEntryWidgetAutoType
+
+ Enable Auto-Type for this entry
+ Zapnout samočinné vyplňování u této položky
+
+
+ Inherit default Auto-Type sequence from the group
+ Převzít výchozí posloupnost samočinného vyplňování ze skupiny
+
+
+ Use custom Auto-Type sequence:
+ Použít vlastní posloupnost samočinného vyplňování:
+
+
+ +
+
+
+
+ -
+
+
+
+ Window title:
+ Titulek okna:
+
+
+ Use default sequence
+ Použít výchozí posloupnost
+
+
+ Set custom sequence:
+ Nastavit vlastní posloupnost:
+
+
+
+ EditEntryWidgetHistory
+
+ Show
+ Zobrazit
+
+
+ Restore
+ Obnovit
+
+
+ Delete
+ Smazat
+
+
+ Delete all
+ Smazat vše
+
+
+
+ EditEntryWidgetMain
+
+ Title:
+ Titulek:
+
+
+ Username:
+ Uživatelské jméno:
+
+
+ Password:
+ Heslo:
+
+
+ Repeat:
+ Zopakovat:
+
+
+ Gen.
+ Obec.
+
+
+ URL:
+ URL adresa:
+
+
+ Expires
+ Platnost skončí:
+
+
+ Presets
+ Přednastavené
+
+
+ Notes:
+ Poznámky:
+
+
+
+ EditGroupWidget
+
+ Group
+ Skupina
+
+
+ Icon
+ Ikona
+
+
+ Properties
+ Vlastnosti
+
+
+ Add group
+ Přidat skupinu
+
+
+ Edit group
+ Upravit skupinu
+
+
+ Enable
+ Zapnout
+
+
+ Disable
+ Vypnout
+
+
+ Inherit from parent group (%1)
+ Převzít z nadřazené skupiny (%1)
+
+
+
+ EditGroupWidgetMain
+
+ Name
+ Název
+
+
+ Notes
+ Poznámky
+
+
+ Expires
+ Platnost skončí
+
+
+ Search
+ Hledat
+
+
+ Auto-type
+ Samočinné vyplňování
+
+
+
+ EditWidgetIcons
+
+ Use default icon
+ Použít výchozí ikonu
+
+
+ Use custom icon
+ Použít vlastní ikonu
+
+
+ Add custom icon
+ Přidat vlastní ikonu
+
+
+ Delete custom icon
+ Smazat vlastní ikonu
+
+
+ Images
+ Obrázky
+
+
+ All files
+ Veškeré soubory
+
+
+ Select Image
+ Vyberte obrázek
+
+
+ Can't delete icon!
+ Ikonu nelze smazat!
+
+
+ Can't delete icon. Still used by %n item(s).
+
+
+
+
+ EditWidgetProperties
+
+ Created:
+ Vytvořeno:
+
+
+ Modified:
+ Čas poslední změny:
+
+
+ Accessed:
+ Čas posledního přístupu:
+
+
+ Uuid:
+ Uuid identifikátor:
+
+
+
+ EntryAttributesModel
+
+ Name
+ Název
+
+
+
+ EntryHistoryModel
+
+ Last modified
+ Čas poslední změny
+
+
+ Title
+ Titulek
+
+
+ Username
+ Uživatelské jméno
+
+
+ URL
+ URL adresa
+
+
+
+ EntryModel
+
+ Group
+ Skupina
+
+
+ Title
+ Titulek
+
+
+ Username
+ Uživatelské jméno
+
+
+ URL
+ URL adresa
+
+
+
+ Group
+
+ Recycle Bin
+ Koš
+
+
+
+ KeePass1OpenWidget
+
+ Import KeePass1 database
+ Importovat databázi aplikace KeePass 1
+
+
+ Error
+ Chyba
+
+
+ Unable to open the database.
+ Databázi nelze otevřít.
+
+
+
+ KeePass1Reader
+
+ Unable to read keyfile.
+ Soubor s klíčem nebylo možné načíst.
+
+
+ Not a KeePass database.
+ Nejedná se o databázi Keepass.
+
+
+ Unsupported encryption algorithm.
+ Nepodporovaný šifrovací algoritmus.
+
+
+ Unsupported KeePass database version.
+ Nepodporovaná verze databáze KeePass.
+
+
+ Root
+ Kořen
+
+
+
+ KeePass2Reader
+
+ Not a KeePass database.
+ Nejedná se o databázi KeePass.
+
+
+ Unsupported KeePass database version.
+ Nepodporovaná verze databáze KeePass.
+
+
+ Wrong key or database file is corrupt.
+ Chybný klíč nebo je databáze poškozená.
+
+
+
+ Main
+
+ Fatal error while testing the cryptographic functions.
+ Při testování šifrovacích funkcí došlo k fatální chybě.
+
+
+ KeePassX - Error
+ KeePassX – chyba
+
+
+
+ MainWindow
+
+ Database
+ Databáze
+
+
+ Recent databases
+ Nedávno otevřené databáze
+
+
+ Help
+ Nápověda
+
+
+ Entries
+ Položky
+
+
+ Copy attribute to clipboard
+ Zkopírovat atribut do schránky
+
+
+ Groups
+ Skupiny
+
+
+ Extras
+ Doplňující
+
+
+ View
+ Zobrazit
+
+
+ Quit
+ Ukončit
+
+
+ About
+ O aplikaci
+
+
+ Open database
+ Otevřít databázi
+
+
+ Save database
+ Uložit databázi
+
+
+ Close database
+ Zavřít databázi
+
+
+ New database
+ Nová databáze
+
+
+ Add new entry
+ Přidat novou položku
+
+
+ View/Edit entry
+ Zobrazit/upravit položku
+
+
+ Delete entry
+ Smazat položku
+
+
+ Add new group
+ Přidat novou skupinu
+
+
+ Edit group
+ Upravit skupinu
+
+
+ Delete group
+ Smazat skupinu
+
+
+ Save database as
+ Uložit databázi jako
+
+
+ Change master key
+ Změnit hlavní klíč
+
+
+ Database settings
+ Nastavení databáze
+
+
+ Import KeePass 1 database
+ Importovat databázi aplikace KeePass 1
+
+
+ Clone entry
+ Klonovat položku
+
+
+ Find
+ Najít
+
+
+ Username
+ Uživatelské jméno
+
+
+ Copy username to clipboard
+ Zkopírovat uživatelské jméno do schránky
+
+
+ Password
+ Heslo
+
+
+ Copy password to clipboard
+ Zkopírovat heslo do schránky
+
+
+ Settings
+ Nastavení
+
+
+ Perform Auto-Type
+ Provést samočinné vyplnění
+
+
+ Open URL
+ Otevřít URL adresu
+
+
+ Lock databases
+ Uzamknout databáze
+
+
+ Title
+ Titulek
+
+
+ URL
+ URL adresa
+
+
+ Notes
+ Poznámky
+
+
+ Show toolbar
+ Zobrazit lištu nástrojů
+
+
+ read-only
+ pouze pro čtení
+
+
+ Toggle window
+ Přepnout okno
+
+
+
+ PasswordGeneratorWidget
+
+ Password:
+ Heslo:
+
+
+ Length:
+ Délka:
+
+
+ Character Types
+ Typy znaků
+
+
+ Upper Case Letters
+ Velká písmena
+
+
+ Lower Case Letters
+ Malá písmena
+
+
+ Numbers
+ Čísla
+
+
+ Special Characters
+ Speciální znaky
+
+
+ Exclude look-alike characters
+ Vynechat podobně vypadající znaky
+
+
+ Ensure that the password contains characters from every group
+ Zajistit, aby heslo obsahovalo znaky ze všech zvolených skupin
+
+
+ Accept
+ Přijmout
+
+
+
+ QCommandLineParser
+
+ Displays version information.
+ Zobrazí informace o verzi.
+
+
+ Displays this help.
+ Zobrazí tuto nápovědu.
+
+
+ Unknown option '%1'.
+ Neznámá volba %1.
+
+
+ Unknown options: %1.
+ Neznámé volby: %1.
+
+
+ Missing value after '%1'.
+ Chybějící hodnota, následující za %1.
+
+
+ Unexpected value after '%1'.
+ Neočekávaná hodnota, následující za %1.
+
+
+ [options]
+ [volby]
+
+
+ Usage: %1
+ Použití: %1
+
+
+ Options:
+ Volby:
+
+
+ Arguments:
+ Argumenty:
+
+
+
+ QSaveFile
+
+ Existing file %1 is not writable
+ Existující soubor %1 není zapisovatelný
+
+
+ Writing canceled by application
+ Zápis byl zrušen aplikací
+
+
+ Partial write. Partition full?
+ Zápis nebylo možné dokončit zcela. Nedostatek volného místa?
+
+
+
+ QtIOCompressor
+
+ Internal zlib error when compressing:
+ Během komprimace se vyskytla vnitřní chyba v knihovně zlib:
+
+
+ Error writing to underlying device:
+ Chyba při zápisu na zařízení:
+
+
+ Error opening underlying device:
+ Chyba při otevírání zařízení:
+
+
+ Error reading data from underlying device:
+ Chyba při čtení dat ze zařízení:
+
+
+ Internal zlib error when decompressing:
+ Vnitřní chyba knihovny zlib při rozbalování:
+
+
+
+ QtIOCompressor::open
+
+ The gzip format not supported in this version of zlib.
+ Formát tohoto gzip archivu není podporován touto verzí knihovny zlib.
+
+
+ Internal zlib error:
+ Vnitřní chyba knihovny zlib:
+
+
+
+ SearchWidget
+
+ Find:
+ Najít:
+
+
+ Case sensitive
+ Rozlišovat velikost písmen
+
+
+ Current group
+ Stávající skupina
+
+
+ Root group
+ Kořenová skupina
+
+
+
+ SettingsWidget
+
+ Application Settings
+ Nastavení aplikace
+
+
+ General
+ Obecné
+
+
+ Security
+ Zabezpečení
+
+
+
+ SettingsWidgetGeneral
+
+ Remember last databases
+ Pamatovat si nedávno otevírané databáze
+
+
+ Open previous databases on startup
+ Při startu otevřít minule otevřenou databázi
+
+
+ Mark as modified on expanded state changes
+ Při změnách rozšířeného stavu označit jako změněné
+
+
+ Automatically save on exit
+ Při ukončování samočinně uložit
+
+
+ Automatically save after every change
+ Po každé změně okamžitě samočinně uložit
+
+
+ Minimize when copying to clipboard
+ Po zkopírování do schránky samočinně zminimalizovat aplikaci
+
+
+ Use group icon on entry creation
+ Při vytváření položky použít ikonu skupiny
+
+
+ Global Auto-Type shortcut
+ Všeobecná klávesová zkratka pro samočinné vyplňování
+
+
+ Use entry title to match windows for global auto-type
+ Použít titulek položky pro porovnání s okny pro všeobecné samočinné vyplňování
+
+
+ Language
+ Jazyk
+
+
+ Show a system tray icon
+ Zobrazit ikonu v oznamovací oblasti
+
+
+ Hide window to system tray when minimized
+ Minimalizovat do oznamovací oblasti
+
+
+
+ SettingsWidgetSecurity
+
+ Clear clipboard after
+ Vyčistit schránku po uplynutí
+
+
+ sec
+ sek
+
+
+ Lock databases after inactivity of
+ Uzamknout databázi při nečinnosti delší než
+
+
+ Show passwords in cleartext by default
+ Vždy zobrazovat hesla
+
+
+ Always ask before performing auto-type
+ Před provedením samočinného vyplnění se vždy dotázat
+
+
+
+ UnlockDatabaseWidget
+
+ Unlock database
+ Odemknout databázi
+
+
+ Error
+ Chyba
+
+
+ Wrong key.
+ Chybný klíč.
+
+
+
+ WelcomeWidget
+
+ Welcome!
+ Vítejte!
+
+
+
+ main
+
+ KeePassX - cross-platform password manager
+ KeePassX – multiplatformní správce hesel
+
+
+ filename of the password database to open (*.kdbx)
+ Soubor s databází hesel (*.kdbx), který otevřít
+
+
+ path to a custom config file
+ umístění souboru s vlastními nastaveními
+
+
+ password of the database (DANGEROUS!)
+ heslo databáze (NEBEZPEČNÉ!)
+
+
+ key file of the database
+ soubor s klíčem k databázi
+
+
+
\ No newline at end of file
diff --git a/share/translations/keepassx_da.ts b/share/translations/keepassx_da.ts
new file mode 100644
index 00000000..25bdd2c1
--- /dev/null
+++ b/share/translations/keepassx_da.ts
@@ -0,0 +1,1202 @@
+
+
+ AboutDialog
+
+ About KeePassX
+ Om KeePassX
+
+
+ KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3.
+ KeePassX distribueres under betingelserne i GNU General Public License (GPL) version 2 eller (efter eget valg) version 3.
+
+
+
+ AutoType
+
+ Auto-Type - KeePassX
+
+
+
+ Couldn't find an entry that matches the window title:
+ Kunne ikke finde en post, der matcher vinduets titel:
+
+
+
+ AutoTypeAssociationsModel
+
+ Window
+ Vindue
+
+
+ Sequence
+ Sekvens
+
+
+ Default sequence
+ Standardsekvens
+
+
+
+ AutoTypeSelectDialog
+
+ Auto-Type - KeePassX
+
+
+
+ Select entry to Auto-Type:
+
+
+
+
+ ChangeMasterKeyWidget
+
+ Password
+ Adgangskode
+
+
+ Enter password:
+ Indtast adgangskode
+
+
+ Repeat password:
+ Gentag adgangskode
+
+
+ Key file
+ Nøglefil
+
+
+ Browse
+ Gennemse
+
+
+ Create
+ Opret
+
+
+ Key files
+ Nøglefiler
+
+
+ All files
+ Alle filer
+
+
+ Create Key File...
+ Opret Nøglefil...
+
+
+ Error
+ Fejl
+
+
+ Unable to create Key File :
+ Kan ikke oprette Nøglefil :
+
+
+ Select a key file
+ Vælg en nøglefil
+
+
+ Question
+ Spørgsmål
+
+
+ Do you really want to use an empty string as password?
+ Vil du virkelig bruge en tom streng som adgangskode?
+
+
+ Different passwords supplied.
+ Andre adgangskoder leveret.
+
+
+
+ DatabaseOpenWidget
+
+ Enter master key
+ Indtast primærnøgle
+
+
+ Key File:
+ Nøglefil:
+
+
+ Password:
+ Adgangskode:
+
+
+ Browse
+ Gennemse
+
+
+ Error
+ Fejl
+
+
+ Unable to open the database.
+ Kan ikke åbne databasen.
+
+
+ Can't open key file
+ Kan ikke åbne nøglefil
+
+
+ All files
+ Alle filer
+
+
+ Key files
+ Nøglefiler
+
+
+ Select key file
+ Vælg nøglefil
+
+
+
+ DatabaseSettingsWidget
+
+ Database name:
+ Databasenavn:
+
+
+ Database description:
+ Beskrivelse af database:
+
+
+ Transform rounds:
+
+
+
+ Default username:
+ Standard brugernavn:
+
+
+ Use recycle bin:
+ Brug skraldespand:
+
+
+ MiB
+ MB
+
+
+ Benchmark
+ Benchmark
+
+
+ Max. history items:
+
+
+
+ Max. history size:
+
+
+
+
+ DatabaseTabWidget
+
+ Root
+
+
+
+ KeePass 2 Database
+ KeePass 2 Database
+
+
+ All files
+ Alle filer
+
+
+ Open database
+ Åben database
+
+
+ Warning
+ Advarsel
+
+
+ File not found!
+ Filen blev ikke fundet!
+
+
+ Open KeePass 1 database
+ Åben KeePass 1 database
+
+
+ KeePass 1 database
+ KeePass 1 database
+
+
+ All files (*)
+ Alle filer (*)
+
+
+ Close?
+ Luk?
+
+
+ "%1" is in edit mode.
+Close anyway?
+
+
+
+ Save changes?
+ Gem ændringer?
+
+
+ "%1" was modified.
+Save changes?
+
+
+
+ Error
+ Fejl
+
+
+ Writing the database failed.
+ Kan ikke skrive til databasen.
+
+
+ Save database as
+ Gem database som
+
+
+ New database
+ Ny database
+
+
+ locked
+ låst
+
+
+
+ DatabaseWidget
+
+ Change master key
+ Skift primærnøgle
+
+
+ Delete entry?
+ Slet post?
+
+
+ Do you really want to delete the entry "%1" for good?
+
+
+
+ Delete entries?
+ Slet poster?
+
+
+ Do you really want to delete %1 entries for good?
+
+
+
+ Move entries to recycle bin?
+ Flyt poster til skraldespanden?
+
+
+ Do you really want to move %n entry(s) to the recycle bin?
+
+
+
+ Delete group?
+ Slet gruppe?
+
+
+ Do you really want to delete the group "%1" for good?
+
+
+
+ Current group
+ Nuværende gruppe
+
+
+
+ EditEntryWidget
+
+ Entry
+ Post
+
+
+ Advanced
+ Avanceret
+
+
+ Icon
+ Ikon
+
+
+ Auto-Type
+
+
+
+ Properties
+ Egenskaber
+
+
+ History
+ Historik
+
+
+ Entry history
+
+
+
+ Add entry
+ Tilføj post
+
+
+ Edit entry
+ Rediger post
+
+
+ Error
+ Fejl
+
+
+ Different passwords supplied.
+ Andre adgangskoder leveret.
+
+
+ New attribute
+ Ny attribut
+
+
+ Select file
+ Vælg fil
+
+
+ Unable to open file
+ Filen kan ikke åbnes
+
+
+ Save attachment
+ Gem vedhæftning
+
+
+ Unable to save the attachment:
+
+
+
+
+ Tomorrow
+ I morgen
+
+
+ %n week(s)
+ %n uge%n uger
+
+
+ %n month(s)
+ %n måned%n måneder
+
+
+ 1 year
+ Et år
+
+
+
+ EditEntryWidgetAdvanced
+
+ Additional attributes
+ Yderligere attributter
+
+
+ Add
+ Tilføj
+
+
+ Edit
+ Rediger
+
+
+ Remove
+ Fjern
+
+
+ Attachments
+ Vedhæftninger
+
+
+ Save
+ Gem
+
+
+
+ EditEntryWidgetAutoType
+
+ Enable Auto-Type for this entry
+
+
+
+ Inherit default Auto-Type sequence from the group
+
+
+
+ Use custom Auto-Type sequence:
+
+
+
+ +
+ +
+
+
+ -
+ -
+
+
+ Window title:
+ Vinduestitel:
+
+
+ Use default sequence
+
+
+
+ Set custom sequence:
+
+
+
+
+ EditEntryWidgetHistory
+
+ Show
+ Vis
+
+
+ Restore
+ Gendan
+
+
+ Delete
+ Slet
+
+
+ Delete all
+ Slet alle
+
+
+
+ EditEntryWidgetMain
+
+ Title:
+ Titel:
+
+
+ Username:
+ Brugernavn:
+
+
+ Password:
+ Adgangskode:
+
+
+ Repeat:
+ Gentag:
+
+
+ Gen.
+
+
+
+ URL:
+ URL:
+
+
+ Expires
+ Udløber
+
+
+ Presets
+
+
+
+ Notes:
+ Noter:
+
+
+
+ EditGroupWidget
+
+ Group
+ Gruppe
+
+
+ Icon
+ Ikon
+
+
+ Properties
+ Egenskaber
+
+
+ Add group
+ Tilføj gruppe
+
+
+ Edit group
+ Rediger gruppe
+
+
+ Enable
+ Aktiver
+
+
+ Disable
+ Deaktivér
+
+
+ Inherit from parent group (%1)
+ Arv fra forældregruppe (%1)
+
+
+
+ EditGroupWidgetMain
+
+ Name
+ Navn
+
+
+ Notes
+ Noter
+
+
+ Expires
+ Udløber
+
+
+ Search
+ Søg
+
+
+ Auto-type
+
+
+
+
+ EditWidgetIcons
+
+ Use default icon
+ Brug standardikon
+
+
+ Use custom icon
+ Brug brugerbestemt ikon
+
+
+ Add custom icon
+ Tilføj brugerbestemt ikon
+
+
+ Delete custom icon
+ Slet brugerbestemt ikon
+
+
+ Images
+ Billeder
+
+
+ All files
+ Alle filer
+
+
+ Select Image
+ Vælg Billede
+
+
+ Can't delete icon!
+ Kan ikke slette ikon!
+
+
+ Can't delete icon. Still used by %n item(s).
+
+
+
+
+ EditWidgetProperties
+
+ Created:
+ Oprettet:
+
+
+ Modified:
+ Ændret:
+
+
+ Accessed:
+ Tilgået:
+
+
+ Uuid:
+ Uuid:
+
+
+
+ EntryAttributesModel
+
+ Name
+ Navn
+
+
+
+ EntryHistoryModel
+
+ Last modified
+ Sidst ændret
+
+
+ Title
+ Titel
+
+
+ Username
+ Brugernavn
+
+
+ URL
+ URL
+
+
+
+ EntryModel
+
+ Group
+ Gruppe
+
+
+ Title
+ Titel
+
+
+ Username
+ Brugernavn
+
+
+ URL
+ URL
+
+
+
+ Group
+
+ Recycle Bin
+ Skraldespand
+
+
+
+ KeePass1OpenWidget
+
+ Import KeePass1 database
+ Importér KeePass1 database
+
+
+ Error
+ Fejl
+
+
+ Unable to open the database.
+ Kan ikke åbne databasen.
+
+
+
+ KeePass1Reader
+
+ Unable to read keyfile.
+ Kan ikke indlæse nøglefil.
+
+
+ Not a KeePass database.
+ Ikke en KeePass database.
+
+
+ Unsupported encryption algorithm.
+ Ikke understøttet krypteringsalgoritme
+
+
+ Unsupported KeePass database version.
+ Ikke understøttet KeePass database version
+
+
+ Root
+
+
+
+
+ KeePass2Reader
+
+ Not a KeePass database.
+ Det er ikke en KeePass database.
+
+
+ Unsupported KeePass database version.
+ KeePass database version ikke understøttet.
+
+
+ Wrong key or database file is corrupt.
+ Forkert nøgle eller databasefil er korrupt.
+
+
+
+ Main
+
+ Fatal error while testing the cryptographic functions.
+ Fatal fejl ved test af kryptografiske funktioner.
+
+
+ KeePassX - Error
+ KeePassX - Fejl
+
+
+
+ MainWindow
+
+ Database
+ Database
+
+
+ Recent databases
+ Seneste databaser
+
+
+ Help
+ Hjælp
+
+
+ Entries
+ Poster
+
+
+ Copy attribute to clipboard
+ Kopiér attribut til udklipsholder
+
+
+ Groups
+ Grupper
+
+
+ Extras
+
+
+
+ View
+ Vis
+
+
+ Quit
+ Afslut
+
+
+ About
+ Om
+
+
+ Open database
+ Åben database
+
+
+ Save database
+ Gem database
+
+
+ Close database
+ Luk databasen
+
+
+ New database
+ Ny database
+
+
+ Add new entry
+ Tilføj ny post
+
+
+ View/Edit entry
+ Vis/Rediger post
+
+
+ Delete entry
+ Slet post
+
+
+ Add new group
+ Tilføj ny gruppe
+
+
+ Edit group
+ Rediger gruppe
+
+
+ Delete group
+ Slet gruppe
+
+
+ Save database as
+ Gem database som
+
+
+ Change master key
+ Skift primærnøgle
+
+
+ Database settings
+ Databaseindstillinger
+
+
+ Import KeePass 1 database
+ Importér KeePass 1 database
+
+
+ Clone entry
+ Klon post
+
+
+ Find
+ Find
+
+
+ Username
+ Brugernavn
+
+
+ Copy username to clipboard
+ Kopiér brugernavn til udklipsholder
+
+
+ Password
+ Adgangskode
+
+
+ Copy password to clipboard
+ Kopiér adgangskode til udklipsholder
+
+
+ Settings
+ Indstillinger
+
+
+ Perform Auto-Type
+
+
+
+ Open URL
+ Åben URL
+
+
+ Lock databases
+ Lås databaser
+
+
+ Title
+ Titel
+
+
+ URL
+ URL
+
+
+ Notes
+ Noter
+
+
+ Show toolbar
+ Vis værktøjslinie
+
+
+ read-only
+ skrivebeskyttet
+
+
+ Toggle window
+ Skift vindue
+
+
+
+ PasswordGeneratorWidget
+
+ Password:
+ Adgangskode:
+
+
+ Length:
+ Længde:
+
+
+ Character Types
+
+
+
+ Upper Case Letters
+ Store Bogstaver
+
+
+ Lower Case Letters
+
+
+
+ Numbers
+ Numre
+
+
+ Special Characters
+
+
+
+ Exclude look-alike characters
+
+
+
+ Ensure that the password contains characters from every group
+
+
+
+ Accept
+ Acceptér
+
+
+
+ QCommandLineParser
+
+ Displays version information.
+ Vis versionsinformation
+
+
+ Displays this help.
+ Vis denne hjælp.
+
+
+ Unknown option '%1'.
+ Ukendt valgmulighed '%1'.
+
+
+ Unknown options: %1.
+ Ukendt valgmuligheder '%1'.
+
+
+ Missing value after '%1'.
+ Manglende værdi efter '%1'.
+
+
+ Unexpected value after '%1'.
+ Uventet værdi efter '%1'.
+
+
+ [options]
+ [Valgmuligheder]
+
+
+ Usage: %1
+ Brug: %1
+
+
+ Options:
+ Muligheder:
+
+
+ Arguments:
+ Argumenter:
+
+
+
+ QSaveFile
+
+ Existing file %1 is not writable
+ Eksisterende fil %1 er ikke skrivbar
+
+
+ Writing canceled by application
+ Skrivning afbrudt af programmet
+
+
+ Partial write. Partition full?
+ Delvis gemt. Diskafsnit fyldt op?
+
+
+
+ QtIOCompressor
+
+ Internal zlib error when compressing:
+ Intern zlib-fejl ved komprimering:
+
+
+ Error writing to underlying device:
+
+
+
+ Error opening underlying device:
+
+
+
+ Error reading data from underlying device:
+ Fejl ved læsning af data fra underliggende enhed:
+
+
+ Internal zlib error when decompressing:
+
+
+
+
+ QtIOCompressor::open
+
+ The gzip format not supported in this version of zlib.
+ Gzip-format understøttes ikke i denne version af zlib.
+
+
+ Internal zlib error:
+ Intern zlib fejl:
+
+
+
+ SearchWidget
+
+ Find:
+ Find:
+
+
+ Case sensitive
+
+
+
+ Current group
+ Nuværende gruppe
+
+
+ Root group
+
+
+
+
+ SettingsWidget
+
+ Application Settings
+ Programindstillinger
+
+
+ General
+
+
+
+ Security
+ Sikkerhed
+
+
+
+ SettingsWidgetGeneral
+
+ Remember last databases
+
+
+
+ Open previous databases on startup
+
+
+
+ Mark as modified on expanded state changes
+
+
+
+ Automatically save on exit
+ Gem automatisk ved afslutning
+
+
+ Automatically save after every change
+ Gem automatisk ved ændringer
+
+
+ Minimize when copying to clipboard
+ Minimér ved kopiering til udklipsholder
+
+
+ Use group icon on entry creation
+
+
+
+ Global Auto-Type shortcut
+
+
+
+ Use entry title to match windows for global auto-type
+
+
+
+ Language
+ Sprog
+
+
+ Show a system tray icon
+ Vis et ikon i systembakken
+
+
+ Hide window to system tray when minimized
+ Skjul vindue i systembakken når det er minimeret
+
+
+
+ SettingsWidgetSecurity
+
+ Clear clipboard after
+ Ryd udklipsholder efter
+
+
+ sec
+ sek
+
+
+ Lock databases after inactivity of
+
+
+
+ Show passwords in cleartext by default
+
+
+
+ Always ask before performing auto-type
+
+
+
+
+ UnlockDatabaseWidget
+
+ Unlock database
+ Lås database op
+
+
+ Error
+ Fejl
+
+
+ Wrong key.
+ Forkert nøgle.
+
+
+
+ WelcomeWidget
+
+ Welcome!
+ Velkommen!
+
+
+
+ main
+
+ KeePassX - cross-platform password manager
+
+
+
+ filename of the password database to open (*.kdbx)
+ filnavn på databasen der skal åbnes (* .kdbx)
+
+
+ path to a custom config file
+
+
+
+ password of the database (DANGEROUS!)
+ adgangskode til databasen (FAARLIGT!)
+
+
+ key file of the database
+ databasens nøglefil
+
+
+
\ No newline at end of file
diff --git a/share/translations/keepassx_de.ts b/share/translations/keepassx_de.ts
index 5433c3ce..59ea1947 100644
--- a/share/translations/keepassx_de.ts
+++ b/share/translations/keepassx_de.ts
@@ -17,8 +17,8 @@
Auto-Type - KeePassX
- Couldn't find an entry that matches the window title.
- Konnte dem Fenstertitel keinen passenden Eintrag zuordnen.
+ Couldn't find an entry that matches the window title:
+ Konnte keinen Eintrag finden, welcher mit dem Fenstertitel übereinstimmt:
@@ -138,7 +138,7 @@
Can't open key file
- Schlüsseldatein kann nicht geöffnet werden
+ Schlüsseldatei kann nicht geöffnet werden
All files
@@ -185,7 +185,7 @@
Max. history items:
- Max Einträge im Verlauf:
+ Max. Einträge im Verlauf:
Max. history size:
@@ -739,6 +739,17 @@ Save changes?
Falscher Schlüssel oder die Datei ist beschädigt.
+
+ Main
+
+ Fatal error while testing the cryptographic functions.
+ Fataler Fehler beim Testen der kryptografischen Funktionen.
+
+
+ KeePassX - Error
+ KeePassX - Fehler
+
+
MainWindow
@@ -897,6 +908,10 @@ Save changes?
read-only
Nur Lesezugriff
+
+ Toggle window
+ Fenster zeigen/verstecken
+
PasswordGeneratorWidget
@@ -1105,6 +1120,18 @@ Save changes?
Use entry title to match windows for global auto-type
Verwende den Eintragstitel für entsprechende Fenster für den globale Auto-Typ
+
+ Language
+ Sprache
+
+
+ Show a system tray icon
+ Taskleistensymbol anzeigen
+
+
+ Hide window to system tray when minimized
+ Fenster verstecken wenn minimiert
+
SettingsWidgetSecurity
diff --git a/share/translations/keepassx_en.ts b/share/translations/keepassx_en.ts
index a7b5b109..af21c20c 100644
--- a/share/translations/keepassx_en.ts
+++ b/share/translations/keepassx_en.ts
@@ -111,6 +111,15 @@
Different passwords supplied.
+
+ Failed to set key file
+
+
+
+ Failed to set %1 as the Key file:
+%2
+
+
DatabaseOpenWidget
@@ -270,6 +279,31 @@ Save changes?
locked
+
+ The database you are trying to open is locked by another instance of KeePassX.
+Do you want to open it anyway? Alternatively the database is opened read-only.
+
+
+
+ Lock database
+
+
+
+ Can't lock the database as you are currently editing it.
+Please press cancel to finish your changes or discard them.
+
+
+
+ This database has never been saved.
+You can save the database or stop locking it.
+
+
+
+ This database has been modified.
+Do you want to save the database before locking it?
+Otherwise your changes are lost.
+
+
DatabaseWidget
@@ -316,6 +350,14 @@ Save changes?
Current group
+
+ Error
+
+
+
+ Unable to calculate master key
+
+
EditEntryWidget
@@ -433,6 +475,10 @@ Save changes?
Save
+
+ Open
+
+
EditEntryWidgetAutoType
@@ -584,6 +630,14 @@ Save changes?
Auto-type
+
+ Use default auto-type sequence of parent group
+
+
+
+ Set default auto-type sequence
+
+
EditWidgetIcons
@@ -735,6 +789,10 @@ Save changes?
Root
+
+ Unable to calculate master key
+
+
KeePass2Reader
@@ -750,6 +808,10 @@ Save changes?
Wrong key or database file is corrupt.
+
+ Unable to calculate master key
+
+
Main
@@ -788,10 +850,6 @@ Save changes?
Groups
-
- Extras
-
-
View
@@ -924,6 +982,10 @@ Save changes?
Toggle window
+
+ Tools
+
+
PasswordGeneratorWidget
@@ -1104,10 +1166,6 @@ Save changes?
Open previous databases on startup
-
- Mark as modified on expanded state changes
-
-
Automatically save on exit
@@ -1144,6 +1202,10 @@ Save changes?
Hide window to system tray when minimized
+
+ Remember last key files
+
+
SettingsWidgetSecurity
@@ -1174,14 +1236,6 @@ Save changes?
Unlock database
-
- Error
-
-
-
- Wrong key.
-
-
WelcomeWidget
diff --git a/share/translations/keepassx_es.ts b/share/translations/keepassx_es.ts
new file mode 100644
index 00000000..b3f6b6ff
--- /dev/null
+++ b/share/translations/keepassx_es.ts
@@ -0,0 +1,1205 @@
+
+
+ AboutDialog
+
+ About KeePassX
+ Acerca de KeePassX
+
+
+ KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3.
+ KeePassX es distribuido bajo los términos de la versión 2 de la Licencia Pública GNU (GPL) o por la versión 3 (si así lo prefiere).
+
+
+
+ AutoType
+
+ Auto-Type - KeePassX
+ Auto-Escritura - KeePassX
+
+
+ Couldn't find an entry that matches the window title:
+ No se puede encontrar una entrada que corresponda al título de la ventana:
+
+
+
+ AutoTypeAssociationsModel
+
+ Window
+ Ventana
+
+
+ Sequence
+ Secuencia
+
+
+ Default sequence
+ Secuencia por defecto
+
+
+
+ AutoTypeSelectDialog
+
+ Auto-Type - KeePassX
+ Auto-Escritura - KeePassX
+
+
+ Select entry to Auto-Type:
+ Seleccionar entrada para Auto-Escritura:
+
+
+
+ ChangeMasterKeyWidget
+
+ Password
+ Contraseña
+
+
+ Enter password:
+ Ingrese la contraseña
+
+
+ Repeat password:
+ Repita la contraseña:
+
+
+ Key file
+ Archivo llave
+
+
+ Browse
+ Abrir archivo
+
+
+ Create
+ Crear
+
+
+ Key files
+ Archivos de llaves
+
+
+ All files
+ Todos los archivos
+
+
+ Create Key File...
+ Crear un Archivo Llave ....
+
+
+ Error
+ Error
+
+
+ Unable to create Key File :
+ No se puede crear el Archivo Llave:
+
+
+ Select a key file
+ Seleccione un archivo llave
+
+
+ Question
+ Pregunta
+
+
+ Do you really want to use an empty string as password?
+ ¿Realmente desea usar una cadena vacía como contraseña?
+
+
+ Different passwords supplied.
+ Las contraseñas ingresadas son distintas.
+
+
+
+ DatabaseOpenWidget
+
+ Enter master key
+ Ingrese la clave maestra
+
+
+ Key File:
+ Archivo clave:
+
+
+ Password:
+ Contraseña:
+
+
+ Browse
+ Navegar
+
+
+ Error
+ Error
+
+
+ Unable to open the database.
+ Incapaz de abrir la base de datos.
+
+
+ Can't open key file
+ No se puede abrir el archivo llave
+
+
+ All files
+ Todos los archivos
+
+
+ Key files
+ Archivos llave
+
+
+ Select key file
+ Seleccionar archivo llave
+
+
+
+ DatabaseSettingsWidget
+
+ Database name:
+ Nombre de la base de datos:
+
+
+ Database description:
+ Descripción de la base de datos:
+
+
+ Transform rounds:
+ Rondas de transformación:
+
+
+ Default username:
+ Nombre de usuario por defecto:
+
+
+ Use recycle bin:
+ Usar papelera de reciclaje:
+
+
+ MiB
+ MiB
+
+
+ Benchmark
+ Prueba de rendimiento
+
+
+ Max. history items:
+ Elementos máximos del historial:
+
+
+ Max. history size:
+ Tamaño máximo del historial:
+
+
+
+ DatabaseTabWidget
+
+ Root
+
+
+
+ KeePass 2 Database
+ Base de datos KeePass 2
+
+
+ All files
+ Todos los archivos
+
+
+ Open database
+ Abrir base de datos
+
+
+ Warning
+ Advertencia
+
+
+ File not found!
+ ¡Archivo no encontrado!
+
+
+ Open KeePass 1 database
+ Abrir base de datos KeePass 1
+
+
+ KeePass 1 database
+ Base de datos KeePass 1
+
+
+ All files (*)
+ Todos los archivos (*)
+
+
+ Close?
+ ¿Cerrar?
+
+
+ "%1" is in edit mode.
+Close anyway?
+ "%1" está en modo de edición.
+¿Cerrar de todas formas?
+
+
+ Save changes?
+ ¿Guardar cambios?
+
+
+ "%1" was modified.
+Save changes?
+ "%1" ha sido modificado.
+¿Guardar cambios?
+
+
+ Error
+ Error
+
+
+ Writing the database failed.
+ La escritura de la base de datos falló.
+
+
+ Save database as
+ Guardar base de datos como
+
+
+ New database
+ Nueva base de datos
+
+
+ locked
+ bloqueado
+
+
+
+ DatabaseWidget
+
+ Change master key
+ Cambiar la llave maestra
+
+
+ Delete entry?
+ ¿Eliminar la entrada?
+
+
+ Do you really want to delete the entry "%1" for good?
+ ¿Realmente quiere eliminar la entrada "%1" de forma definitiva?
+
+
+ Delete entries?
+ ¿Eliminar entradas?
+
+
+ Do you really want to delete %1 entries for good?
+ ¿Realmente quiere eliminar las entradas "%1" de forma definitiva?
+
+
+ Move entries to recycle bin?
+ ¿Mover entradas a la papelera de reciclaje?
+
+
+ Do you really want to move %n entry(s) to the recycle bin?
+ ¿Realmente quiere mover la entrada "%1" a la papelera de reciclaje?¿Realmente quiere mover las entradas "%1" a la papelera de reciclaje?
+
+
+ Delete group?
+ ¿Eliminar grupo?
+
+
+ Do you really want to delete the group "%1" for good?
+ ¿Realmente quiere eliminar el grupo "%1" de forma definitiva?
+
+
+ Current group
+ Grupo actual
+
+
+
+ EditEntryWidget
+
+ Entry
+ Entrada
+
+
+ Advanced
+ Avanzado
+
+
+ Icon
+ Icono
+
+
+ Auto-Type
+ Auto-Escritura
+
+
+ Properties
+ Propiedades
+
+
+ History
+ Historial
+
+
+ Entry history
+ Historial de entradas
+
+
+ Add entry
+ Añadir entrada
+
+
+ Edit entry
+ Editar entrada
+
+
+ Error
+ Error
+
+
+ Different passwords supplied.
+ Las contraseñas ingresadas son distintas.
+
+
+ New attribute
+ Nuevo atributo
+
+
+ Select file
+ Seleccionar archivo llave
+
+
+ Unable to open file
+ Incapaz de abrir el archivo
+
+
+ Save attachment
+ Guardar adjunto
+
+
+ Unable to save the attachment:
+
+ Incapaz de guardar el adjunto:
+
+
+
+ Tomorrow
+ Mañana
+
+
+ %n week(s)
+ %n semana%n semanas
+
+
+ %n month(s)
+ %n mes%n meses
+
+
+ 1 year
+ 1 año
+
+
+
+ EditEntryWidgetAdvanced
+
+ Additional attributes
+ Atributos adicionales
+
+
+ Add
+ Añadir
+
+
+ Edit
+ Editar
+
+
+ Remove
+ Eliminar
+
+
+ Attachments
+ Adjuntos
+
+
+ Save
+ Guardar
+
+
+
+ EditEntryWidgetAutoType
+
+ Enable Auto-Type for this entry
+ Activar Auto-Escritura para esta entrada
+
+
+ Inherit default Auto-Type sequence from the group
+ Heredar Auto-Escritura por defecto del grupo
+
+
+ Use custom Auto-Type sequence:
+ Utilizar secuencia de Auto-Escritura personalizada:
+
+
+ +
+ +
+
+
+ -
+ -
+
+
+ Window title:
+ Título de la ventana:
+
+
+ Use default sequence
+ Utilizar secuencia por defecto
+
+
+ Set custom sequence:
+ Definir secuencia personalizada:
+
+
+
+ EditEntryWidgetHistory
+
+ Show
+ Mostrar
+
+
+ Restore
+ Recuperar
+
+
+ Delete
+ Eliminar
+
+
+ Delete all
+ Eliminar todo
+
+
+
+ EditEntryWidgetMain
+
+ Title:
+ Título:
+
+
+ Username:
+ Nombre de usuario:
+
+
+ Password:
+ Contraseña:
+
+
+ Repeat:
+ Repetir:
+
+
+ Gen.
+ Gen.
+
+
+ URL:
+ URL:
+
+
+ Expires
+ Expira
+
+
+ Presets
+ Predeterminado
+
+
+ Notes:
+ Notas:
+
+
+
+ EditGroupWidget
+
+ Group
+ Grupo
+
+
+ Icon
+ Icono
+
+
+ Properties
+ Propiedades
+
+
+ Add group
+ Añadir grupo
+
+
+ Edit group
+ Editar grupo
+
+
+ Enable
+ Habilitar
+
+
+ Disable
+ Deshabilitar
+
+
+ Inherit from parent group (%1)
+ Heredar del grupo padre (%1)
+
+
+
+ EditGroupWidgetMain
+
+ Name
+ Nombre
+
+
+ Notes
+ Notas
+
+
+ Expires
+ Expira
+
+
+ Search
+ Buscar
+
+
+ Auto-type
+ Auto-escritura
+
+
+
+ EditWidgetIcons
+
+ Use default icon
+ Usar icono por defecto
+
+
+ Use custom icon
+ Usar icono personalizado
+
+
+ Add custom icon
+ Añadir icono personalizado
+
+
+ Delete custom icon
+ Eliminar icono personalizado
+
+
+ Images
+ Imágenes
+
+
+ All files
+ Todos los archivos
+
+
+ Select Image
+ Seleccionar imagen
+
+
+ Can't delete icon!
+ ¡No se puede eliminar el icono!
+
+
+ Can't delete icon. Still used by %n item(s).
+ No se puede eliminar el icono. Utilizado aún en %n elementoNo se puede eliminar el icono. Utilizado aún en %n elementos
+
+
+
+ EditWidgetProperties
+
+ Created:
+ Creado:
+
+
+ Modified:
+ Modificado:
+
+
+ Accessed:
+ Accedido:
+
+
+ Uuid:
+ Uuid:
+
+
+
+ EntryAttributesModel
+
+ Name
+ Nombre
+
+
+
+ EntryHistoryModel
+
+ Last modified
+ Última modificación
+
+
+ Title
+ Título
+
+
+ Username
+ Nombre de usuario
+
+
+ URL
+ URL
+
+
+
+ EntryModel
+
+ Group
+ Grupo
+
+
+ Title
+ Título
+
+
+ Username
+ Nombre de usuario:
+
+
+ URL
+ URL
+
+
+
+ Group
+
+ Recycle Bin
+ Papelera de reciclaje
+
+
+
+ KeePass1OpenWidget
+
+ Import KeePass1 database
+ Importar base de datos KeePass1
+
+
+ Error
+ Error
+
+
+ Unable to open the database.
+ Incapaz de abrir la base de datos.
+
+
+
+ KeePass1Reader
+
+ Unable to read keyfile.
+ Incapaz de leer el archivo
+
+
+ Not a KeePass database.
+ No es una base de datos KeePass.
+
+
+ Unsupported encryption algorithm.
+ Algoritmo de cifrado no soportado.
+
+
+ Unsupported KeePass database version.
+ Versión de la base de datos KeePass no soportada.
+
+
+ Root
+
+
+
+
+ KeePass2Reader
+
+ Not a KeePass database.
+ No es una base de datos KeePass.
+
+
+ Unsupported KeePass database version.
+ Versión de la base de datos KeePass no soportada.
+
+
+ Wrong key or database file is corrupt.
+ La contraseña es incorrecta o el archivo está dañado
+
+
+
+ Main
+
+ Fatal error while testing the cryptographic functions.
+ Error fatal comprobando las funciones criptográficas.
+
+
+ KeePassX - Error
+ KeePassX - Error
+
+
+
+ MainWindow
+
+ Database
+ Base de datos
+
+
+ Recent databases
+ Bases de datos recientes
+
+
+ Help
+ Ayuda
+
+
+ Entries
+ Entradas
+
+
+ Copy attribute to clipboard
+ Copiar atributo al portapapeles
+
+
+ Groups
+ Grupos
+
+
+ Extras
+ Extras
+
+
+ View
+ Ver
+
+
+ Quit
+ Salir
+
+
+ About
+ Acerca de
+
+
+ Open database
+ Abrir base de datos
+
+
+ Save database
+ Guardar base de datos
+
+
+ Close database
+ Cerrar base de datos
+
+
+ New database
+ Nueva base de datos
+
+
+ Add new entry
+ Añadir nueva entrada
+
+
+ View/Edit entry
+ Ver/Editar entrada
+
+
+ Delete entry
+ Eliminar entrada
+
+
+ Add new group
+ Añadir nuevo grupo
+
+
+ Edit group
+ Editar grupo
+
+
+ Delete group
+ Eliminar grupo
+
+
+ Save database as
+ Guardar base de datos como
+
+
+ Change master key
+ Cambiar la llave maestra
+
+
+ Database settings
+ Configuración de la base de datos
+
+
+ Import KeePass 1 database
+ Importar base de datos KeePass 1
+
+
+ Clone entry
+ Clonar entrada
+
+
+ Find
+ Buscar
+
+
+ Username
+ Usuario
+
+
+ Copy username to clipboard
+ Copiar nombre de usuario al portapapeles
+
+
+ Password
+ Contraseña
+
+
+ Copy password to clipboard
+ Copiar contraseña al portapapeles
+
+
+ Settings
+ Configuración
+
+
+ Perform Auto-Type
+ Relizar Auto-Escritura
+
+
+ Open URL
+ Abrir URL
+
+
+ Lock databases
+ Bloquear base de datos
+
+
+ Title
+ Título
+
+
+ URL
+ URL
+
+
+ Notes
+ Notas
+
+
+ Show toolbar
+ Mostrar barra de herramientas
+
+
+ read-only
+ sólo lectura
+
+
+ Toggle window
+ Cambiar a ventana
+
+
+
+ PasswordGeneratorWidget
+
+ Password:
+ Contraseña:
+
+
+ Length:
+ Longitud:
+
+
+ Character Types
+ Tipos de caracteres
+
+
+ Upper Case Letters
+ Letras mayúsculas
+
+
+ Lower Case Letters
+ Letras minúsculas
+
+
+ Numbers
+ Números
+
+
+ Special Characters
+ Caracteres especiales:
+
+
+ Exclude look-alike characters
+ Excluir caracteres similares
+
+
+ Ensure that the password contains characters from every group
+ Asegurar que la contraseña contiene caracteres de todos los grupos
+
+
+ Accept
+ Aceptar
+
+
+
+ QCommandLineParser
+
+ Displays version information.
+ Muestra información de versión.
+
+
+ Displays this help.
+ Muestra esta ayuda.
+
+
+ Unknown option '%1'.
+ Opción desconocida '%1'.
+
+
+ Unknown options: %1.
+ Opciones desconocidas '%1'.
+
+
+ Missing value after '%1'.
+ Falta valor después de '%1',
+
+
+ Unexpected value after '%1'.
+ Valor inesperado después de '%1'.
+
+
+ [options]
+ [opciones]
+
+
+ Usage: %1
+ Uso: %1
+
+
+ Options:
+ Opciones:
+
+
+ Arguments:
+ Argumentos:
+
+
+
+ QSaveFile
+
+ Existing file %1 is not writable
+ El archivo existente %1 no se puede sobrescribir
+
+
+ Writing canceled by application
+ escritura cancelada por la aplicación
+
+
+ Partial write. Partition full?
+ Escritura parcial. ¿La partición está llena?
+
+
+
+ QtIOCompressor
+
+ Internal zlib error when compressing:
+ Error interno de zlib comprimiendo:
+
+
+ Error writing to underlying device:
+ Error al escribir en el dispositivo subyacente:
+
+
+ Error opening underlying device:
+ Error al abrir el dispositivo subyacente:
+
+
+ Error reading data from underlying device:
+ Error al leer el dispositivo subyacente:
+
+
+ Internal zlib error when decompressing:
+ Error interno de zlib descomprimiendo:
+
+
+
+ QtIOCompressor::open
+
+ The gzip format not supported in this version of zlib.
+ El formato gzip no está soportado en esta versión de zlib.
+
+
+ Internal zlib error:
+ Error interno de zlib:
+
+
+
+ SearchWidget
+
+ Find:
+ Buscar:
+
+
+ Case sensitive
+ Distinguir mayúsculas/minúsculas
+
+
+ Current group
+ Grupo actual
+
+
+ Root group
+
+
+
+
+ SettingsWidget
+
+ Application Settings
+ Configuración de la aplicación
+
+
+ General
+ General
+
+
+ Security
+ Seguridad
+
+
+
+ SettingsWidgetGeneral
+
+ Remember last databases
+ Recordar última base de datos
+
+
+ Open previous databases on startup
+ Abrir base de datos anterior al inicio
+
+
+ Mark as modified on expanded state changes
+ Marcar como modificado en los cambios de estado ampliados
+
+
+ Automatically save on exit
+ Guardar automáticamente al salir
+
+
+ Automatically save after every change
+ Guardar automáticamente después de cada cambio
+
+
+ Minimize when copying to clipboard
+ Minimizar al copiar al portapapeles
+
+
+ Use group icon on entry creation
+ Usar icono del grupo en la creación de entrada
+
+
+ Global Auto-Type shortcut
+ Atajo global de Auto-Escritura
+
+
+ Use entry title to match windows for global auto-type
+ Usar el título de la entrada para coincidir con la ventana para la auto-escritura global
+
+
+ Language
+ Idioma
+
+
+ Show a system tray icon
+ Mostrar icono en la bandeja de del sistema
+
+
+ Hide window to system tray when minimized
+ Ocultar la ventana a la bandeja del sistema cuando se minimiza
+
+
+
+ SettingsWidgetSecurity
+
+ Clear clipboard after
+ Limpiar el portapapeles después de
+
+
+ sec
+ segundos
+
+
+ Lock databases after inactivity of
+ Bloquear base de datos tras un periodo de inactividad de
+
+
+ Show passwords in cleartext by default
+ Mostrar contraseñas en texto claro por defecto
+
+
+ Always ask before performing auto-type
+ Preguntar siempre antes de realizar auto-escritura
+
+
+
+ UnlockDatabaseWidget
+
+ Unlock database
+ Desbloquear base de datos
+
+
+ Error
+ Error
+
+
+ Wrong key.
+ Clave incorrecta.
+
+
+
+ WelcomeWidget
+
+ Welcome!
+ Bienvenid@!
+
+
+
+ main
+
+ KeePassX - cross-platform password manager
+ KeePassX - gestor de claves multiplataforma
+
+
+ filename of the password database to open (*.kdbx)
+ nombre de archivo de la base de datos de contraseñas a abrir (*.kdbx)
+
+
+ path to a custom config file
+ ruta a un archivo de configuración personalizado
+
+
+ password of the database (DANGEROUS!)
+ contraseña de la base de datos (¡PELIGROSO!)
+
+
+ key file of the database
+ archivo llave de la base de datos
+
+
+
\ No newline at end of file
diff --git a/share/translations/keepassx_fr.ts b/share/translations/keepassx_fr.ts
new file mode 100644
index 00000000..cf575025
--- /dev/null
+++ b/share/translations/keepassx_fr.ts
@@ -0,0 +1,1205 @@
+
+
+ AboutDialog
+
+ About KeePassX
+ À propos de KeePassX
+
+
+ KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3.
+ KeePassX est distribué selon les conditions de la GNU General Public License (GPL) version 2 ou (à votre choix) version 3.
+
+
+
+ AutoType
+
+ Auto-Type - KeePassX
+ Auto-Type - KeePassX
+
+
+ Couldn't find an entry that matches the window title:
+ Impossible de trouver une entrée qui corresponde au titre de la fenêtre :
+
+
+
+ AutoTypeAssociationsModel
+
+ Window
+ Fenêtre
+
+
+ Sequence
+ Séquence
+
+
+ Default sequence
+ Séquence par défaut
+
+
+
+ AutoTypeSelectDialog
+
+ Auto-Type - KeePassX
+ Auto-Type - KeePassX
+
+
+ Select entry to Auto-Type:
+ Choisissez un champ pour Auto-Type :
+
+
+
+ ChangeMasterKeyWidget
+
+ Password
+ Mot de passe
+
+
+ Enter password:
+ Entrez un mot de passe :
+
+
+ Repeat password:
+ Confirmez le mot de passe :
+
+
+ Key file
+ Fichier de clé
+
+
+ Browse
+ Naviguer
+
+
+ Create
+ Créer
+
+
+ Key files
+ Fichiers de clé
+
+
+ All files
+ Tous les fichiers
+
+
+ Create Key File...
+ Créer un fichier de clé...
+
+
+ Error
+ Erreur
+
+
+ Unable to create Key File :
+ Incapable de créer un fichier de clé :
+
+
+ Select a key file
+ Choisir un fichier de clé
+
+
+ Question
+ Question
+
+
+ Do you really want to use an empty string as password?
+ Voulez-vous vraiment utiliser une chaîne vide comme mot de passe ?
+
+
+ Different passwords supplied.
+ Les mots de passe ne sont pas identiques.
+
+
+
+ DatabaseOpenWidget
+
+ Enter master key
+ Entrez la clé maîtresse
+
+
+ Key File:
+ Fichier de clé :
+
+
+ Password:
+ Mot de passe :
+
+
+ Browse
+ Naviguer
+
+
+ Error
+ Erreur
+
+
+ Unable to open the database.
+ Impossible d'ouvrir la base de données.
+
+
+ Can't open key file
+ Impossible d'ouvrir le fichier de clé
+
+
+ All files
+ Tous les fichiers
+
+
+ Key files
+ Fichiers de clé
+
+
+ Select key file
+ Choisissez un fichier de clé
+
+
+
+ DatabaseSettingsWidget
+
+ Database name:
+ Nom de la base de données :
+
+
+ Database description:
+ Description de la base de données :
+
+
+ Transform rounds:
+ Passes de transformation :
+
+
+ Default username:
+ Nom d'utilisateur par défaut :
+
+
+ Use recycle bin:
+ Utiliser la corbeille :
+
+
+ MiB
+ MiB
+
+
+ Benchmark
+ Base de référence
+
+
+ Max. history items:
+ Nombre max. d'éléments dans l'historique :
+
+
+ Max. history size:
+ Taille max. de l'historique :
+
+
+
+ DatabaseTabWidget
+
+ Root
+ Racine
+
+
+ KeePass 2 Database
+ Base de données Keepass 2
+
+
+ All files
+ Tous les fichiers
+
+
+ Open database
+ Ovrire la base de données
+
+
+ Warning
+ Attention
+
+
+ File not found!
+ Fichier introuvable!
+
+
+ Open KeePass 1 database
+ Ouvrir une base de données KeePass 1
+
+
+ KeePass 1 database
+ Base de données Keepass 1
+
+
+ All files (*)
+ Tous les fichiers (*)
+
+
+ Close?
+ Fermer?
+
+
+ "%1" is in edit mode.
+Close anyway?
+ "%1" est en cours de modification.
+Fermer quand même ?
+
+
+ Save changes?
+ Enregistrer les modifications ?
+
+
+ "%1" was modified.
+Save changes?
+ "%1" a été modifié.
+Enregistrer les modifications ?
+
+
+ Error
+ Erreur
+
+
+ Writing the database failed.
+ Une erreur s'est produite lors de l'écriture de la base de données.
+
+
+ Save database as
+ Enregistrer comme nouvelle base de données
+
+
+ New database
+ Nouvelle base de données
+
+
+ locked
+ verrouillée
+
+
+
+ DatabaseWidget
+
+ Change master key
+ Changer la clé maîtresse
+
+
+ Delete entry?
+ Supprimer l'entrée ?
+
+
+ Do you really want to delete the entry "%1" for good?
+ Voulez-vous supprimer l'entrée "%1" définitivement ?
+
+
+ Delete entries?
+ Supprimer les entrées ?
+
+
+ Do you really want to delete %1 entries for good?
+ Voulez-vous supprimer "%1" entrées définitivement ?
+
+
+ Move entries to recycle bin?
+ Déplacer les entrées vers la corbeille ?
+
+
+ Do you really want to move %n entry(s) to the recycle bin?
+ Voulez-vous déplacer %n entrée(s) vers la corbeille ?Voulez-vous déplacer %n entrée(s) vers la corbeille ?
+
+
+ Delete group?
+ Supprimer le groupe ?
+
+
+ Do you really want to delete the group "%1" for good?
+ Voulez-vous supprimer le groupe "%1" définitivement ?
+
+
+ Current group
+ Group actif
+
+
+
+ EditEntryWidget
+
+ Entry
+ Entrée
+
+
+ Advanced
+ Avancées
+
+
+ Icon
+ Icône
+
+
+ Auto-Type
+ Auto-Type
+
+
+ Properties
+ Propriétés
+
+
+ History
+ Historique
+
+
+ Entry history
+ Historique de l'entrée
+
+
+ Add entry
+ Ajouter une entrée
+
+
+ Edit entry
+ Modifier l'entrée
+
+
+ Error
+ Erreur
+
+
+ Different passwords supplied.
+ Les mots de passe ne sont pas identiques.
+
+
+ New attribute
+ Nouvel attribut
+
+
+ Select file
+ Choisissez un fichier
+
+
+ Unable to open file
+ Impossible d'ouvrir le fichier
+
+
+ Save attachment
+ Enregistrer le fichier attaché
+
+
+ Unable to save the attachment:
+
+ Impossible de enregistrer le fichier attaché:
+
+
+
+ Tomorrow
+ Demain
+
+
+ %n week(s)
+ %n semaine(s)%n semaine(s)
+
+
+ %n month(s)
+ %n mois%n mois
+
+
+ 1 year
+ Une année
+
+
+
+ EditEntryWidgetAdvanced
+
+ Additional attributes
+ Attributs additionnel
+
+
+ Add
+ Ajouter
+
+
+ Edit
+ Modifier
+
+
+ Remove
+ Supprimer
+
+
+ Attachments
+ Affichage
+
+
+ Save
+ Enregistrer le fichier
+
+
+
+ EditEntryWidgetAutoType
+
+ Enable Auto-Type for this entry
+ Activer l'Auto-Type pour cette entrée
+
+
+ Inherit default Auto-Type sequence from the group
+
+
+
+ Use custom Auto-Type sequence:
+ Utiliser une séquence d'Auto-Type personnalisée :
+
+
+ +
+ +
+
+
+ -
+ -
+
+
+ Window title:
+ Titre de la fenêtre:
+
+
+ Use default sequence
+ Utiliser la séquence par défaut
+
+
+ Set custom sequence:
+
+
+
+
+ EditEntryWidgetHistory
+
+ Show
+ Exposer
+
+
+ Restore
+ Rétablir
+
+
+ Delete
+ Supprimer
+
+
+ Delete all
+ Supprimer tout
+
+
+
+ EditEntryWidgetMain
+
+ Title:
+ Titre:
+
+
+ Username:
+ Non d'utilisateur:
+
+
+ Password:
+ Mot de passe:
+
+
+ Repeat:
+ Confirme:
+
+
+ Gen.
+ Gen.
+
+
+ URL:
+ URL:
+
+
+ Expires
+ Expiration
+
+
+ Presets
+ Valeurs par défaut
+
+
+ Notes:
+ Notes:
+
+
+
+ EditGroupWidget
+
+ Group
+ Groupe
+
+
+ Icon
+ Icône
+
+
+ Properties
+ Propriétés
+
+
+ Add group
+ Ajouter un groupe.
+
+
+ Edit group
+ Modifie le groupe
+
+
+ Enable
+ Activer
+
+
+ Disable
+ Désactiver
+
+
+ Inherit from parent group (%1)
+ Hériter du groupe parent (%1)
+
+
+
+ EditGroupWidgetMain
+
+ Name
+ Nom
+
+
+ Notes
+ Notes
+
+
+ Expires
+ Expires
+
+
+ Search
+ Chercher
+
+
+ Auto-type
+ Auto-type
+
+
+
+ EditWidgetIcons
+
+ Use default icon
+ Utiliser l'icône par défaut
+
+
+ Use custom icon
+ Utiliser une icône personnalisée
+
+
+ Add custom icon
+ Ajouter une icône personnalisée
+
+
+ Delete custom icon
+ Supprimer une icône personnalisée
+
+
+ Images
+ Images
+
+
+ All files
+ Tous les dossiers
+
+
+ Select Image
+ Choisis un image.
+
+
+ Can't delete icon!
+ Impossible de supprimer l'icône !
+
+
+ Can't delete icon. Still used by %n item(s).
+ Impossible de supprimer l'icône. Toujours utilisée par %n objet(s).Impossible de supprimer l'icône. Toujours utilisée par %n objet(s).
+
+
+
+ EditWidgetProperties
+
+ Created:
+ Créé le :
+
+
+ Modified:
+ Modifié le :
+
+
+ Accessed:
+ Accédé le :
+
+
+ Uuid:
+ Uuid:
+
+
+
+ EntryAttributesModel
+
+ Name
+ Nom
+
+
+
+ EntryHistoryModel
+
+ Last modified
+ Dernière modification
+
+
+ Title
+ Titre
+
+
+ Username
+ Nom d'utilisateur
+
+
+ URL
+ URL
+
+
+
+ EntryModel
+
+ Group
+ Groupe
+
+
+ Title
+ Titre
+
+
+ Username
+ Non d'utilisateur
+
+
+ URL
+ URL
+
+
+
+ Group
+
+ Recycle Bin
+ Bac de Recyclage
+
+
+
+ KeePass1OpenWidget
+
+ Import KeePass1 database
+ Importe un KeePass1 bas de données
+
+
+ Error
+ Erreur
+
+
+ Unable to open the database.
+ Impossible d'ouvrir la bas de données.
+
+
+
+ KeePass1Reader
+
+ Unable to read keyfile.
+ Impossible de lire le fichier de clé.
+
+
+ Not a KeePass database.
+ Ce n'est pas une base de donnée KeePass.
+
+
+ Unsupported encryption algorithm.
+ Algorithme de chiffrement non supporté.
+
+
+ Unsupported KeePass database version.
+ Version de base de donnée KeePass non supportée.
+
+
+ Root
+ Racine
+
+
+
+ KeePass2Reader
+
+ Not a KeePass database.
+ Ce n'est pas une base de donnée KeePass.
+
+
+ Unsupported KeePass database version.
+ Version de base de donnée KeePass non supportée.
+
+
+ Wrong key or database file is corrupt.
+ Mauvaise clé ou fichier de base de donnée corrompu.
+
+
+
+ Main
+
+ Fatal error while testing the cryptographic functions.
+ Erreur fatale lors des tests des fonctions cryptographiques.
+
+
+ KeePassX - Error
+ KeePassX - Erreur
+
+
+
+ MainWindow
+
+ Database
+ Base de données
+
+
+ Recent databases
+ Bases de données récentes
+
+
+ Help
+ Aide
+
+
+ Entries
+ Entrées
+
+
+ Copy attribute to clipboard
+ Copier l'attribut dans le presse-papiers
+
+
+ Groups
+ Groupes
+
+
+ Extras
+ Extras
+
+
+ View
+ Vue
+
+
+ Quit
+ Quitter
+
+
+ About
+ À propos
+
+
+ Open database
+ Ouvrir une base de donnée
+
+
+ Save database
+ Enregistrer la base de donnée
+
+
+ Close database
+ Fermer la base de donnée
+
+
+ New database
+ Nouvelle base de donnée
+
+
+ Add new entry
+ Ajouter une entrée
+
+
+ View/Edit entry
+ Voir/Modifier l'entrée
+
+
+ Delete entry
+ Supprimer l'entrée
+
+
+ Add new group
+ Ajouter un groupe
+
+
+ Edit group
+ Modifier le groupe
+
+
+ Delete group
+ Supprimer le groupe
+
+
+ Save database as
+ Enregistrer la base de donnée sous
+
+
+ Change master key
+ Modifier la clé maître
+
+
+ Database settings
+ Paramètre de la base de donnée
+
+
+ Import KeePass 1 database
+ Importer une base de donnée KeePass 1
+
+
+ Clone entry
+ Dupliquer l'entrée
+
+
+ Find
+ Chercher
+
+
+ Username
+ Nom d'utilisateur
+
+
+ Copy username to clipboard
+ Copier le nom d'utilisateur dans le presse-papiers
+
+
+ Password
+ Mot de passe
+
+
+ Copy password to clipboard
+ Copier le mot de passe dans le presse-papiers
+
+
+ Settings
+ Paramètres
+
+
+ Perform Auto-Type
+ Effectuer un Auto-Type
+
+
+ Open URL
+ Ouvrir l'URL
+
+
+ Lock databases
+ Verrouiller les bases de données
+
+
+ Title
+ Titre
+
+
+ URL
+ URL
+
+
+ Notes
+ Notes
+
+
+ Show toolbar
+ Afficher la barre d'outils
+
+
+ read-only
+ Lecture seulement
+
+
+ Toggle window
+ Basculer de fenêtre
+
+
+
+ PasswordGeneratorWidget
+
+ Password:
+ Mot de passe:
+
+
+ Length:
+ Longueur :
+
+
+ Character Types
+ Types de caractère
+
+
+ Upper Case Letters
+ Lettres majuscules
+
+
+ Lower Case Letters
+ Lettres minuscules
+
+
+ Numbers
+ Nombres
+
+
+ Special Characters
+ Caractères spéciaux
+
+
+ Exclude look-alike characters
+ Exclure les caractères se ressemblant
+
+
+ Ensure that the password contains characters from every group
+ S'assurer que le mot de passe possède un caractère de chaque groupe
+
+
+ Accept
+ Accepter
+
+
+
+ QCommandLineParser
+
+ Displays version information.
+ Afficher les informations de version
+
+
+ Displays this help.
+ Afficher cette aide.
+
+
+ Unknown option '%1'.
+ Option inconnue '%1'.
+
+
+ Unknown options: %1.
+ Options inconnues : %1.
+
+
+ Missing value after '%1'.
+ Valeur manquante après '%1'.
+
+
+ Unexpected value after '%1'.
+ Valeur inattendue après '%1'.
+
+
+ [options]
+ [options]
+
+
+ Usage: %1
+ Utilisation : %1
+
+
+ Options:
+ Options :
+
+
+ Arguments:
+ Arguments :
+
+
+
+ QSaveFile
+
+ Existing file %1 is not writable
+ Le fichier existant %1 n'est pas accessible en écriture
+
+
+ Writing canceled by application
+ Écriture annulée par l'application
+
+
+ Partial write. Partition full?
+ Écriture partielle. Partition pleine ?
+
+
+
+ QtIOCompressor
+
+ Internal zlib error when compressing:
+ Erreur interne zlib lors de la compression :
+
+
+ Error writing to underlying device:
+
+
+
+ Error opening underlying device:
+
+
+
+ Error reading data from underlying device:
+
+
+
+ Internal zlib error when decompressing:
+ Erreur interne zlib lors de la décompression :
+
+
+
+ QtIOCompressor::open
+
+ The gzip format not supported in this version of zlib.
+ Le format gzip n'est pas supporté dans cette version de zlib.
+
+
+ Internal zlib error:
+ Erreur interne zlib :
+
+
+
+ SearchWidget
+
+ Find:
+ Chercher :
+
+
+ Case sensitive
+ Sensible à la casse
+
+
+ Current group
+ Groupe actif
+
+
+ Root group
+ Groupe racine
+
+
+
+ SettingsWidget
+
+ Application Settings
+ Paramètre de l'application
+
+
+ General
+ Général
+
+
+ Security
+ Sécurité
+
+
+
+ SettingsWidgetGeneral
+
+ Remember last databases
+ Se souvenir des dernières bases de données
+
+
+ Open previous databases on startup
+ Ouvrir les base de données précédentes au démarrage
+
+
+ Mark as modified on expanded state changes
+
+
+
+ Automatically save on exit
+ Sauvegarde automatiquement à la sortie
+
+
+ Automatically save after every change
+ Sauvegarde automatiquement après chaque modification
+
+
+ Minimize when copying to clipboard
+
+
+
+ Use group icon on entry creation
+ Utiliser l'icône de groupe à la création d'une entrée
+
+
+ Global Auto-Type shortcut
+ Raccourci d'Auto-Type global
+
+
+ Use entry title to match windows for global auto-type
+ Utiliser la correspondance entre le titre de l'entrée et de la fenêtre pour l'Auto-Type global
+
+
+ Language
+ Langue
+
+
+ Show a system tray icon
+
+
+
+ Hide window to system tray when minimized
+
+
+
+
+ SettingsWidgetSecurity
+
+ Clear clipboard after
+ Vider le presse-papiers après
+
+
+ sec
+ s
+
+
+ Lock databases after inactivity of
+ Verrouiller les bases de donnée après une inactivité de
+
+
+ Show passwords in cleartext by default
+ Afficher les mots de passe en clair par défaut
+
+
+ Always ask before performing auto-type
+ Toujours demander avant d'effectuer un auto-type
+
+
+
+ UnlockDatabaseWidget
+
+ Unlock database
+ Déverrouiller la base de donnée
+
+
+ Error
+ Erreur
+
+
+ Wrong key.
+ Mauvaise clé.
+
+
+
+ WelcomeWidget
+
+ Welcome!
+ Bienvenue !
+
+
+
+ main
+
+ KeePassX - cross-platform password manager
+ KeePassX - Gestionnaire de mot de passe multi-plateforme
+
+
+ filename of the password database to open (*.kdbx)
+ Nom de fichier de la base de donnée de mot de pass à ouvrir (*.kdbx)
+
+
+ path to a custom config file
+ Chemin vers un fichier de configuration personnalisé
+
+
+ password of the database (DANGEROUS!)
+ Mot de passe de la base de donnée (DANGEREUX !)
+
+
+ key file of the database
+ Fichier de clé de la base de donnée
+
+
+
\ No newline at end of file
diff --git a/share/translations/keepassx_id.ts b/share/translations/keepassx_id.ts
new file mode 100644
index 00000000..d71e981e
--- /dev/null
+++ b/share/translations/keepassx_id.ts
@@ -0,0 +1,1205 @@
+
+
+ AboutDialog
+
+ About KeePassX
+ Tentang KeePassX
+
+
+ KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3.
+
+
+
+
+ AutoType
+
+ Auto-Type - KeePassX
+ Ketik-Otomatis - KeePassX
+
+
+ Couldn't find an entry that matches the window title:
+ Tidak dapat menemukan entri yang cocok dengan judul jendela
+
+
+
+ AutoTypeAssociationsModel
+
+ Window
+ Jendela
+
+
+ Sequence
+ Urutan
+
+
+ Default sequence
+ Urutan baku
+
+
+
+ AutoTypeSelectDialog
+
+ Auto-Type - KeePassX
+ Ketik-Otomatis - KeePassX
+
+
+ Select entry to Auto-Type:
+ Pilih entri untuk Ketik-Otomatis:
+
+
+
+ ChangeMasterKeyWidget
+
+ Password
+ Kata Sandi
+
+
+ Enter password:
+ Masukan kata sandi:
+
+
+ Repeat password:
+ Ulangi kata sandi:
+
+
+ Key file
+ Berkas kunci
+
+
+ Browse
+ Telusuri
+
+
+ Create
+ Buat
+
+
+ Key files
+ Berkas Kunci
+
+
+ All files
+ Semua berkas
+
+
+ Create Key File...
+ Buat Berkas Kunci...
+
+
+ Error
+ Galat
+
+
+ Unable to create Key File :
+ Tidak dapat membuat Berkas Kunci :
+
+
+ Select a key file
+ Pilih sebuah berkas kunci
+
+
+ Question
+ Pertanyaan
+
+
+ Do you really want to use an empty string as password?
+ Apakah anda ingin menggunakan string kosong sebagai kata sandi?
+
+
+ Different passwords supplied.
+ Kata sandi yang berbeda diberikan.
+
+
+
+ DatabaseOpenWidget
+
+ Enter master key
+ Masukan kunci utama
+
+
+ Key File:
+ Berkas Kunci:
+
+
+ Password:
+ Kata sandi:
+
+
+ Browse
+ Telusuri
+
+
+ Error
+ Galat
+
+
+ Unable to open the database.
+ Tidak dapat membuka basis data
+
+
+ Can't open key file
+ Tidak dapat membuka berkas kunci
+
+
+ All files
+ Semua berkas
+
+
+ Key files
+ Berkas kunci
+
+
+ Select key file
+ Pilih berkas kunci
+
+
+
+ DatabaseSettingsWidget
+
+ Database name:
+ Nama basis data:
+
+
+ Database description:
+ Deskripsi basis data:
+
+
+ Transform rounds:
+
+
+
+ Default username:
+ Nama pengguna baku:
+
+
+ Use recycle bin:
+
+
+
+ MiB
+ MiB
+
+
+ Benchmark
+
+
+
+ Max. history items:
+ Maks. item riwayat:
+
+
+ Max. history size:
+ Maks. ukuran riwayat:
+
+
+
+ DatabaseTabWidget
+
+ Root
+
+
+
+ KeePass 2 Database
+ Basis data KeePass 2
+
+
+ All files
+ Semua berkas
+
+
+ Open database
+ Buka basis data
+
+
+ Warning
+ Peringatan
+
+
+ File not found!
+ Berkas tidak ditemukan!
+
+
+ Open KeePass 1 database
+ Buka basis data KeePass 1
+
+
+ KeePass 1 database
+ Basis data KeePass 1
+
+
+ All files (*)
+ Semua berkas (*)
+
+
+ Close?
+ Tutup?
+
+
+ "%1" is in edit mode.
+Close anyway?
+ "%1" dalam berada mode sunting.
+Tetap tutup?
+
+
+ Save changes?
+ Simpan perubahan?
+
+
+ "%1" was modified.
+Save changes?
+ "%1" telah dimodifikasi.
+Simpan perubahan?
+
+
+ Error
+ Galat
+
+
+ Writing the database failed.
+ Menulis basis data gagal.
+
+
+ Save database as
+ Simpan basis data sebagai
+
+
+ New database
+ Basis data baru
+
+
+ locked
+ terkunci
+
+
+
+ DatabaseWidget
+
+ Change master key
+ Ubah kunci utama
+
+
+ Delete entry?
+ Hapus entri?
+
+
+ Do you really want to delete the entry "%1" for good?
+ Apakah anda ingin menghapus entri "%1" untuk selamanya?
+
+
+ Delete entries?
+ Hapus entri?
+
+
+ Do you really want to delete %1 entries for good?
+ Apakah anda ingin menghapus entri %1 untuk selamanya?
+
+
+ Move entries to recycle bin?
+
+
+
+ Do you really want to move %n entry(s) to the recycle bin?
+
+
+
+ Delete group?
+ Hapus grup?
+
+
+ Do you really want to delete the group "%1" for good?
+ Apakah anda ingin menghapus grup "%1" untuk selamanya?
+
+
+ Current group
+ Grup saat ini
+
+
+
+ EditEntryWidget
+
+ Entry
+ Entri
+
+
+ Advanced
+ Tingkat Lanjut
+
+
+ Icon
+ Ikon
+
+
+ Auto-Type
+ Ketik-Otomatis
+
+
+ Properties
+ Properti
+
+
+ History
+ Riwayat
+
+
+ Entry history
+ Entri riwayat
+
+
+ Add entry
+ Tambah entri
+
+
+ Edit entry
+ Sunting entri
+
+
+ Error
+ Galat
+
+
+ Different passwords supplied.
+ Kata sandi yang berbeda diberikan.
+
+
+ New attribute
+ Atribut baru
+
+
+ Select file
+ Pilih berkas
+
+
+ Unable to open file
+ Tidak dapat membuka berkas
+
+
+ Save attachment
+ Simpan lampiran
+
+
+ Unable to save the attachment:
+
+ Tidak dapat menyimpan lampiran:
+
+
+
+ Tomorrow
+ Besok
+
+
+ %n week(s)
+ %n minggu(s)
+
+
+ %n month(s)
+ %n bulan(s)
+
+
+ 1 year
+ 1 tahun
+
+
+
+ EditEntryWidgetAdvanced
+
+ Additional attributes
+ Atribut tambahan
+
+
+ Add
+ Tambah
+
+
+ Edit
+ Sunting
+
+
+ Remove
+ Hapus
+
+
+ Attachments
+ Lampiran
+
+
+ Save
+ Simpan
+
+
+
+ EditEntryWidgetAutoType
+
+ Enable Auto-Type for this entry
+ Aktifkan Ketik-Otomatis untuk entri ini
+
+
+ Inherit default Auto-Type sequence from the group
+
+
+
+ Use custom Auto-Type sequence:
+
+
+
+ +
+ +
+
+
+ -
+ -
+
+
+ Window title:
+ Judul jendela:
+
+
+ Use default sequence
+ Gunakan urutan baku
+
+
+ Set custom sequence:
+ Tetapkan urutan kustom:
+
+
+
+ EditEntryWidgetHistory
+
+ Show
+ Tampilkan
+
+
+ Restore
+ Kembalikan
+
+
+ Delete
+ Hapus
+
+
+ Delete all
+ Hapus semua
+
+
+
+ EditEntryWidgetMain
+
+ Title:
+ Judul:
+
+
+ Username:
+ Nama pengguna:
+
+
+ Password:
+ Kata sandi:
+
+
+ Repeat:
+ Ulangi:
+
+
+ Gen.
+ Gen.
+
+
+ URL:
+ URL:
+
+
+ Expires
+ Kadaluarsa
+
+
+ Presets
+
+
+
+ Notes:
+ Catatan:
+
+
+
+ EditGroupWidget
+
+ Group
+ Grup
+
+
+ Icon
+ Ikon
+
+
+ Properties
+ Properti
+
+
+ Add group
+ Tambah grup
+
+
+ Edit group
+ Sunting grup
+
+
+ Enable
+ Aktifkan
+
+
+ Disable
+ Nonaktifkan
+
+
+ Inherit from parent group (%1)
+
+
+
+
+ EditGroupWidgetMain
+
+ Name
+ Nama
+
+
+ Notes
+ Catatan
+
+
+ Expires
+ Kadaluarsa
+
+
+ Search
+ Cari
+
+
+ Auto-type
+ Ketik-otomatis
+
+
+
+ EditWidgetIcons
+
+ Use default icon
+ Gunakan ikon baku
+
+
+ Use custom icon
+ Gunakan ikon kustom
+
+
+ Add custom icon
+ Tambah ikon kustom
+
+
+ Delete custom icon
+ Hapus ikon kustom
+
+
+ Images
+ Gambar
+
+
+ All files
+ Semua berkas
+
+
+ Select Image
+ Pilih gambar
+
+
+ Can't delete icon!
+ Tidak dapat menghapus ikon!
+
+
+ Can't delete icon. Still used by %n item(s).
+ Can't delete icon. Still used by %n item(s).
+
+
+
+ EditWidgetProperties
+
+ Created:
+ Dibuat:
+
+
+ Modified:
+ Dimodifikasi:
+
+
+ Accessed:
+ Diakses:
+
+
+ Uuid:
+ Uuid:
+
+
+
+ EntryAttributesModel
+
+ Name
+ Nama
+
+
+
+ EntryHistoryModel
+
+ Last modified
+ Terakhir dimodifikasi
+
+
+ Title
+ Judul
+
+
+ Username
+ Nama pengguna
+
+
+ URL
+ URL
+
+
+
+ EntryModel
+
+ Group
+ Grup
+
+
+ Title
+ Judul
+
+
+ Username
+ Nama pengguna
+
+
+ URL
+ URL
+
+
+
+ Group
+
+ Recycle Bin
+
+
+
+
+ KeePass1OpenWidget
+
+ Import KeePass1 database
+ Impor basis data KeePass1
+
+
+ Error
+ Galat
+
+
+ Unable to open the database.
+ Tidak dapat membuka basis data
+
+
+
+ KeePass1Reader
+
+ Unable to read keyfile.
+ Tidak dapat membaca berkas kunci.
+
+
+ Not a KeePass database.
+ Bukan basis data KeePass
+
+
+ Unsupported encryption algorithm.
+ Algoritma enkripsi tidak didukung
+
+
+ Unsupported KeePass database version.
+ Versi Basis data KeePass tidak didukung
+
+
+ Root
+
+
+
+
+ KeePass2Reader
+
+ Not a KeePass database.
+ Bukan basis data KeePass
+
+
+ Unsupported KeePass database version.
+ Versi basis data KeePass tidak didukung
+
+
+ Wrong key or database file is corrupt.
+ Kunci salah atau berkas basis data korup.
+
+
+
+ Main
+
+ Fatal error while testing the cryptographic functions.
+
+
+
+ KeePassX - Error
+ KeePassX - Galat
+
+
+
+ MainWindow
+
+ Database
+ Basis data
+
+
+ Recent databases
+
+
+
+ Help
+ Bantuan
+
+
+ Entries
+ Entri
+
+
+ Copy attribute to clipboard
+ Salin atribut ke papan klip
+
+
+ Groups
+ Grup
+
+
+ Extras
+ Ekstra
+
+
+ View
+
+
+
+ Quit
+ Keluar
+
+
+ About
+ Tentang
+
+
+ Open database
+ Buka basis data
+
+
+ Save database
+ Simpan basis data
+
+
+ Close database
+ Tutup basis data
+
+
+ New database
+ Basis data baru
+
+
+ Add new entry
+ Tambah entri baru
+
+
+ View/Edit entry
+ Tampil/Sunting entri
+
+
+ Delete entry
+ Hapus entri
+
+
+ Add new group
+ Tambah grup baru
+
+
+ Edit group
+ Sunting grup
+
+
+ Delete group
+ Hapus grup
+
+
+ Save database as
+ Simpan basis data sebagai
+
+
+ Change master key
+ Ubah kunci utama
+
+
+ Database settings
+ Pengaturan basis data
+
+
+ Import KeePass 1 database
+ Impor basis data KeePass 1
+
+
+ Clone entry
+
+
+
+ Find
+ Temukan
+
+
+ Username
+ Nama pengguna
+
+
+ Copy username to clipboard
+ Salin nama pengguna ke papan klip
+
+
+ Password
+ Kata sandi
+
+
+ Copy password to clipboard
+ Salin kata sandi ke papan klip
+
+
+ Settings
+ Pengaturan
+
+
+ Perform Auto-Type
+ Melakukan Ketik-Otomatis
+
+
+ Open URL
+ Buka URL
+
+
+ Lock databases
+ Kunci basis data
+
+
+ Title
+ Judul
+
+
+ URL
+ URL
+
+
+ Notes
+ Catatan
+
+
+ Show toolbar
+ Tampilkan bilah alat
+
+
+ read-only
+ hanya-baca
+
+
+ Toggle window
+
+
+
+
+ PasswordGeneratorWidget
+
+ Password:
+ Kata sandi:
+
+
+ Length:
+ Panjang:
+
+
+ Character Types
+ Tipe karakter
+
+
+ Upper Case Letters
+
+
+
+ Lower Case Letters
+
+
+
+ Numbers
+ Angka
+
+
+ Special Characters
+ Karakter Spesial
+
+
+ Exclude look-alike characters
+
+
+
+ Ensure that the password contains characters from every group
+ Pastikan kata sandi berisi karakter dari setiap grup
+
+
+ Accept
+ Terima
+
+
+
+ QCommandLineParser
+
+ Displays version information.
+ Tampilkan informasi versi
+
+
+ Displays this help.
+ Tampilkan bantuan ini
+
+
+ Unknown option '%1'.
+ Opsi tidak diketahui '%1'.
+
+
+ Unknown options: %1.
+ Opsi tidak diketahui: %1.
+
+
+ Missing value after '%1'.
+ Nilai hilang setelah '%1'.
+
+
+ Unexpected value after '%1'.
+ Nilai tidak terduga setelah '%1'.
+
+
+ [options]
+ [opsi]
+
+
+ Usage: %1
+ Penggunaan: %1
+
+
+ Options:
+ Opsi:
+
+
+ Arguments:
+ Argumen
+
+
+
+ QSaveFile
+
+ Existing file %1 is not writable
+ Berkas yang ada %1 tidak dapat ditulis
+
+
+ Writing canceled by application
+ Menulis dibatalkan oleh aplikasi
+
+
+ Partial write. Partition full?
+
+
+
+
+ QtIOCompressor
+
+ Internal zlib error when compressing:
+ Galat zlib internal ketika mengkompress:
+
+
+ Error writing to underlying device:
+
+
+
+ Error opening underlying device:
+
+
+
+ Error reading data from underlying device:
+
+
+
+ Internal zlib error when decompressing:
+ Galat zlib internal ketika dekompress
+
+
+
+ QtIOCompressor::open
+
+ The gzip format not supported in this version of zlib.
+ Format gzip tidak didukung pada versi zlib ini.
+
+
+ Internal zlib error:
+ Galat zlib internal:
+
+
+
+ SearchWidget
+
+ Find:
+ Temukan:
+
+
+ Case sensitive
+
+
+
+ Current group
+
+
+
+ Root group
+
+
+
+
+ SettingsWidget
+
+ Application Settings
+ Pengaturan Aplikasi
+
+
+ General
+ Umum
+
+
+ Security
+ Keamanan
+
+
+
+ SettingsWidgetGeneral
+
+ Remember last databases
+ Ingat basis data terakhir
+
+
+ Open previous databases on startup
+ Buka basis data sebelumnya saat mulai
+
+
+ Mark as modified on expanded state changes
+
+
+
+ Automatically save on exit
+ Otomatis simpan ketika keluar
+
+
+ Automatically save after every change
+ Otomatis simpan setelah setiap perubahan
+
+
+ Minimize when copying to clipboard
+ Kecilkan ketika menyalin ke papan klip
+
+
+ Use group icon on entry creation
+ Gunakan ikon grup pada pembuatan entri
+
+
+ Global Auto-Type shortcut
+ Jalan pintas global Ketik-Otomatis
+
+
+ Use entry title to match windows for global auto-type
+
+
+
+ Language
+ Bahasa
+
+
+ Show a system tray icon
+ Tampilkan sebuah ikon baki sistem
+
+
+ Hide window to system tray when minimized
+ Sembunyikan jendela ke baki sistem ketika dikecilkan
+
+
+
+ SettingsWidgetSecurity
+
+ Clear clipboard after
+ Bersihkan papan klip setelaj
+
+
+ sec
+ det
+
+
+ Lock databases after inactivity of
+
+
+
+ Show passwords in cleartext by default
+
+
+
+ Always ask before performing auto-type
+
+
+
+
+ UnlockDatabaseWidget
+
+ Unlock database
+
+
+
+ Error
+ Galat
+
+
+ Wrong key.
+ Kunci salah.
+
+
+
+ WelcomeWidget
+
+ Welcome!
+ Selamat Datang.
+
+
+
+ main
+
+ KeePassX - cross-platform password manager
+ KeePassX - manajer kata sandi cross-platform
+
+
+ filename of the password database to open (*.kdbx)
+ nama berkasi dari basis data kata sandi untuk dibuka (*.kdbx)
+
+
+ path to a custom config file
+
+
+
+ password of the database (DANGEROUS!)
+ kata sandi dari basis data (BERBAHAYA!)
+
+
+ key file of the database
+ berkas kunci dari basis data
+
+
+
\ No newline at end of file
diff --git a/share/translations/keepassx_nl_NL.ts b/share/translations/keepassx_nl_NL.ts
index aa6320ee..da376921 100644
--- a/share/translations/keepassx_nl_NL.ts
+++ b/share/translations/keepassx_nl_NL.ts
@@ -17,8 +17,8 @@
Auto-typen - KeePassX
- Couldn't find an entry that matches the window title.
- Kon geen element vinden dat overeenkomt met de venstertitel.
+ Couldn't find an entry that matches the window title:
+ Kon geen element vinden dat overeenkomt met de venstertitel:
@@ -740,6 +740,17 @@ Opslaan?
Verkeerde sleutel of corrupte database.
+
+ Main
+
+ Fatal error while testing the cryptographic functions.
+ Fatale fout bij het testen van de cryptografische functies.
+
+
+ KeePassX - Error
+ KeePassX - Fout
+
+
MainWindow
@@ -898,6 +909,10 @@ Opslaan?
read-only
alleen-lezen
+
+ Toggle window
+ Wissel venster
+
PasswordGeneratorWidget
@@ -1106,6 +1121,18 @@ Opslaan?
Use entry title to match windows for global auto-type
Gebruik naam van element als vensternaam voor auto-typen
+
+ Language
+ Taal
+
+
+ Show a system tray icon
+ Toon een icoon in de systray
+
+
+ Hide window to system tray when minimized
+ Bij minimaliseren enkel icoon in systray tonen
+
SettingsWidgetSecurity
diff --git a/share/translations/keepassx_ru.ts b/share/translations/keepassx_ru.ts
new file mode 100644
index 00000000..6b8092ef
--- /dev/null
+++ b/share/translations/keepassx_ru.ts
@@ -0,0 +1,1205 @@
+
+
+ AboutDialog
+
+ About KeePassX
+ О KeePassX
+
+
+ KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3.
+ KeePassX распространяется на условиях Стандартной общественной лицензии GNU (GPL) версии 2 или (на ваше усмотрение) версии 3.
+
+
+
+ AutoType
+
+ Auto-Type - KeePassX
+ Автоввод — KeePassX
+
+
+ Couldn't find an entry that matches the window title:
+ Невозможно найти запись, соответствующую заголовку окна:
+
+
+
+ AutoTypeAssociationsModel
+
+ Window
+ Окно
+
+
+ Sequence
+ Последовательность
+
+
+ Default sequence
+ Стандартная последовательность
+
+
+
+ AutoTypeSelectDialog
+
+ Auto-Type - KeePassX
+ Автоввод — KeePassX
+
+
+ Select entry to Auto-Type:
+ Выберете запись для автоввода:
+
+
+
+ ChangeMasterKeyWidget
+
+ Password
+ Пароль
+
+
+ Enter password:
+ Введите пароль:
+
+
+ Repeat password:
+ Повторите пароль:
+
+
+ Key file
+ Файл-ключ
+
+
+ Browse
+ Обзор
+
+
+ Create
+ Создать
+
+
+ Key files
+ Файлы-ключи
+
+
+ All files
+ Все файлы
+
+
+ Create Key File...
+ Создать файл-ключ...
+
+
+ Error
+ Ошибка
+
+
+ Unable to create Key File :
+ Невозможно создать файл-ключ:
+
+
+ Select a key file
+ Выбрать файл-ключ
+
+
+ Question
+ Вопрос
+
+
+ Do you really want to use an empty string as password?
+ Вы действительно хотите использовать пустую строку как пароль?
+
+
+ Different passwords supplied.
+ Пароли не совпадают.
+
+
+
+ DatabaseOpenWidget
+
+ Enter master key
+ Введите мастер-пароль
+
+
+ Key File:
+ Файл-ключ:
+
+
+ Password:
+ Пароль:
+
+
+ Browse
+ Обзор
+
+
+ Error
+ Ошибка
+
+
+ Unable to open the database.
+ Невозможно открыть хранилище.
+
+
+ Can't open key file
+ Не могу открыть файл-ключ
+
+
+ All files
+ Все файлы
+
+
+ Key files
+ Файлы-ключи
+
+
+ Select key file
+ Выберете файл-ключ
+
+
+
+ DatabaseSettingsWidget
+
+ Database name:
+ Имя хранилища:
+
+
+ Database description:
+ Описание хранилища:
+
+
+ Transform rounds:
+ Раундов преобразований:
+
+
+ Default username:
+ Имя пользователя по-умолчанию:
+
+
+ Use recycle bin:
+ Использовать корзину:
+
+
+ MiB
+ МиБ
+
+
+ Benchmark
+ Проверка
+
+
+ Max. history items:
+ Максимум записей истории:
+
+
+ Max. history size:
+ Максимальный размер истории:
+
+
+
+ DatabaseTabWidget
+
+ Root
+ Корень
+
+
+ KeePass 2 Database
+ Хранилище KeePass 2
+
+
+ All files
+ Все файлы
+
+
+ Open database
+ Открыть хранилище
+
+
+ Warning
+ Внимание
+
+
+ File not found!
+ Файл не найден!
+
+
+ Open KeePass 1 database
+ Открыть хранилище KeePass 1
+
+
+ KeePass 1 database
+ Хранилище KeePass 1
+
+
+ All files (*)
+ Все файлы (*)
+
+
+ Close?
+ Закрыть?
+
+
+ "%1" is in edit mode.
+Close anyway?
+ «%1» в режиме редактирования.
+Всё равно закрыть?
+
+
+ Save changes?
+ Сохранить изменения?
+
+
+ "%1" was modified.
+Save changes?
+ «%1» изменён.
+Сохранить изменения?
+
+
+ Error
+ Ошибка
+
+
+ Writing the database failed.
+ Не удалось записать хранилище.
+
+
+ Save database as
+ Сохранить хранилище как
+
+
+ New database
+ Новое хранилище
+
+
+ locked
+ заблокированно
+
+
+
+ DatabaseWidget
+
+ Change master key
+ Сменить мастер-пароль
+
+
+ Delete entry?
+ Удалить запись?
+
+
+ Do you really want to delete the entry "%1" for good?
+ Вы действительно хотите навсегда удалить запись «%1»?
+
+
+ Delete entries?
+ Удалить записи?
+
+
+ Do you really want to delete %1 entries for good?
+ Вы действительно хотите навсегда удалить %1 записей?
+
+
+ Move entries to recycle bin?
+ Поместить записи в корзину?
+
+
+ Do you really want to move %n entry(s) to the recycle bin?
+ Вы действительно хотите поместить %n запись в корзину?Вы действительно хотите поместить %n записи в корзину?Вы действительно хотите поместить %n записей в корзину?Вы действительно хотите поместить %n записей в корзину?
+
+
+ Delete group?
+ Удалить группу?
+
+
+ Do you really want to delete the group "%1" for good?
+ Вы действительно хотите навсегда удалить группу «%1»?
+
+
+ Current group
+ Текущая группа
+
+
+
+ EditEntryWidget
+
+ Entry
+ Запись
+
+
+ Advanced
+ Расширенные
+
+
+ Icon
+ Иконка
+
+
+ Auto-Type
+ Автоввод
+
+
+ Properties
+ Параметры
+
+
+ History
+ История
+
+
+ Entry history
+ История записи
+
+
+ Add entry
+ Добавить запись
+
+
+ Edit entry
+ Редактировать запись
+
+
+ Error
+ Ошибка
+
+
+ Different passwords supplied.
+ Пароли не совпадают.
+
+
+ New attribute
+ Новый атрибут
+
+
+ Select file
+ Выбрать файл
+
+
+ Unable to open file
+ Невозможно открыть файл
+
+
+ Save attachment
+ Сохранить вложение
+
+
+ Unable to save the attachment:
+
+ Невозможно сохранить вложение:
+
+
+
+ Tomorrow
+ Завтра
+
+
+ %n week(s)
+ %n неделя%n недели%n недель%n недель
+
+
+ %n month(s)
+ %n месяц%n месяца%n месяцев%n месяцев
+
+
+ 1 year
+ 1 год
+
+
+
+ EditEntryWidgetAdvanced
+
+ Additional attributes
+ Дополнительные атрибуты
+
+
+ Add
+ Добавить
+
+
+ Edit
+ Изменить
+
+
+ Remove
+ Удалить
+
+
+ Attachments
+ Вложения
+
+
+ Save
+ Сохранить
+
+
+
+ EditEntryWidgetAutoType
+
+ Enable Auto-Type for this entry
+ Включить автоввод для этой записи
+
+
+ Inherit default Auto-Type sequence from the group
+ Унаследовать стандартную последовательность автоввода от группы
+
+
+ Use custom Auto-Type sequence:
+ Использовать свою последовательность автоввода:
+
+
+ +
+ +
+
+
+ -
+ -
+
+
+ Window title:
+ Заголовок окна:
+
+
+ Use default sequence
+ Использовать стандартную последовательность
+
+
+ Set custom sequence:
+ Установить свою последовательность:
+
+
+
+ EditEntryWidgetHistory
+
+ Show
+ Показать
+
+
+ Restore
+ Восстановить
+
+
+ Delete
+ Удалить
+
+
+ Delete all
+ Удалить всё
+
+
+
+ EditEntryWidgetMain
+
+ Title:
+ Заголовок:
+
+
+ Username:
+ Имя пользователя:
+
+
+ Password:
+ Пароль:
+
+
+ Repeat:
+ Пароль ещё раз:
+
+
+ Gen.
+
+
+
+ URL:
+ URL:
+
+
+ Expires
+ Истекает
+
+
+ Presets
+ Предустановки
+
+
+ Notes:
+ Примечания:
+
+
+
+ EditGroupWidget
+
+ Group
+ Группа
+
+
+ Icon
+ Иконка
+
+
+ Properties
+ Параметры
+
+
+ Add group
+ Добавить группу
+
+
+ Edit group
+ Редактировать группу
+
+
+ Enable
+ Включено
+
+
+ Disable
+ Выключено
+
+
+ Inherit from parent group (%1)
+ Наследовать у родительской группы (%1)
+
+
+
+ EditGroupWidgetMain
+
+ Name
+ Имя
+
+
+ Notes
+ Примечания
+
+
+ Expires
+ Истекает
+
+
+ Search
+ Поиск
+
+
+ Auto-type
+ автоввод
+
+
+
+ EditWidgetIcons
+
+ Use default icon
+ Использовать стандартную иконку
+
+
+ Use custom icon
+ Использовать свою иконку
+
+
+ Add custom icon
+ Добавить свою иконку
+
+
+ Delete custom icon
+ Удалить свою иконку
+
+
+ Images
+ Изображения
+
+
+ All files
+ Все файлы
+
+
+ Select Image
+ Выбор изображения
+
+
+ Can't delete icon!
+ Не могу удалить иконку!
+
+
+ Can't delete icon. Still used by %n item(s).
+ Не могу удалить иконку, %n запись всё ещё использует её.Не могу удалить иконку, %n записи всё ещё использует её.Не могу удалить иконку, %n записей всё ещё использует её.Не могу удалить иконку, %n записей всё ещё использует её.
+
+
+
+ EditWidgetProperties
+
+ Created:
+ Создано:
+
+
+ Modified:
+ Изменено:
+
+
+ Accessed:
+ Доступ:
+
+
+ Uuid:
+ Uuid:
+
+
+
+ EntryAttributesModel
+
+ Name
+ Имя
+
+
+
+ EntryHistoryModel
+
+ Last modified
+ Последнее изменение
+
+
+ Title
+ Заголовок
+
+
+ Username
+ Имя пользователя
+
+
+ URL
+ URL
+
+
+
+ EntryModel
+
+ Group
+ Группа
+
+
+ Title
+ Заголовок
+
+
+ Username
+ Имя пользователя
+
+
+ URL
+ URL
+
+
+
+ Group
+
+ Recycle Bin
+ Корзина
+
+
+
+ KeePass1OpenWidget
+
+ Import KeePass1 database
+ Импортировать хранилище KeePass 1
+
+
+ Error
+ Ошибка
+
+
+ Unable to open the database.
+ Невозможно открыть хранилище.
+
+
+
+ KeePass1Reader
+
+ Unable to read keyfile.
+ Невозможно прочесть файл-ключ.
+
+
+ Not a KeePass database.
+ Не хранилище KeePass.
+
+
+ Unsupported encryption algorithm.
+ Алгоритм шифрования не поддерживается.
+
+
+ Unsupported KeePass database version.
+ Версия хранилища KeePass не поддерживается.
+
+
+ Root
+ Корень
+
+
+
+ KeePass2Reader
+
+ Not a KeePass database.
+ Не хранилище KeePass.
+
+
+ Unsupported KeePass database version.
+ Версия хранилища KeePass не поддерживается.
+
+
+ Wrong key or database file is corrupt.
+ Неверный ключ или файл хранилища повреждён.
+
+
+
+ Main
+
+ Fatal error while testing the cryptographic functions.
+ Неисправимая ошибка в процессе тестирования криптографических функций.
+
+
+ KeePassX - Error
+ KeePassX — Ошибка
+
+
+
+ MainWindow
+
+ Database
+ Хранилище
+
+
+ Recent databases
+ Недавние хранилища
+
+
+ Help
+ Помощь
+
+
+ Entries
+ Записи
+
+
+ Copy attribute to clipboard
+ Скопировать атрибут в буфер обмена
+
+
+ Groups
+ Группы
+
+
+ Extras
+ Дополнительно
+
+
+ View
+ Вид
+
+
+ Quit
+ Выход
+
+
+ About
+ О программе
+
+
+ Open database
+ Открыть хранилище
+
+
+ Save database
+ Сохранить хранилище
+
+
+ Close database
+ Закрыть хранилище
+
+
+ New database
+ Новое хранилище
+
+
+ Add new entry
+ Добавить новую запись
+
+
+ View/Edit entry
+ Посмотреть/редактировать запись
+
+
+ Delete entry
+ Удалить запись
+
+
+ Add new group
+ Добавить новую группу
+
+
+ Edit group
+ Редактировать группу
+
+
+ Delete group
+ Удалить группу
+
+
+ Save database as
+ Сохранить хранилище как
+
+
+ Change master key
+ Сменить мастер-пароль
+
+
+ Database settings
+ Параметры хранилища
+
+
+ Import KeePass 1 database
+ Импортировать хранилище KeePass 1
+
+
+ Clone entry
+ Клонировать запись
+
+
+ Find
+ Найти
+
+
+ Username
+ Имя пользователя
+
+
+ Copy username to clipboard
+ Скопировать имя пользователя в буфер обмена
+
+
+ Password
+ Пароль
+
+
+ Copy password to clipboard
+ Скопировать пароль в буфер обмена
+
+
+ Settings
+ Настройки
+
+
+ Perform Auto-Type
+ Произвести автоввод
+
+
+ Open URL
+ Открыть URL
+
+
+ Lock databases
+ Заблокировать хранилище
+
+
+ Title
+ Заголовок
+
+
+ URL
+ URL
+
+
+ Notes
+ Примечания
+
+
+ Show toolbar
+ Показать панель инструментов
+
+
+ read-only
+ только для чтения
+
+
+ Toggle window
+ Переключить окно
+
+
+
+ PasswordGeneratorWidget
+
+ Password:
+ Пароль:
+
+
+ Length:
+ Длина:
+
+
+ Character Types
+ Виды символов
+
+
+ Upper Case Letters
+ Заглавные буквы
+
+
+ Lower Case Letters
+ Строчные буквы
+
+
+ Numbers
+ Цифры
+
+
+ Special Characters
+ Особые символы
+
+
+ Exclude look-alike characters
+ Исключить похожие символы
+
+
+ Ensure that the password contains characters from every group
+ Пожалуйста, пусть пароль будет содержать символы всех видов
+
+
+ Accept
+ Принять
+
+
+
+ QCommandLineParser
+
+ Displays version information.
+ Показывает информацию о версии.
+
+
+ Displays this help.
+ Показывает эту справку.
+
+
+ Unknown option '%1'.
+ Неизвестная опция «%1».
+
+
+ Unknown options: %1.
+ Неизвестные опции %1.
+
+
+ Missing value after '%1'.
+ Пропущено значение после «%1».
+
+
+ Unexpected value after '%1'.
+ Непредвиденное значение после «%1».
+
+
+ [options]
+ [опции]
+
+
+ Usage: %1
+ Использование: %1
+
+
+ Options:
+ Опции:
+
+
+ Arguments:
+ Аргументы:
+
+
+
+ QSaveFile
+
+ Existing file %1 is not writable
+ Существующий файл %1 непригоден для записи
+
+
+ Writing canceled by application
+ Запись отменена приложением
+
+
+ Partial write. Partition full?
+ Частичная запись. Раздел переполнен?
+
+
+
+ QtIOCompressor
+
+ Internal zlib error when compressing:
+ Внутренняя ошибка zlib при сжатии:
+
+
+ Error writing to underlying device:
+ Ошибка записи на нижлежащее устройство:
+
+
+ Error opening underlying device:
+ Ошибка открытия нижлежащего устройства:
+
+
+ Error reading data from underlying device:
+ Ошибка чтения с нижлежащего устройства:
+
+
+ Internal zlib error when decompressing:
+ Внутренняя ошибка zlib при расжатии:
+
+
+
+ QtIOCompressor::open
+
+ The gzip format not supported in this version of zlib.
+ Формат gzip не поддерживается в этой версии zlib.
+
+
+ Internal zlib error:
+ Внутренняя ошибка zlib:
+
+
+
+ SearchWidget
+
+ Find:
+ Найти:
+
+
+ Case sensitive
+ Чувствительно к регистру
+
+
+ Current group
+ Текущая группа
+
+
+ Root group
+ Корневая группа
+
+
+
+ SettingsWidget
+
+ Application Settings
+ Параметры приложения
+
+
+ General
+ Общие
+
+
+ Security
+ Безопасность
+
+
+
+ SettingsWidgetGeneral
+
+ Remember last databases
+ Помнить последнее хранилище
+
+
+ Open previous databases on startup
+ Открывать предыдущее хранилище при запуске
+
+
+ Mark as modified on expanded state changes
+
+
+
+ Automatically save on exit
+ Автоматически сохранять при выходе
+
+
+ Automatically save after every change
+ Автоматически сохранять после каждого изменения
+
+
+ Minimize when copying to clipboard
+ Сворачивать при копировании в буфер обмена
+
+
+ Use group icon on entry creation
+ Использовать значок группы для новых записей
+
+
+ Global Auto-Type shortcut
+ Глобальное сочетание клавиш для автоввода
+
+
+ Use entry title to match windows for global auto-type
+ Использовать заголовок записи для подбора окон для глобального автоввода
+
+
+ Language
+ Язык
+
+
+ Show a system tray icon
+ Показывать иконку в трее
+
+
+ Hide window to system tray when minimized
+ При сворачивании прятать окно в область системных уведомлений
+
+
+
+ SettingsWidgetSecurity
+
+ Clear clipboard after
+ Потом почтистить буфер обмена
+
+
+ sec
+ сек
+
+
+ Lock databases after inactivity of
+ Заблокировать хранилище после неактивности длительностью
+
+
+ Show passwords in cleartext by default
+ Показывать пароль в открытую по-умолчанию
+
+
+ Always ask before performing auto-type
+ Всегда спрашивать перед тем, как производить автоввод
+
+
+
+ UnlockDatabaseWidget
+
+ Unlock database
+ Разблокировать хранилище
+
+
+ Error
+ Ошибка
+
+
+ Wrong key.
+ Неверный ключ.
+
+
+
+ WelcomeWidget
+
+ Welcome!
+ Добро пожаловать!
+
+
+
+ main
+
+ KeePassX - cross-platform password manager
+ KeePassX — кросс-платформенный менеджер паролей
+
+
+ filename of the password database to open (*.kdbx)
+ имя файла открываемого хранилища паролей (*.kdbx)
+
+
+ path to a custom config file
+ путь к своему файлу настроек
+
+
+ password of the database (DANGEROUS!)
+ пароль от хранилища (ОПАСНО!)
+
+
+ key file of the database
+ файл-ключ хранилища
+
+
+
\ No newline at end of file
diff --git a/share/translations/keepassx_sv.ts b/share/translations/keepassx_sv.ts
index 2a3ba790..c18a3102 100644
--- a/share/translations/keepassx_sv.ts
+++ b/share/translations/keepassx_sv.ts
@@ -17,8 +17,8 @@
Auto-skriv - KeePassX
- Couldn't find an entry that matches the window title.
- Kunde inte hitta en post som matchar fönstertiteln.
+ Couldn't find an entry that matches the window title:
+ Kunde inte hitta en post som matchar fönstertiteln:
@@ -740,6 +740,17 @@ Spara ändringarna?
Fel lösenord eller korrupt databas-fil
+
+ Main
+
+ Fatal error while testing the cryptographic functions.
+ Allvarligt fel vid testning av kryptografiska funktioner.
+
+
+ KeePassX - Error
+ KeePassX - Fel
+
+
MainWindow
@@ -898,6 +909,10 @@ Spara ändringarna?
read-only
läs bara
+
+ Toggle window
+ Visa/dölj fönster
+
PasswordGeneratorWidget
@@ -1106,6 +1121,18 @@ Spara ändringarna?
Use entry title to match windows for global auto-type
Använda postens titel till matchning med fönster för globalt auto-skriv
+
+ Language
+ Språk
+
+
+ Show a system tray icon
+ Visa statusfält ikon
+
+
+ Hide window to system tray when minimized
+ Vid minimering, minimera fönstret till systemfältet
+
SettingsWidgetSecurity
diff --git a/share/translations/keepassx_zh_TW.ts b/share/translations/keepassx_zh_TW.ts
new file mode 100644
index 00000000..76307744
--- /dev/null
+++ b/share/translations/keepassx_zh_TW.ts
@@ -0,0 +1,1203 @@
+
+
+ AboutDialog
+
+ About KeePassX
+ 關於 KeePassX
+
+
+ KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3.
+ KeePassX 是使用第 2 版 GNU 通用公共授權條款所發佈的 (或者,可根據你的選擇選用第 3 版)
+
+
+
+ AutoType
+
+ Auto-Type - KeePassX
+ KeePassX - 自動輸入
+
+
+ Couldn't find an entry that matches the window title:
+ 無法找到符合視窗標題的項目
+
+
+
+ AutoTypeAssociationsModel
+
+ Window
+ 視窗
+
+
+ Sequence
+ 序列
+
+
+ Default sequence
+ 預設的序列
+
+
+
+ AutoTypeSelectDialog
+
+ Auto-Type - KeePassX
+ KeePassX - 自動輸入
+
+
+ Select entry to Auto-Type:
+ 選擇自動輸入的項目
+
+
+
+ ChangeMasterKeyWidget
+
+ Password
+ 密碼
+
+
+ Enter password:
+ 輸入密碼
+
+
+ Repeat password:
+ 再次輸入密碼
+
+
+ Key file
+ 金鑰檔案
+
+
+ Browse
+ 瀏覽
+
+
+ Create
+ 建立
+
+
+ Key files
+ 金鑰檔案
+
+
+ All files
+ 所有的檔案
+
+
+ Create Key File...
+ 建立一個金鑰檔案
+
+
+ Error
+ 錯誤
+
+
+ Unable to create Key File :
+ 無法建立金鑰檔案:
+
+
+ Select a key file
+ 選擇一個金鑰檔案
+
+
+ Question
+ 問題
+
+
+ Do you really want to use an empty string as password?
+ 你真的想使用空白密碼嗎?
+
+
+ Different passwords supplied.
+ 提供了不同的密碼
+
+
+
+ DatabaseOpenWidget
+
+ Enter master key
+ 輸入主金鑰
+
+
+ Key File:
+ 金鑰檔案:
+
+
+ Password:
+ 密碼:
+
+
+ Browse
+ 瀏覽
+
+
+ Error
+ 錯誤
+
+
+ Unable to open the database.
+ 無法打開這個資料庫
+
+
+ Can't open key file
+ 無法打開金鑰檔案
+
+
+ All files
+ 所有的檔案
+
+
+ Key files
+ 金鑰檔案
+
+
+ Select key file
+ 選擇金鑰檔案
+
+
+
+ DatabaseSettingsWidget
+
+ Database name:
+ 資料庫名稱:
+
+
+ Database description:
+ 資料庫描述:
+
+
+ Transform rounds:
+ 加密轉換次數
+
+
+ Default username:
+ 預設的使用者名稱:
+
+
+ Use recycle bin:
+ 使用垃圾桶:
+
+
+ MiB
+ MiB
+
+
+ Benchmark
+ 效能測試
+
+
+ Max. history items:
+ 最大的歷史筆數:
+
+
+ Max. history size:
+ 最大的歷史大小:
+
+
+
+ DatabaseTabWidget
+
+ Root
+ 根
+
+
+ KeePass 2 Database
+ KeePass 2 資料庫
+
+
+ All files
+ 所有的檔案
+
+
+ Open database
+ 打開資料庫
+
+
+ Warning
+ 警告
+
+
+ File not found!
+ 找不到檔案!
+
+
+ Open KeePass 1 database
+ 打開 KeePass 1 資料庫
+
+
+ KeePass 1 database
+ KeePass 1 資料庫
+
+
+ All files (*)
+ 所有的檔案 (*)
+
+
+ Close?
+ 關閉?
+
+
+ "%1" is in edit mode.
+Close anyway?
+ "%1" 正在編輯中。仍然要關閉嗎?
+
+
+ Save changes?
+ 儲存修改?
+
+
+ "%1" was modified.
+Save changes?
+ "%1" 已被修改。要儲存嗎?
+
+
+ Error
+ 錯誤
+
+
+ Writing the database failed.
+ 寫入資料庫失敗
+
+
+ Save database as
+ 將資料庫儲存為
+
+
+ New database
+ 新的資料庫
+
+
+ locked
+ 已鎖住
+
+
+
+ DatabaseWidget
+
+ Change master key
+ 變更主金鑰
+
+
+ Delete entry?
+ 刪除項目
+
+
+ Do you really want to delete the entry "%1" for good?
+ 你真的想永遠的刪除 "%1" 項目嗎
+
+
+ Delete entries?
+ 刪除項目?
+
+
+ Do you really want to delete %1 entries for good?
+ 你真的想永遠刪除 "%1 " 項目嗎?
+
+
+ Move entries to recycle bin?
+ 移動項目到垃圾桶?
+
+
+ Do you really want to move %n entry(s) to the recycle bin?
+ 你真的想將 %n 個項目移到垃圾桶?
+
+
+ Delete group?
+ 刪除群組?
+
+
+ Do you really want to delete the group "%1" for good?
+ 你真的想永遠刪除 "%1 " 群組嗎?
+
+
+ Current group
+ 目前的群組
+
+
+
+ EditEntryWidget
+
+ Entry
+ 項目
+
+
+ Advanced
+ 進階的
+
+
+ Icon
+ 圖示
+
+
+ Auto-Type
+ 自動輸入
+
+
+ Properties
+ 性質
+
+
+ History
+ 歷史記錄
+
+
+ Entry history
+ 項目歷史
+
+
+ Add entry
+ 增加項目
+
+
+ Edit entry
+ 編輯項目
+
+
+ Error
+ 錯誤
+
+
+ Different passwords supplied.
+ 提供了不同的密碼
+
+
+ New attribute
+ 新的屬性
+
+
+ Select file
+ 選擇檔案
+
+
+ Unable to open file
+ 無法打開檔案
+
+
+ Save attachment
+ 儲存附件
+
+
+ Unable to save the attachment:
+
+ 無法儲存這個附件:
+
+
+
+ Tomorrow
+ 明天
+
+
+ %n week(s)
+ %n 個禮拜
+
+
+ %n month(s)
+ %n 個月
+
+
+ 1 year
+ 1 年
+
+
+
+ EditEntryWidgetAdvanced
+
+ Additional attributes
+ 額外的屬性
+
+
+ Add
+ 加入
+
+
+ Edit
+ 編輯
+
+
+ Remove
+ 移除
+
+
+ Attachments
+ 附件
+
+
+ Save
+ 儲存
+
+
+
+ EditEntryWidgetAutoType
+
+ Enable Auto-Type for this entry
+ 打開此項目的自動輸入
+
+
+ Inherit default Auto-Type sequence from the group
+ 從父群組繼承預設的自動輸入序列
+
+
+ Use custom Auto-Type sequence:
+ 使用自訂的自動輸入序列
+
+
+ +
+ +
+
+
+ -
+ -
+
+
+ Window title:
+ 視窗標題:
+
+
+ Use default sequence
+ 使用預設序列
+
+
+ Set custom sequence:
+ 設定自訂的序列
+
+
+
+ EditEntryWidgetHistory
+
+ Show
+ 顯示
+
+
+ Restore
+ 還原
+
+
+ Delete
+ 刪除
+
+
+ Delete all
+ 刪除全部
+
+
+
+ EditEntryWidgetMain
+
+ Title:
+ 標題:
+
+
+ Username:
+ 使用者名稱:
+
+
+ Password:
+ 密碼:
+
+
+ Repeat:
+ 重複:
+
+
+ Gen.
+ 產生
+
+
+ URL:
+ 網址:
+
+
+ Expires
+ 過期
+
+
+ Presets
+ 預設
+
+
+ Notes:
+ 附註:
+
+
+
+ EditGroupWidget
+
+ Group
+ 群組
+
+
+ Icon
+ 圖示
+
+
+ Properties
+ 性質
+
+
+ Add group
+ 加入群組
+
+
+ Edit group
+ 編輯群組
+
+
+ Enable
+ 啟用
+
+
+ Disable
+ 關閉
+
+
+ Inherit from parent group (%1)
+ 繼承自父群組 (%1)
+
+
+
+ EditGroupWidgetMain
+
+ Name
+ 名稱
+
+
+ Notes
+ 附註
+
+
+ Expires
+ 過期
+
+
+ Search
+ 搜尋
+
+
+ Auto-type
+ 自動輸入
+
+
+
+ EditWidgetIcons
+
+ Use default icon
+ 使用預設的圖示
+
+
+ Use custom icon
+ 使用自訂的圖示
+
+
+ Add custom icon
+ 加入自訂的圖示
+
+
+ Delete custom icon
+ 刪除自訂的圖示
+
+
+ Images
+ 圖片
+
+
+ All files
+ 所有的檔案
+
+
+ Select Image
+ 選擇圖片
+
+
+ Can't delete icon!
+ 不能刪除圖示!
+
+
+ Can't delete icon. Still used by %n item(s).
+ 不能刪除圖示。仍在被 %n 個使用
+
+
+
+ EditWidgetProperties
+
+ Created:
+ 已建立:
+
+
+ Modified:
+ 已修改:
+
+
+ Accessed:
+ 已存取
+
+
+ Uuid:
+ Uuid (通用唯一識別碼)
+
+
+
+ EntryAttributesModel
+
+ Name
+ 名稱
+
+
+
+ EntryHistoryModel
+
+ Last modified
+ 最後修改
+
+
+ Title
+ 標題
+
+
+ Username
+ 使用者名稱
+
+
+ URL
+ 網址
+
+
+
+ EntryModel
+
+ Group
+ 群組
+
+
+ Title
+ 標題
+
+
+ Username
+ 使用者名稱
+
+
+ URL
+ 網址
+
+
+
+ Group
+
+ Recycle Bin
+ 回收桶
+
+
+
+ KeePass1OpenWidget
+
+ Import KeePass1 database
+ 匯入 KeePass 1 資料庫
+
+
+ Error
+ 錯誤
+
+
+ Unable to open the database.
+ 無法開啟這個資料庫
+
+
+
+ KeePass1Reader
+
+ Unable to read keyfile.
+ 無法讀取金鑰檔案
+
+
+ Not a KeePass database.
+ 不是 KeePass 資料庫
+
+
+ Unsupported encryption algorithm.
+ 不支援的加密演算法
+
+
+ Unsupported KeePass database version.
+ 不支援的 KeePass 資料庫版本
+
+
+ Root
+ 根
+
+
+
+ KeePass2Reader
+
+ Not a KeePass database.
+ 不是 KeePass 的資料庫
+
+
+ Unsupported KeePass database version.
+ 不支援的 KeePass 資料庫版本
+
+
+ Wrong key or database file is corrupt.
+ 無法的金鑰或資料庫損壞
+
+
+
+ Main
+
+ Fatal error while testing the cryptographic functions.
+ 重大錯誤,在測試加密函數時
+
+
+ KeePassX - Error
+ KeePassX - 錯誤
+
+
+
+ MainWindow
+
+ Database
+ 資料庫
+
+
+ Recent databases
+ 近期的資料庫
+
+
+ Help
+ 幫助
+
+
+ Entries
+ 項目
+
+
+ Copy attribute to clipboard
+ 將屬性複製到剪貼簿
+
+
+ Groups
+ 群組
+
+
+ Extras
+ 其它
+
+
+ View
+ 顯示
+
+
+ Quit
+ 關閉
+
+
+ About
+ 關於
+
+
+ Open database
+ 打開資料庫
+
+
+ Save database
+ 儲存資料庫
+
+
+ Close database
+ 關閉資料庫
+
+
+ New database
+ 新增資料庫
+
+
+ Add new entry
+ 加入項目
+
+
+ View/Edit entry
+ 瀏覽/編輯項目
+
+
+ Delete entry
+ 刪除項目
+
+
+ Add new group
+ 增加新的群組
+
+
+ Edit group
+ 編輯群組
+
+
+ Delete group
+ 刪除群組
+
+
+ Save database as
+ 儲存資料庫為
+
+
+ Change master key
+ 變更主金鑰
+
+
+ Database settings
+ 資料庫設定
+
+
+ Import KeePass 1 database
+ 匯入 KeePass 1 資料庫
+
+
+ Clone entry
+ 拷貝項目
+
+
+ Find
+ 尋找
+
+
+ Username
+ 使用者名稱
+
+
+ Copy username to clipboard
+ 將使用者名稱複製到剪貼簿
+
+
+ Password
+ 密碼
+
+
+ Copy password to clipboard
+ 將密碼複製到剪貼簿
+
+
+ Settings
+ 設定
+
+
+ Perform Auto-Type
+ 執行自動輸入
+
+
+ Open URL
+ 打開網址
+
+
+ Lock databases
+ 鎖住資料庫
+
+
+ Title
+ 標題
+
+
+ URL
+ 網址
+
+
+ Notes
+ 附註
+
+
+ Show toolbar
+ 顯示工具列
+
+
+ read-only
+ 唯讀
+
+
+ Toggle window
+ 切換視窗
+
+
+
+ PasswordGeneratorWidget
+
+ Password:
+ 密碼:
+
+
+ Length:
+ 長度:
+
+
+ Character Types
+ 字元類型
+
+
+ Upper Case Letters
+ 大寫英文字母
+
+
+ Lower Case Letters
+ 小寫英文字母
+
+
+ Numbers
+ 數字
+
+
+ Special Characters
+ 特殊字元
+
+
+ Exclude look-alike characters
+ 去除相似的字元
+
+
+ Ensure that the password contains characters from every group
+ 確定密碼包含每一組的字元
+
+
+ Accept
+ 接受
+
+
+
+ QCommandLineParser
+
+ Displays version information.
+ 顯示版本資訊
+
+
+ Displays this help.
+ 顯示這個幫助訊息
+
+
+ Unknown option '%1'.
+ 不知的選項 '%1'
+
+
+ Unknown options: %1.
+ 不知的選項 '%1'
+
+
+ Missing value after '%1'.
+ 在 "%1" 後缺少值
+
+
+ Unexpected value after '%1'.
+ "%1" 後有不預期的值
+
+
+ [options]
+ [選項]
+
+
+ Usage: %1
+ 使用方式:%1
+
+
+ Options:
+ 選項:
+
+
+ Arguments:
+ 參數
+
+
+
+ QSaveFile
+
+ Existing file %1 is not writable
+ 現有的檔案 %1 不可寫入
+
+
+ Writing canceled by application
+ 應用程式取消寫入
+
+
+ Partial write. Partition full?
+ 部分寫入。分區滿了嗎?
+
+
+
+ QtIOCompressor
+
+ Internal zlib error when compressing:
+ 進行壓縮時,函式庫 zlib 出錯
+
+
+ Error writing to underlying device:
+ 寫入底層裝置時出錯
+
+
+ Error opening underlying device:
+ 開啟底層裝置時出錯
+
+
+ Error reading data from underlying device:
+ 讀取底層裝置時出錯
+
+
+ Internal zlib error when decompressing:
+ 在解壓縮時,內部的 zlib 函式庫發生錯誤:
+
+
+
+ QtIOCompressor::open
+
+ The gzip format not supported in this version of zlib.
+ 這個版本的壓縮函式庫 zlib 不支援 gzip
+
+
+ Internal zlib error:
+ 內部函式庫 zlib 發生錯誤:
+
+
+
+ SearchWidget
+
+ Find:
+ 尋找:
+
+
+ Case sensitive
+ 區分大小寫
+
+
+ Current group
+ 目前的群組
+
+
+ Root group
+ 根群組
+
+
+
+ SettingsWidget
+
+ Application Settings
+ 應用程式設定
+
+
+ General
+ 一般
+
+
+ Security
+ 安全性
+
+
+
+ SettingsWidgetGeneral
+
+ Remember last databases
+ 記住最近的資料庫
+
+
+ Open previous databases on startup
+ 在啟動時開啟最近的資料庫
+
+
+ Mark as modified on expanded state changes
+ 擴展狀態發生變化時,標記為已修改
+
+
+ Automatically save on exit
+ 離開時,自動儲存
+
+
+ Automatically save after every change
+ 修改後,自動儲存
+
+
+ Minimize when copying to clipboard
+ 在複製到剪貼簿時最小化
+
+
+ Use group icon on entry creation
+ 新增項目時使用群組圖示
+
+
+ Global Auto-Type shortcut
+ 全域自動輸入快捷鍵
+
+
+ Use entry title to match windows for global auto-type
+ 使用項目標題來找尋自動輸入的目標視窗
+
+
+ Language
+ 語言
+
+
+ Show a system tray icon
+ 顯示系統夾圖示
+
+
+ Hide window to system tray when minimized
+ 將視窗最小化至工作列
+
+
+
+ SettingsWidgetSecurity
+
+ Clear clipboard after
+ 在多久後清除剪貼簿
+
+
+ sec
+ 秒
+
+
+ Lock databases after inactivity of
+ 在多久沒有動作之後鎖住資料庫
+
+
+ Show passwords in cleartext by default
+ 預設以明碼顯示密碼
+
+
+ Always ask before performing auto-type
+ 在執行自動輸入前通常要詢問
+
+
+
+ UnlockDatabaseWidget
+
+ Unlock database
+ 解鎖資料庫
+
+
+ Error
+ 錯誤
+
+
+ Wrong key.
+ 錯誤的金鑰
+
+
+
+ WelcomeWidget
+
+ Welcome!
+ 歡迎!
+
+
+
+ main
+
+ KeePassX - cross-platform password manager
+ KeePassX - 跨平台密碼管理軟體
+
+
+ filename of the password database to open (*.kdbx)
+ 要開啟的密碼資料庫檔案名稱 (*.kdbx)
+
+
+ path to a custom config file
+ 自定設定檔的路徑
+
+
+ password of the database (DANGEROUS!)
+ 資料庫的密碼(危險!)
+
+
+ key file of the database
+ 資料庫的金鑰
+
+
+
\ No newline at end of file
diff --git a/share/translations/update.sh b/share/translations/update.sh
index 6828dc82..c6296b54 100755
--- a/share/translations/update.sh
+++ b/share/translations/update.sh
@@ -4,5 +4,9 @@ BASEDIR=$(dirname $0)
cd $BASEDIR/../..
+echo Updating source file
lupdate -no-ui-lines -disable-heuristic similartext -locations none -no-obsolete src -ts share/translations/keepassx_en.ts
lupdate -no-ui-lines -disable-heuristic similartext -locations none -pluralonly src -ts share/translations/keepassx_en_plurals.ts
+
+echo Pulling translations from Transifex
+tx pull -a --minimum-perc=80
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 7967cf8a..67eb44c6 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -43,6 +43,7 @@ set(keepassx_SOURCES
core/ListDeleter.h
core/Metadata.cpp
core/PasswordGenerator.cpp
+ core/qlockfile.cpp
core/qsavefile.cpp
core/qsavefile_p.h
core/SignalMultiplexer.cpp
@@ -138,6 +139,18 @@ if(NOT GCRYPT_HAS_SALSA20)
)
endif()
+if(UNIX)
+ set(keepassx_SOURCES
+ ${keepassx_SOURCES}
+ core/qlockfile_unix.cpp
+ )
+elseif(MINGW)
+ set(keepassx_SOURCES
+ ${keepassx_SOURCES}
+ core/qlockfile_win.cpp
+ )
+endif()
+
set(keepassx_SOURCES_MAINEXE
main.cpp
)
diff --git a/src/autotype/AutoTypeAction.cpp b/src/autotype/AutoTypeAction.cpp
index cc751abe..090ca823 100644
--- a/src/autotype/AutoTypeAction.cpp
+++ b/src/autotype/AutoTypeAction.cpp
@@ -89,5 +89,7 @@ void AutoTypeExecutor::execDelay(AutoTypeDelay* action)
void AutoTypeExecutor::execClearField(AutoTypeClearField* action)
{
+ Q_UNUSED(action);
+
// TODO: implement
}
diff --git a/src/autotype/x11/AutoTypeX11.cpp b/src/autotype/x11/AutoTypeX11.cpp
index 47ab9127..ddcf809e 100644
--- a/src/autotype/x11/AutoTypeX11.cpp
+++ b/src/autotype/x11/AutoTypeX11.cpp
@@ -655,7 +655,7 @@ void AutoTypePlatformX11::SendKeyPressedEvent(KeySym keysym)
int keycode;
if (keysym == NoSymbol) {
- qWarning("No such key: keysym=0x%lX", static_cast(keysym));
+ qWarning("No such key: keysym=0x%lX", keysym);
return;
}
@@ -682,7 +682,7 @@ void AutoTypePlatformX11::SendKeyPressedEvent(KeySym keysym)
/* determine keycode and mask for the given keysym */
keycode = GetKeycode(keysym, &wanted_mask);
if (keycode < 8 || keycode > 255) {
- qWarning("Unable to get valid keycode for key: keysym=0x%lX", static_cast(keysym));
+ qWarning("Unable to get valid keycode for key: keysym=0x%lX", keysym);
return;
}
diff --git a/src/config-keepassx.h.cmake b/src/config-keepassx.h.cmake
index 305da341..197c0d32 100644
--- a/src/config-keepassx.h.cmake
+++ b/src/config-keepassx.h.cmake
@@ -8,8 +8,8 @@
#define KEEPASSX_SOURCE_DIR "${CMAKE_SOURCE_DIR}"
#define KEEPASSX_BINARY_DIR "${CMAKE_BINARY_DIR}"
+#define KEEPASSX_PREFIX_DIR "${CMAKE_INSTALL_PREFIX}"
#define KEEPASSX_PLUGIN_DIR "${PLUGIN_INSTALL_DIR}"
-
#define KEEPASSX_DATA_DIR "${DATA_INSTALL_DIR}"
#cmakedefine HAVE_PR_SET_DUMPABLE 1
diff --git a/src/core/Database.cpp b/src/core/Database.cpp
index 4c888eab..098cc06a 100644
--- a/src/core/Database.cpp
+++ b/src/core/Database.cpp
@@ -188,32 +188,51 @@ void Database::setCompressionAlgo(Database::CompressionAlgorithm algo)
m_data.compressionAlgo = algo;
}
-void Database::setTransformRounds(quint64 rounds)
+bool Database::setTransformRounds(quint64 rounds)
{
if (m_data.transformRounds != rounds) {
+ quint64 oldRounds = m_data.transformRounds;
+
m_data.transformRounds = rounds;
if (m_data.hasKey) {
- setKey(m_data.key);
+ if (!setKey(m_data.key)) {
+ m_data.transformRounds = oldRounds;
+ return false;
+ }
}
}
+
+ return true;
}
-void Database::setKey(const CompositeKey& key, const QByteArray& transformSeed, bool updateChangedTime)
+bool Database::setKey(const CompositeKey& key, const QByteArray& transformSeed,
+ bool updateChangedTime)
{
+ bool ok;
+ QString errorString;
+
+ QByteArray transformedMasterKey =
+ key.transform(transformSeed, transformRounds(), &ok, &errorString);
+ if (!ok) {
+ return false;
+ }
+
m_data.key = key;
m_data.transformSeed = transformSeed;
- m_data.transformedMasterKey = key.transform(transformSeed, transformRounds());
+ m_data.transformedMasterKey = transformedMasterKey;
m_data.hasKey = true;
if (updateChangedTime) {
m_metadata->setMasterKeyChanged(Tools::currentDateTimeUtc());
}
Q_EMIT modifiedImmediate();
+
+ return true;
}
-void Database::setKey(const CompositeKey& key)
+bool Database::setKey(const CompositeKey& key)
{
- setKey(key, randomGen()->randomArray(32));
+ return setKey(key, randomGen()->randomArray(32));
}
bool Database::hasKey() const
diff --git a/src/core/Database.h b/src/core/Database.h
index 3e0c675d..97ccad23 100644
--- a/src/core/Database.h
+++ b/src/core/Database.h
@@ -90,13 +90,14 @@ public:
void setCipher(const Uuid& cipher);
void setCompressionAlgo(Database::CompressionAlgorithm algo);
- void setTransformRounds(quint64 rounds);
- void setKey(const CompositeKey& key, const QByteArray& transformSeed, bool updateChangedTime = true);
+ bool setTransformRounds(quint64 rounds);
+ bool setKey(const CompositeKey& key, const QByteArray& transformSeed,
+ bool updateChangedTime = true);
/**
* Sets the database key and generates a random transform seed.
*/
- void setKey(const CompositeKey& key);
+ bool setKey(const CompositeKey& key);
bool hasKey() const;
bool verifyKey(const CompositeKey& key) const;
void recycleEntry(Entry* entry);
diff --git a/src/core/FilePath.cpp b/src/core/FilePath.cpp
index e414fdec..5366fe5c 100644
--- a/src/core/FilePath.cpp
+++ b/src/core/FilePath.cpp
@@ -49,13 +49,20 @@ QString FilePath::pluginPath(const QString& name)
pluginPaths << QCoreApplication::applicationDirPath();
- QString systemPluginDir = KEEPASSX_PLUGIN_DIR;
- if (systemPluginDir != ".") {
- if (!QDir(systemPluginDir).isAbsolute()) {
- systemPluginDir = QCoreApplication::applicationDirPath() + "/../" + systemPluginDir;
- systemPluginDir = QDir(systemPluginDir).canonicalPath();
+ QString configuredPluginDir = KEEPASSX_PLUGIN_DIR;
+ if (configuredPluginDir != ".") {
+ if (QDir(configuredPluginDir).isAbsolute()) {
+ pluginPaths << configuredPluginDir;
+ }
+ else {
+ QString relativePluginDir = QString("%1/../%2")
+ .arg(QCoreApplication::applicationDirPath(), configuredPluginDir);
+ pluginPaths << QDir(relativePluginDir).canonicalPath();
+
+ QString absolutePluginDir = QString("%1/%2")
+ .arg(KEEPASSX_PREFIX_DIR, configuredPluginDir);
+ pluginPaths << QDir(absolutePluginDir).canonicalPath();
}
- pluginPaths << systemPluginDir;
}
QStringList dirFilter;
@@ -164,6 +171,9 @@ QIcon FilePath::onOffIcon(const QString& category, const QString& name)
FilePath::FilePath()
{
+ const QString appDirPath = QCoreApplication::applicationDirPath();
+ bool isDataDirAbsolute = QDir::isAbsolutePath(KEEPASSX_DATA_DIR);
+
if (false) {
}
#ifdef QT_DEBUG
@@ -171,17 +181,19 @@ FilePath::FilePath()
}
#endif
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
- else if (testSetDir(QCoreApplication::applicationDirPath() + "/../share/keepassx")) {
+ else if (isDataDirAbsolute && testSetDir(KEEPASSX_DATA_DIR)) {
}
- else if (testSetDir(KEEPASSX_DATA_DIR)) {
+ else if (!isDataDirAbsolute && testSetDir(QString("%1/../%2").arg(appDirPath, KEEPASSX_DATA_DIR))) {
+ }
+ else if (!isDataDirAbsolute && testSetDir(QString("%1/%2").arg(KEEPASSX_PREFIX_DIR, KEEPASSX_DATA_DIR))) {
}
#endif
#ifdef Q_OS_MAC
- else if (testSetDir(QCoreApplication::applicationDirPath() + "/../Resources")) {
+ else if (testSetDir(appDirPath + "/../Resources")) {
}
#endif
#ifdef Q_OS_WIN
- else if (testSetDir(QCoreApplication::applicationDirPath() + "/share")) {
+ else if (testSetDir(appDirPath + "/share")) {
}
#endif
diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp
index 01ec316e..8034417f 100644
--- a/src/core/Tools.cpp
+++ b/src/core/Tools.cpp
@@ -39,10 +39,12 @@
#include "config-keepassx.h"
+#if defined(HAVE_RLIMIT_CORE)
+#include
+#endif
+
#if defined(HAVE_PR_SET_DUMPABLE)
#include
-#elif defined(HAVE_RLIMIT_CORE)
-#include
#endif
#ifdef HAVE_PT_DENY_ATTACH
@@ -222,21 +224,23 @@ QString platform()
void disableCoreDumps()
{
- bool success = false;
+ // default to true
+ // there is no point in printing a warning if this is not implemented on the platform
+ bool success = true;
- // prefer PR_SET_DUMPABLE since that also prevents ptrace
-#if defined(HAVE_PR_SET_DUMPABLE)
- success = (prctl(PR_SET_DUMPABLE, 0) == 0);
-#elif defined(HAVE_RLIMIT_CORE)
+#if defined(HAVE_RLIMIT_CORE)
struct rlimit limit;
limit.rlim_cur = 0;
limit.rlim_max = 0;
- success = (setrlimit(RLIMIT_CORE, &limit) == 0);
+ success = success && (setrlimit(RLIMIT_CORE, &limit) == 0);
+#endif
+
+#if defined(HAVE_PR_SET_DUMPABLE)
+ success = success && (prctl(PR_SET_DUMPABLE, 0) == 0);
#endif
// Mac OS X
#ifdef HAVE_PT_DENY_ATTACH
- // make sure setrlimit() and ptrace() succeeded
success = success && (ptrace(PT_DENY_ATTACH, 0, 0, 0) == 0);
#endif
diff --git a/src/core/qlockfile.cpp b/src/core/qlockfile.cpp
new file mode 100644
index 00000000..980f8ed8
--- /dev/null
+++ b/src/core/qlockfile.cpp
@@ -0,0 +1,337 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 David Faure
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qlockfile.h"
+#include "qlockfile_p.h"
+
+#include
+#include
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QLockFile
+ \inmodule QtCore
+ \brief The QLockFile class provides locking between processes using a file.
+ \since 5.1
+
+ A lock file can be used to prevent multiple processes from accessing concurrently
+ the same resource. For instance, a configuration file on disk, or a socket, a port,
+ a region of shared memory...
+
+ Serialization is only guaranteed if all processes that access the shared resource
+ use QLockFile, with the same file path.
+
+ QLockFile supports two use cases:
+ to protect a resource for a short-term operation (e.g. verifying if a configuration
+ file has changed before saving new settings), and for long-lived protection of a
+ resource (e.g. a document opened by a user in an editor) for an indefinite amount of time.
+
+ When protecting for a short-term operation, it is acceptable to call lock() and wait
+ until any running operation finishes.
+ When protecting a resource over a long time, however, the application should always
+ call setStaleLockTime(0) and then tryLock() with a short timeout, in order to
+ warn the user that the resource is locked.
+
+ If the process holding the lock crashes, the lock file stays on disk and can prevent
+ any other process from accessing the shared resource, ever. For this reason, QLockFile
+ tries to detect such a "stale" lock file, based on the process ID written into the file,
+ and (in case that process ID got reused meanwhile), on the last modification time of
+ the lock file (30s by default, for the use case of a short-lived operation).
+ If the lock file is found to be stale, it will be deleted.
+
+ For the use case of protecting a resource over a long time, you should therefore call
+ setStaleLockTime(0), and when tryLock() returns LockFailedError, inform the user
+ that the document is locked, possibly using getLockInfo() for more details.
+*/
+
+/*!
+ \enum QLockFile::LockError
+
+ This enum describes the result of the last call to lock() or tryLock().
+
+ \value NoError The lock was acquired successfully.
+ \value LockFailedError The lock could not be acquired because another process holds it.
+ \value PermissionError The lock file could not be created, for lack of permissions
+ in the parent directory.
+ \value UnknownError Another error happened, for instance a full partition
+ prevented writing out the lock file.
+*/
+
+/*!
+ Constructs a new lock file object.
+ The object is created in an unlocked state.
+ When calling lock() or tryLock(), a lock file named \a fileName will be created,
+ if it doesn't already exist.
+
+ \sa lock(), unlock()
+*/
+QLockFile::QLockFile(const QString &fileName)
+ : d_ptr(new QLockFilePrivate(fileName))
+{
+}
+
+/*!
+ Destroys the lock file object.
+ If the lock was acquired, this will release the lock, by deleting the lock file.
+*/
+QLockFile::~QLockFile()
+{
+ unlock();
+}
+
+/*!
+ Sets \a staleLockTime to be the time in milliseconds after which
+ a lock file is considered stale.
+ The default value is 30000, i.e. 30 seconds.
+ If your application typically keeps the file locked for more than 30 seconds
+ (for instance while saving megabytes of data for 2 minutes), you should set
+ a bigger value using setStaleLockTime().
+
+ The value of \a staleLockTime is used by lock() and tryLock() in order
+ to determine when an existing lock file is considered stale, i.e. left over
+ by a crashed process. This is useful for the case where the PID got reused
+ meanwhile, so the only way to detect a stale lock file is by the fact that
+ it has been around for a long time.
+
+ \sa staleLockTime()
+*/
+void QLockFile::setStaleLockTime(int staleLockTime)
+{
+ Q_D(QLockFile);
+ d->staleLockTime = staleLockTime;
+}
+
+/*!
+ Returns the time in milliseconds after which
+ a lock file is considered stale.
+
+ \sa setStaleLockTime()
+*/
+int QLockFile::staleLockTime() const
+{
+ Q_D(const QLockFile);
+ return d->staleLockTime;
+}
+
+/*!
+ Returns \c true if the lock was acquired by this QLockFile instance,
+ otherwise returns \c false.
+
+ \sa lock(), unlock(), tryLock()
+*/
+bool QLockFile::isLocked() const
+{
+ Q_D(const QLockFile);
+ return d->isLocked;
+}
+
+/*!
+ Creates the lock file.
+
+ If another process (or another thread) has created the lock file already,
+ this function will block until that process (or thread) releases it.
+
+ Calling this function multiple times on the same lock from the same
+ thread without unlocking first is not allowed. This function will
+ \e dead-lock when the file is locked recursively.
+
+ Returns \c true if the lock was acquired, false if it could not be acquired
+ due to an unrecoverable error, such as no permissions in the parent directory.
+
+ \sa unlock(), tryLock()
+*/
+bool QLockFile::lock()
+{
+ return tryLock(-1);
+}
+
+/*!
+ Attempts to create the lock file. This function returns \c true if the
+ lock was obtained; otherwise it returns \c false. If another process (or
+ another thread) has created the lock file already, this function will
+ wait for at most \a timeout milliseconds for the lock file to become
+ available.
+
+ Note: Passing a negative number as the \a timeout is equivalent to
+ calling lock(), i.e. this function will wait forever until the lock
+ file can be locked if \a timeout is negative.
+
+ If the lock was obtained, it must be released with unlock()
+ before another process (or thread) can successfully lock it.
+
+ Calling this function multiple times on the same lock from the same
+ thread without unlocking first is not allowed, this function will
+ \e always return false when attempting to lock the file recursively.
+
+ \sa lock(), unlock()
+*/
+bool QLockFile::tryLock(int timeout)
+{
+ Q_D(QLockFile);
+ QElapsedTimer timer;
+ if (timeout > 0)
+ timer.start();
+ int sleepTime = 100;
+ Q_FOREVER {
+ d->lockError = d->tryLock_sys();
+ switch (d->lockError) {
+ case NoError:
+ d->isLocked = true;
+ return true;
+ case PermissionError:
+ case UnknownError:
+ return false;
+ case LockFailedError:
+ if (!d->isLocked && d->isApparentlyStale()) {
+ // Stale lock from another thread/process
+ // Ensure two processes don't remove it at the same time
+ QLockFile rmlock(d->fileName + QLatin1String(".rmlock"));
+ if (rmlock.tryLock()) {
+ if (d->isApparentlyStale() && d->removeStaleLock())
+ continue;
+ }
+ }
+ break;
+ }
+ if (timeout == 0 || (timeout > 0 && timer.hasExpired(timeout)))
+ return false;
+ QLockFileThread::msleep(sleepTime);
+ if (sleepTime < 5 * 1000)
+ sleepTime *= 2;
+ }
+ // not reached
+ return false;
+}
+
+/*!
+ \fn void QLockFile::unlock()
+ Releases the lock, by deleting the lock file.
+
+ Calling unlock() without locking the file first, does nothing.
+
+ \sa lock(), tryLock()
+*/
+
+/*!
+ Retrieves information about the current owner of the lock file.
+
+ If tryLock() returns \c false, and error() returns LockFailedError,
+ this function can be called to find out more information about the existing
+ lock file:
+ \list
+ \li the PID of the application (returned in \a pid)
+ \li the \a hostname it's running on (useful in case of networked filesystems),
+ \li the name of the application which created it (returned in \a appname),
+ \endlist
+
+ Note that tryLock() automatically deleted the file if there is no
+ running application with this PID, so LockFailedError can only happen if there is
+ an application with this PID (it could be unrelated though).
+
+ This can be used to inform users about the existing lock file and give them
+ the choice to delete it. After removing the file using removeStaleLockFile(),
+ the application can call tryLock() again.
+
+ This function returns \c true if the information could be successfully retrieved, false
+ if the lock file doesn't exist or doesn't contain the expected data.
+ This can happen if the lock file was deleted between the time where tryLock() failed
+ and the call to this function. Simply call tryLock() again if this happens.
+*/
+bool QLockFile::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const
+{
+ Q_D(const QLockFile);
+ return d->getLockInfo(pid, hostname, appname);
+}
+
+bool QLockFilePrivate::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const
+{
+ QFile reader(fileName);
+ if (!reader.open(QIODevice::ReadOnly))
+ return false;
+
+ QByteArray pidLine = reader.readLine();
+ pidLine.chop(1);
+ QByteArray appNameLine = reader.readLine();
+ appNameLine.chop(1);
+ QByteArray hostNameLine = reader.readLine();
+ hostNameLine.chop(1);
+ if (pidLine.isEmpty())
+ return false;
+
+ qint64 thePid = pidLine.toLongLong();
+ if (pid)
+ *pid = thePid;
+ if (appname)
+ *appname = QString::fromUtf8(appNameLine);
+ if (hostname)
+ *hostname = QString::fromUtf8(hostNameLine);
+ return thePid > 0;
+}
+
+/*!
+ Attempts to forcefully remove an existing lock file.
+
+ Calling this is not recommended when protecting a short-lived operation: QLockFile
+ already takes care of removing lock files after they are older than staleLockTime().
+
+ This method should only be called when protecting a resource for a long time, i.e.
+ with staleLockTime(0), and after tryLock() returned LockFailedError, and the user
+ agreed on removing the lock file.
+
+ Returns \c true on success, false if the lock file couldn't be removed. This happens
+ on Windows, when the application owning the lock is still running.
+*/
+bool QLockFile::removeStaleLockFile()
+{
+ Q_D(QLockFile);
+ if (d->isLocked) {
+ qWarning("removeStaleLockFile can only be called when not holding the lock");
+ return false;
+ }
+ return d->removeStaleLock();
+}
+
+/*!
+ Returns the lock file error status.
+
+ If tryLock() returns \c false, this function can be called to find out
+ the reason why the locking failed.
+*/
+QLockFile::LockError QLockFile::error() const
+{
+ Q_D(const QLockFile);
+ return d->lockError;
+}
+
+QT_END_NAMESPACE
diff --git a/src/core/qlockfile.h b/src/core/qlockfile.h
new file mode 100644
index 00000000..673026f2
--- /dev/null
+++ b/src/core/qlockfile.h
@@ -0,0 +1,79 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 David Faure
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QLOCKFILE_H
+#define QLOCKFILE_H
+
+#include
+#include
+
+QT_BEGIN_NAMESPACE
+
+class QLockFilePrivate;
+
+class QLockFile
+{
+public:
+ QLockFile(const QString &fileName);
+ ~QLockFile();
+
+ bool lock();
+ bool tryLock(int timeout = 0);
+ void unlock();
+
+ void setStaleLockTime(int);
+ int staleLockTime() const;
+
+ bool isLocked() const;
+ bool getLockInfo(qint64 *pid, QString *hostname, QString *appname) const;
+ bool removeStaleLockFile();
+
+ enum LockError {
+ NoError = 0,
+ LockFailedError = 1,
+ PermissionError = 2,
+ UnknownError = 3
+ };
+ LockError error() const;
+
+protected:
+ QScopedPointer d_ptr;
+
+private:
+ Q_DECLARE_PRIVATE(QLockFile)
+ Q_DISABLE_COPY(QLockFile)
+};
+
+QT_END_NAMESPACE
+
+#endif // QLOCKFILE_H
diff --git a/src/core/qlockfile_p.h b/src/core/qlockfile_p.h
new file mode 100644
index 00000000..93092683
--- /dev/null
+++ b/src/core/qlockfile_p.h
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 David Faure
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QLOCKFILE_P_H
+#define QLOCKFILE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qlockfile.h"
+
+#include
+#include
+
+#ifdef Q_OS_WIN
+#include
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QLockFileThread : public QThread
+{
+public:
+ static void msleep(unsigned long msecs) { QThread::msleep(msecs); }
+};
+
+class QLockFilePrivate
+{
+public:
+ QLockFilePrivate(const QString &fn)
+ : fileName(fn),
+#ifdef Q_OS_WIN
+ fileHandle(INVALID_HANDLE_VALUE),
+#else
+ fileHandle(-1),
+#endif
+ staleLockTime(30 * 1000), // 30 seconds
+ lockError(QLockFile::NoError),
+ isLocked(false)
+ {
+ }
+ QLockFile::LockError tryLock_sys();
+ bool removeStaleLock();
+ bool getLockInfo(qint64 *pid, QString *hostname, QString *appname) const;
+ // Returns \c true if the lock belongs to dead PID, or is old.
+ // The attempt to delete it will tell us if it was really stale or not, though.
+ bool isApparentlyStale() const;
+
+#ifdef Q_OS_UNIX
+ static int checkFcntlWorksAfterFlock();
+#endif
+
+ QString fileName;
+#ifdef Q_OS_WIN
+ Qt::HANDLE fileHandle;
+#else
+ int fileHandle;
+#endif
+ int staleLockTime; // "int milliseconds" is big enough for 24 days
+ QLockFile::LockError lockError;
+ bool isLocked;
+};
+
+QT_END_NAMESPACE
+
+#endif /* QLOCKFILE_P_H */
diff --git a/src/core/qlockfile_unix.cpp b/src/core/qlockfile_unix.cpp
new file mode 100644
index 00000000..99678c0b
--- /dev/null
+++ b/src/core/qlockfile_unix.cpp
@@ -0,0 +1,199 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 David Faure
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qlockfile_p.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include // flock
+#include // kill
+#include // kill
+#include
+
+#include
+
+QT_BEGIN_NAMESPACE
+
+#define EINTR_LOOP(var, cmd) \
+ do { \
+ var = cmd; \
+ } while (var == -1 && errno == EINTR)
+
+// don't call QT_OPEN or ::open
+// call qt_safe_open
+static inline int qt_safe_open(const char *pathname, int flags, mode_t mode = 0777)
+{
+#ifdef O_CLOEXEC
+ flags |= O_CLOEXEC;
+#endif
+ int fd;
+ EINTR_LOOP(fd, ::open(pathname, flags, mode));
+
+ // unknown flags are ignored, so we have no way of verifying if
+ // O_CLOEXEC was accepted
+ if (fd != -1)
+ ::fcntl(fd, F_SETFD, FD_CLOEXEC);
+ return fd;
+}
+
+static inline qint64 qt_safe_write(int fd, const void *data, qint64 len)
+{
+ qint64 ret = 0;
+ EINTR_LOOP(ret, ::write(fd, data, len));
+ return ret;
+}
+
+static QString localHostName() // from QHostInfo::localHostName()
+{
+ char hostName[512];
+ if (gethostname(hostName, sizeof(hostName)) == -1)
+ return QString();
+ hostName[sizeof(hostName) - 1] = '\0';
+ return QString::fromLocal8Bit(hostName);
+}
+
+// ### merge into qt_safe_write?
+static qint64 qt_write_loop(int fd, const char *data, qint64 len)
+{
+ qint64 pos = 0;
+ while (pos < len) {
+ const qint64 ret = qt_safe_write(fd, data + pos, len - pos);
+ if (ret == -1) // e.g. partition full
+ return pos;
+ pos += ret;
+ }
+ return pos;
+}
+
+static bool setNativeLocks(int fd)
+{
+#if defined(LOCK_EX) && defined(LOCK_NB)
+ if (flock(fd, LOCK_EX | LOCK_NB) == -1) // other threads, and other processes on a local fs
+ return false;
+#endif
+ struct flock flockData;
+ flockData.l_type = F_WRLCK;
+ flockData.l_whence = SEEK_SET;
+ flockData.l_start = 0;
+ flockData.l_len = 0; // 0 = entire file
+ flockData.l_pid = getpid();
+ if (fcntl(fd, F_SETLK, &flockData) == -1) // for networked filesystems
+ return false;
+ return true;
+}
+
+QLockFile::LockError QLockFilePrivate::tryLock_sys()
+{
+ // Assemble data, to write in a single call to write
+ // (otherwise we'd have to check every write call)
+ // Use operator% from the fast builder to avoid multiple memory allocations.
+ QByteArray fileData = QByteArray::number(QCoreApplication::applicationPid()) + '\n'
+ + qAppName().toUtf8() + '\n'
+ + localHostName().toUtf8() + '\n';
+
+ const QByteArray lockFileName = QFile::encodeName(fileName);
+ const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY | O_CREAT | O_EXCL, 0644);
+ if (fd < 0) {
+ switch (errno) {
+ case EEXIST:
+ return QLockFile::LockFailedError;
+ case EACCES:
+ case EROFS:
+ return QLockFile::PermissionError;
+ default:
+ return QLockFile::UnknownError;
+ }
+ }
+ // Ensure nobody else can delete the file while we have it
+ if (!setNativeLocks(fd))
+ qWarning() << "setNativeLocks failed:" << strerror(errno);
+
+ if (qt_write_loop(fd, fileData.constData(), fileData.size()) < fileData.size()) {
+ close(fd);
+ if (!QFile::remove(fileName))
+ qWarning("QLockFile: Could not remove our own lock file %s.", qPrintable(fileName));
+ return QLockFile::UnknownError; // partition full
+ }
+
+ // We hold the lock, continue.
+ fileHandle = fd;
+
+ return QLockFile::NoError;
+}
+
+bool QLockFilePrivate::removeStaleLock()
+{
+ const QByteArray lockFileName = QFile::encodeName(fileName);
+ const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY, 0644);
+ if (fd < 0) // gone already?
+ return false;
+ bool success = setNativeLocks(fd) && (::unlink(lockFileName) == 0);
+ close(fd);
+ return success;
+}
+
+bool QLockFilePrivate::isApparentlyStale() const
+{
+ qint64 pid;
+ QString hostname, appname;
+ if (!getLockInfo(&pid, &hostname, &appname))
+ return false;
+ if (hostname.isEmpty() || hostname == localHostName()) {
+ if (::kill(pid, 0) == -1 && errno == ESRCH)
+ return true; // PID doesn't exist anymore
+ }
+ const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTime());
+ return staleLockTime > 0 && age > staleLockTime;
+}
+
+void QLockFile::unlock()
+{
+ Q_D(QLockFile);
+ if (!d->isLocked)
+ return;
+ close(d->fileHandle);
+ d->fileHandle = -1;
+ if (!QFile::remove(d->fileName)) {
+ qWarning() << "Could not remove our own lock file" << d->fileName << "maybe permissions changed meanwhile?";
+ // This is bad because other users of this lock file will now have to wait for the stale-lock-timeout...
+ }
+ QFile::remove(d->fileName);
+ d->lockError = QLockFile::NoError;
+ d->isLocked = false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/core/qlockfile_win.cpp b/src/core/qlockfile_win.cpp
new file mode 100644
index 00000000..4ca07eb7
--- /dev/null
+++ b/src/core/qlockfile_win.cpp
@@ -0,0 +1,178 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 David Faure
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef _UNICODE
+#define _UNICODE
+#endif
+
+#ifndef UNICODE
+#define UNICODE
+#endif
+
+#include "qlockfile_p.h"
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+QT_BEGIN_NAMESPACE
+
+static inline QByteArray localHostName()
+{
+ return qgetenv("COMPUTERNAME");
+}
+
+static inline bool fileExists(const wchar_t *fileName)
+{
+ WIN32_FILE_ATTRIBUTE_DATA data;
+ return GetFileAttributesEx(fileName, GetFileExInfoStandard, &data);
+}
+
+QLockFile::LockError QLockFilePrivate::tryLock_sys()
+{
+ const ushort* nativePath = QDir::toNativeSeparators(fileName).utf16();
+ // When writing, allow others to read.
+ // When reading, QFile will allow others to read and write, all good.
+ // Adding FILE_SHARE_DELETE would allow forceful deletion of stale files,
+ // but Windows doesn't allow recreating it while this handle is open anyway,
+ // so this would only create confusion (can't lock, but no lock file to read from).
+ const DWORD dwShareMode = FILE_SHARE_READ;
+#ifndef Q_OS_WINRT
+ SECURITY_ATTRIBUTES securityAtts = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE };
+ HANDLE fh = CreateFile((const wchar_t*)nativePath,
+ GENERIC_WRITE,
+ dwShareMode,
+ &securityAtts,
+ CREATE_NEW, // error if already exists
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+#else // !Q_OS_WINRT
+ HANDLE fh = CreateFile2((const wchar_t*)nativePath,
+ GENERIC_WRITE,
+ dwShareMode,
+ CREATE_NEW, // error if already exists
+ NULL);
+#endif // Q_OS_WINRT
+ if (fh == INVALID_HANDLE_VALUE) {
+ const DWORD lastError = GetLastError();
+ switch (lastError) {
+ case ERROR_SHARING_VIOLATION:
+ case ERROR_ALREADY_EXISTS:
+ case ERROR_FILE_EXISTS:
+ return QLockFile::LockFailedError;
+ case ERROR_ACCESS_DENIED:
+ // readonly file, or file still in use by another process.
+ // Assume the latter if the file exists, since we don't create it readonly.
+ return fileExists((const wchar_t*)nativePath)
+ ? QLockFile::LockFailedError
+ : QLockFile::PermissionError;
+ default:
+ qWarning() << "Got unexpected locking error" << lastError;
+ return QLockFile::UnknownError;
+ }
+ }
+
+ // We hold the lock, continue.
+ fileHandle = fh;
+ // Assemble data, to write in a single call to write
+ // (otherwise we'd have to check every write call)
+ QByteArray fileData;
+ fileData += QByteArray::number(QCoreApplication::applicationPid());
+ fileData += '\n';
+ fileData += QCoreApplication::applicationName().toUtf8();
+ fileData += '\n';
+ fileData += localHostName();
+ fileData += '\n';
+ DWORD bytesWritten = 0;
+ QLockFile::LockError error = QLockFile::NoError;
+ if (!WriteFile(fh, fileData.constData(), fileData.size(), &bytesWritten, NULL) || !FlushFileBuffers(fh))
+ error = QLockFile::UnknownError; // partition full
+ return error;
+}
+
+bool QLockFilePrivate::removeStaleLock()
+{
+ // QFile::remove fails on Windows if the other process is still using the file, so it's not stale.
+ return QFile::remove(fileName);
+}
+
+bool QLockFilePrivate::isApparentlyStale() const
+{
+ qint64 pid;
+ QString hostname, appname;
+ if (!getLockInfo(&pid, &hostname, &appname))
+ return false;
+
+ // On WinRT there seems to be no way of obtaining information about other
+ // processes due to sandboxing
+#ifndef Q_OS_WINRT
+ if (hostname == QString::fromLocal8Bit(localHostName())) {
+ HANDLE procHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
+ if (!procHandle)
+ return true;
+ // We got a handle but check if process is still alive
+ DWORD dwR = ::WaitForSingleObject(procHandle, 0);
+ ::CloseHandle(procHandle);
+ if (dwR == WAIT_TIMEOUT)
+ return true;
+ }
+#endif // !Q_OS_WINRT
+ const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTime());
+ return staleLockTime > 0 && age > staleLockTime;
+}
+
+void QLockFile::unlock()
+{
+ Q_D(QLockFile);
+ if (!d->isLocked)
+ return;
+ CloseHandle(d->fileHandle);
+ int attempts = 0;
+ static const int maxAttempts = 500; // 500ms
+ while (!QFile::remove(d->fileName) && ++attempts < maxAttempts) {
+ // Someone is reading the lock file right now (on Windows this prevents deleting it).
+ QLockFileThread::msleep(1);
+ }
+ if (attempts == maxAttempts) {
+ qWarning() << "Could not remove our own lock file" << d->fileName << ". Either other users of the lock file are reading it constantly for 500 ms, or we (no longer) have permissions to delete the file";
+ // This is bad because other users of this lock file will now have to wait for the stale-lock-timeout...
+ }
+ d->lockError = QLockFile::NoError;
+ d->isLocked = false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/crypto/Crypto.cpp b/src/crypto/Crypto.cpp
index bf3076e8..e32e2d2d 100644
--- a/src/crypto/Crypto.cpp
+++ b/src/crypto/Crypto.cpp
@@ -142,66 +142,181 @@ bool Crypto::checkAlgorithms()
}
bool Crypto::selfTest()
+{
+ return testSha256() && testAes256Cbc() && testAes256Ecb() && testTwofish() && testSalsa20();
+}
+
+void Crypto::raiseError(const QString& str)
+{
+ m_errorStr = str;
+ qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
+}
+
+bool Crypto::testSha256()
{
QByteArray sha256Test = CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
CryptoHash::Sha256);
if (sha256Test != QByteArray::fromHex("248D6A61D20638B8E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1")) {
- m_errorStr = "SHA-256 mismatch.";
- qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
+ raiseError("SHA-256 mismatch.");
return false;
}
+ return true;
+}
+
+bool Crypto::testAes256Cbc()
+{
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6");
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
+ bool ok;
- SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt, key, iv);
- if (aes256Encrypt.process(plainText) != cipherText) {
- m_errorStr = "AES-256 encryption mismatch.";
- qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
+ SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
+ if (!aes256Encrypt.init(key, iv)) {
+ raiseError(aes256Encrypt.errorString());
+ return false;
+ }
+ QByteArray encryptedText = aes256Encrypt.process(plainText, &ok);
+ if (!ok) {
+ raiseError(aes256Encrypt.errorString());
+ return false;
+ }
+ if (encryptedText != cipherText) {
+ raiseError("AES-256 CBC encryption mismatch.");
return false;
}
- SymmetricCipher aes256Descrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt, key, iv);
- if (aes256Descrypt.process(cipherText) != plainText) {
- m_errorStr = "AES-256 decryption mismatch.";
- qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
+ SymmetricCipher aes256Descrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
+ if (!aes256Descrypt.init(key, iv)) {
+ raiseError(aes256Descrypt.errorString());
return false;
}
-
- // Twofish
- cipherText = QByteArray::fromHex("e0227c3cc80f3cb1b2ed847cc6f57d3c");
- cipherText.append(QByteArray::fromHex("657b1e7960b30fb7c8d62e72ae37c3a0"));
-
- SymmetricCipher twofishEncrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Encrypt, key, iv);
- if (twofishEncrypt.process(plainText) != cipherText) {
- m_errorStr = "Twofish encryption mismatch.";
- qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
+ QByteArray decryptedText = aes256Descrypt.process(cipherText, &ok);
+ if (!ok) {
+ raiseError(aes256Descrypt.errorString());
return false;
}
-
- SymmetricCipher twofishDecrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Decrypt, key, iv);
- if (twofishDecrypt.process(cipherText) != plainText) {
- m_errorStr = "Twofish decryption mismatch.";
- qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
- return false;
- }
-
- QByteArray salsa20Key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112");
- QByteArray salsa20iv = QByteArray::fromHex("0000000000000000");
- QByteArray salsa20Plain = QByteArray::fromHex("00000000000000000000000000000000");
- QByteArray salsa20Cipher = QByteArray::fromHex("B4C0AFA503BE7FC29A62058166D56F8F");
-
- SymmetricCipher salsa20Stream(SymmetricCipher::Salsa20, SymmetricCipher::Stream,
- SymmetricCipher::Encrypt, salsa20Key, salsa20iv);
-
- if (salsa20Stream.process(salsa20Plain) != salsa20Cipher) {
- m_errorStr = "Salsa20 stream cipher mismatch.";
- qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
+ if (decryptedText != plainText) {
+ raiseError("AES-256 CBC decryption mismatch.");
+ return false;
+ }
+
+ return true;
+}
+
+bool Crypto::testAes256Ecb()
+{
+ QByteArray key = QByteArray::fromHex("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
+ QByteArray iv = QByteArray::fromHex("00000000000000000000000000000000");
+ QByteArray plainText = QByteArray::fromHex("00112233445566778899AABBCCDDEEFF");
+ plainText.append(QByteArray::fromHex("00112233445566778899AABBCCDDEEFF"));
+ QByteArray cipherText = QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089");
+ cipherText.append(QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089"));
+ bool ok;
+
+ SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Encrypt);
+ if (!aes256Encrypt.init(key, iv)) {
+ raiseError(aes256Encrypt.errorString());
+ return false;
+ }
+ QByteArray encryptedText = aes256Encrypt.process(plainText, &ok);
+ if (!ok) {
+ raiseError(aes256Encrypt.errorString());
+ return false;
+ }
+ if (encryptedText != cipherText) {
+ raiseError("AES-256 ECB encryption mismatch.");
+ return false;
+ }
+
+ SymmetricCipher aes256Descrypt(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Decrypt);
+ if (!aes256Descrypt.init(key, iv)) {
+ raiseError(aes256Descrypt.errorString());
+ return false;
+ }
+ QByteArray decryptedText = aes256Descrypt.process(cipherText, &ok);
+ if (!ok) {
+ raiseError(aes256Descrypt.errorString());
+ return false;
+ }
+ if (decryptedText != plainText) {
+ raiseError("AES-256 ECB decryption mismatch.");
+ return false;
+ }
+
+ return true;
+}
+
+bool Crypto::testTwofish()
+{
+ QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
+ QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
+ QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
+ plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
+ QByteArray cipherText = QByteArray::fromHex("e0227c3cc80f3cb1b2ed847cc6f57d3c");
+ cipherText.append(QByteArray::fromHex("657b1e7960b30fb7c8d62e72ae37c3a0"));
+ bool ok;
+
+ SymmetricCipher twofishEncrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
+ if (!twofishEncrypt.init(key, iv)) {
+ raiseError(twofishEncrypt.errorString());
+ return false;
+ }
+ QByteArray encryptedText = twofishEncrypt.process(plainText, &ok);
+ if (!ok) {
+ raiseError(twofishEncrypt.errorString());
+ return false;
+ }
+ if (encryptedText != cipherText) {
+ raiseError("Twofish encryption mismatch.");
+ return false;
+ }
+
+
+ SymmetricCipher twofishDecrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
+ if (!twofishDecrypt.init(key, iv)) {
+ raiseError(twofishEncrypt.errorString());
+ return false;
+ }
+ QByteArray decryptedText = twofishDecrypt.process(cipherText, &ok);
+ if (!ok) {
+ raiseError(twofishDecrypt.errorString());
+ return false;
+ }
+ if (decryptedText != plainText) {
+ raiseError("Twofish encryption mismatch.");
+ return false;
+ }
+
+ return true;
+}
+
+bool Crypto::testSalsa20()
+{
+ QByteArray salsa20Key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112");
+ QByteArray salsa20iv = QByteArray::fromHex("0000000000000000");
+ QByteArray salsa20Plain = QByteArray::fromHex("00000000000000000000000000000000");
+ QByteArray salsa20Cipher = QByteArray::fromHex("B4C0AFA503BE7FC29A62058166D56F8F");
+ bool ok;
+
+ SymmetricCipher salsa20Stream(SymmetricCipher::Salsa20, SymmetricCipher::Stream,
+ SymmetricCipher::Encrypt);
+ if (!salsa20Stream.init(salsa20Key, salsa20iv)) {
+ raiseError(salsa20Stream.errorString());
+ return false;
+ }
+
+ QByteArray salsaProcessed = salsa20Stream.process(salsa20Plain, &ok);
+ if (!ok) {
+ raiseError(salsa20Stream.errorString());
+ return false;
+ }
+ if (salsaProcessed != salsa20Cipher) {
+ raiseError("Salsa20 stream cipher mismatch.");
return false;
}
diff --git a/src/crypto/Crypto.h b/src/crypto/Crypto.h
index 9926f14b..b801cbb5 100644
--- a/src/crypto/Crypto.h
+++ b/src/crypto/Crypto.h
@@ -34,6 +34,12 @@ private:
Crypto();
static bool checkAlgorithms();
static bool selfTest();
+ static void raiseError(const QString& str);
+ static bool testSha256();
+ static bool testAes256Cbc();
+ static bool testAes256Ecb();
+ static bool testTwofish();
+ static bool testSalsa20();
static bool m_initalized;
static QString m_errorStr;
diff --git a/src/crypto/CryptoHash.cpp b/src/crypto/CryptoHash.cpp
index f175b775..d116451f 100644
--- a/src/crypto/CryptoHash.cpp
+++ b/src/crypto/CryptoHash.cpp
@@ -49,6 +49,7 @@ CryptoHash::CryptoHash(CryptoHash::Algorithm algo)
gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, 0);
Q_ASSERT(error == 0); // TODO: error handling
+ Q_UNUSED(error);
d->hashLen = gcry_md_get_algo_dlen(algoGcrypt);
}
diff --git a/src/crypto/SymmetricCipher.cpp b/src/crypto/SymmetricCipher.cpp
index 5f922265..454548ca 100644
--- a/src/crypto/SymmetricCipher.cpp
+++ b/src/crypto/SymmetricCipher.cpp
@@ -22,17 +22,39 @@
#include "crypto/SymmetricCipherSalsa20.h"
SymmetricCipher::SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
- SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv)
+ SymmetricCipher::Direction direction)
: m_backend(createBackend(algo, mode, direction))
+ , m_initialized(false)
{
- m_backend->setKey(key);
- m_backend->setIv(iv);
}
SymmetricCipher::~SymmetricCipher()
{
}
+bool SymmetricCipher::init(const QByteArray& key, const QByteArray& iv)
+{
+ if (!m_backend->init()) {
+ return false;
+ }
+
+ if (!m_backend->setKey(key)) {
+ return false;
+ }
+
+ if (!m_backend->setIv(iv)) {
+ return false;
+ }
+
+ m_initialized = true;
+ return true;
+}
+
+bool SymmetricCipher::isInitalized() const
+{
+ return m_initialized;
+}
+
SymmetricCipherBackend* SymmetricCipher::createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
SymmetricCipher::Direction direction)
{
@@ -55,12 +77,17 @@ SymmetricCipherBackend* SymmetricCipher::createBackend(SymmetricCipher::Algorith
}
}
-void SymmetricCipher::reset()
+bool SymmetricCipher::reset()
{
- m_backend->reset();
+ return m_backend->reset();
}
int SymmetricCipher::blockSize() const
{
return m_backend->blockSize();
}
+
+QString SymmetricCipher::errorString() const
+{
+ return m_backend->errorString();
+}
diff --git a/src/crypto/SymmetricCipher.h b/src/crypto/SymmetricCipher.h
index d036e532..b8b3eb13 100644
--- a/src/crypto/SymmetricCipher.h
+++ b/src/crypto/SymmetricCipher.h
@@ -20,6 +20,7 @@
#include
#include
+#include
#include "core/Global.h"
#include "crypto/SymmetricCipherBackend.h"
@@ -48,30 +49,35 @@ public:
};
SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
- SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv);
+ SymmetricCipher::Direction direction);
~SymmetricCipher();
- inline QByteArray process(const QByteArray& data) {
- return m_backend->process(data);
+ bool init(const QByteArray& key, const QByteArray& iv);
+ bool isInitalized() const;
+
+ inline QByteArray process(const QByteArray& data, bool* ok) {
+ return m_backend->process(data, ok);
}
- inline void processInPlace(QByteArray& data) {
- m_backend->processInPlace(data);
+ inline bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT {
+ return m_backend->processInPlace(data);
}
- inline void processInPlace(QByteArray& data, quint64 rounds) {
+ inline bool processInPlace(QByteArray& data, quint64 rounds) Q_REQUIRED_RESULT {
Q_ASSERT(rounds > 0);
- m_backend->processInPlace(data, rounds);
+ return m_backend->processInPlace(data, rounds);
}
- void reset();
+ bool reset();
int blockSize() const;
+ QString errorString() const;
private:
static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
SymmetricCipher::Direction direction);
const QScopedPointer m_backend;
+ bool m_initialized;
Q_DISABLE_COPY(SymmetricCipher)
};
diff --git a/src/crypto/SymmetricCipherBackend.h b/src/crypto/SymmetricCipherBackend.h
index e6110cba..8f19b8ed 100644
--- a/src/crypto/SymmetricCipherBackend.h
+++ b/src/crypto/SymmetricCipherBackend.h
@@ -24,15 +24,18 @@ class SymmetricCipherBackend
{
public:
virtual ~SymmetricCipherBackend() {}
- virtual void setKey(const QByteArray& key) = 0;
- virtual void setIv(const QByteArray& iv) = 0;
+ virtual bool init() = 0;
+ virtual bool setKey(const QByteArray& key) = 0;
+ virtual bool setIv(const QByteArray& iv) = 0;
- virtual QByteArray process(const QByteArray& data) = 0;
- virtual void processInPlace(QByteArray& data) = 0;
- virtual void processInPlace(QByteArray& data, quint64 rounds) = 0;
+ virtual QByteArray process(const QByteArray& data, bool* ok) = 0;
+ virtual bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT = 0;
+ virtual bool processInPlace(QByteArray& data, quint64 rounds) Q_REQUIRED_RESULT = 0;
- virtual void reset() = 0;
+ virtual bool reset() = 0;
virtual int blockSize() const = 0;
+
+ virtual QString errorString() const = 0;
};
#endif // KEEPASSX_SYMMETRICCIPHERBACKEND_H
diff --git a/src/crypto/SymmetricCipherGcrypt.cpp b/src/crypto/SymmetricCipherGcrypt.cpp
index cf52bafa..5c4fe6ab 100644
--- a/src/crypto/SymmetricCipherGcrypt.cpp
+++ b/src/crypto/SymmetricCipherGcrypt.cpp
@@ -22,22 +22,12 @@
SymmetricCipherGcrypt::SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
SymmetricCipher::Direction direction)
- : m_algo(gcryptAlgo(algo))
+ : m_ctx(Q_NULLPTR)
+ , m_algo(gcryptAlgo(algo))
, m_mode(gcryptMode(mode))
, m_direction(direction)
, m_blockSize(-1)
{
- Q_ASSERT(Crypto::initalized());
-
- gcry_error_t error;
-
- error = gcry_cipher_open(&m_ctx, m_algo, m_mode, 0);
- Q_ASSERT(error == 0); // TODO: real error checking
-
- size_t blockSizeT;
- error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_BLKLEN, Q_NULLPTR, &blockSizeT);
- Q_ASSERT(error == 0);
- m_blockSize = blockSizeT;
}
SymmetricCipherGcrypt::~SymmetricCipherGcrypt()
@@ -83,21 +73,65 @@ int SymmetricCipherGcrypt::gcryptMode(SymmetricCipher::Mode mode)
}
}
-void SymmetricCipherGcrypt::setKey(const QByteArray& key)
+void SymmetricCipherGcrypt::setErrorString(gcry_error_t err)
+{
+ const char* gcryptError = gcry_strerror(err);
+ const char* gcryptErrorSource = gcry_strsource(err);
+
+ m_errorString = QString("%1/%2").arg(QString::fromLocal8Bit(gcryptErrorSource),
+ QString::fromLocal8Bit(gcryptError));
+}
+
+bool SymmetricCipherGcrypt::init()
+{
+ Q_ASSERT(Crypto::initalized());
+
+ gcry_error_t error;
+
+ error = gcry_cipher_open(&m_ctx, m_algo, m_mode, 0);
+ if (error != 0) {
+ setErrorString(error);
+ return false;
+ }
+
+ size_t blockSizeT;
+ error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_BLKLEN, Q_NULLPTR, &blockSizeT);
+ if (error != 0) {
+ setErrorString(error);
+ return false;
+ }
+
+ m_blockSize = blockSizeT;
+ return true;
+}
+
+bool SymmetricCipherGcrypt::setKey(const QByteArray& key)
{
m_key = key;
gcry_error_t error = gcry_cipher_setkey(m_ctx, m_key.constData(), m_key.size());
- Q_ASSERT(error == 0);
+
+ if (error != 0) {
+ setErrorString(error);
+ return false;
+ }
+
+ return true;
}
-void SymmetricCipherGcrypt::setIv(const QByteArray& iv)
+bool SymmetricCipherGcrypt::setIv(const QByteArray& iv)
{
m_iv = iv;
gcry_error_t error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
- Q_ASSERT(error == 0);
+
+ if (error != 0) {
+ setErrorString(error);
+ return false;
+ }
+
+ return true;
}
-QByteArray SymmetricCipherGcrypt::process(const QByteArray& data)
+QByteArray SymmetricCipherGcrypt::process(const QByteArray& data, bool* ok)
{
// TODO: check block size
@@ -113,12 +147,16 @@ QByteArray SymmetricCipherGcrypt::process(const QByteArray& data)
error = gcry_cipher_encrypt(m_ctx, result.data(), data.size(), data.constData(), data.size());
}
- Q_ASSERT(error == 0);
+ if (error != 0) {
+ setErrorString(error);
+ *ok = false;
+ }
+ *ok = true;
return result;
}
-void SymmetricCipherGcrypt::processInPlace(QByteArray& data)
+bool SymmetricCipherGcrypt::processInPlace(QByteArray& data)
{
// TODO: check block size
@@ -131,10 +169,15 @@ void SymmetricCipherGcrypt::processInPlace(QByteArray& data)
error = gcry_cipher_encrypt(m_ctx, data.data(), data.size(), Q_NULLPTR, 0);
}
- Q_ASSERT(error == 0);
+ if (error != 0) {
+ setErrorString(error);
+ return false;
+ }
+
+ return true;
}
-void SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds)
+bool SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds)
{
// TODO: check block size
@@ -146,28 +189,52 @@ void SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds)
if (m_direction == SymmetricCipher::Decrypt) {
for (quint64 i = 0; i != rounds; ++i) {
error = gcry_cipher_decrypt(m_ctx, rawData, size, Q_NULLPTR, 0);
- Q_ASSERT(error == 0);
+
+ if (error != 0) {
+ setErrorString(error);
+ return false;
+ }
}
}
else {
for (quint64 i = 0; i != rounds; ++i) {
error = gcry_cipher_encrypt(m_ctx, rawData, size, Q_NULLPTR, 0);
- Q_ASSERT(error == 0);
+
+ if (error != 0) {
+ setErrorString(error);
+ return false;
+ }
}
}
+
+ return true;
}
-void SymmetricCipherGcrypt::reset()
+bool SymmetricCipherGcrypt::reset()
{
gcry_error_t error;
error = gcry_cipher_reset(m_ctx);
- Q_ASSERT(error == 0);
+ if (error != 0) {
+ setErrorString(error);
+ return false;
+ }
+
error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
- Q_ASSERT(error == 0);
+ if (error != 0) {
+ setErrorString(error);
+ return false;
+ }
+
+ return true;
}
int SymmetricCipherGcrypt::blockSize() const
{
return m_blockSize;
}
+
+QString SymmetricCipherGcrypt::errorString() const
+{
+ return m_errorString;
+}
diff --git a/src/crypto/SymmetricCipherGcrypt.h b/src/crypto/SymmetricCipherGcrypt.h
index c22059a4..367ee5b9 100644
--- a/src/crypto/SymmetricCipherGcrypt.h
+++ b/src/crypto/SymmetricCipherGcrypt.h
@@ -29,19 +29,24 @@ public:
SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
SymmetricCipher::Direction direction);
~SymmetricCipherGcrypt();
- void setKey(const QByteArray& key);
- void setIv(const QByteArray& iv);
- QByteArray process(const QByteArray& data);
- void processInPlace(QByteArray& data);
- void processInPlace(QByteArray& data, quint64 rounds);
+ bool init();
+ bool setKey(const QByteArray& key);
+ bool setIv(const QByteArray& iv);
- void reset();
+ QByteArray process(const QByteArray& data, bool* ok);
+ bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT;
+ bool processInPlace(QByteArray& data, quint64 rounds) Q_REQUIRED_RESULT;
+
+ bool reset();
int blockSize() const;
+ QString errorString() const;
+
private:
static int gcryptAlgo(SymmetricCipher::Algorithm algo);
static int gcryptMode(SymmetricCipher::Mode mode);
+ void setErrorString(gcry_error_t err);
gcry_cipher_hd_t m_ctx;
const int m_algo;
@@ -50,6 +55,7 @@ private:
QByteArray m_key;
QByteArray m_iv;
int m_blockSize;
+ QString m_errorString;
};
#endif // KEEPASSX_SYMMETRICCIPHERGCRYPT_H
diff --git a/src/crypto/SymmetricCipherSalsa20.cpp b/src/crypto/SymmetricCipherSalsa20.cpp
index d1732544..7e477656 100644
--- a/src/crypto/SymmetricCipherSalsa20.cpp
+++ b/src/crypto/SymmetricCipherSalsa20.cpp
@@ -33,23 +33,32 @@ SymmetricCipherSalsa20::~SymmetricCipherSalsa20()
{
}
-void SymmetricCipherSalsa20::setKey(const QByteArray& key)
+bool SymmetricCipherSalsa20::init()
+{
+ return true;
+}
+
+bool SymmetricCipherSalsa20::setKey(const QByteArray& key)
{
Q_ASSERT((key.size() == 16) || (key.size() == 32));
m_key = key;
ECRYPT_keysetup(&m_ctx, reinterpret_cast(m_key.constData()), m_key.size()*8, 64);
+
+ return true;
}
-void SymmetricCipherSalsa20::setIv(const QByteArray& iv)
+bool SymmetricCipherSalsa20::setIv(const QByteArray& iv)
{
Q_ASSERT(iv.size() == 8);
m_iv = iv;
ECRYPT_ivsetup(&m_ctx, reinterpret_cast(m_iv.constData()));
+
+ return true;
}
-QByteArray SymmetricCipherSalsa20::process(const QByteArray& data)
+QByteArray SymmetricCipherSalsa20::process(const QByteArray& data, bool* ok)
{
Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0));
@@ -59,18 +68,21 @@ QByteArray SymmetricCipherSalsa20::process(const QByteArray& data)
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast(data.constData()),
reinterpret_cast(result.data()), data.size());
+ *ok = true;
return result;
}
-void SymmetricCipherSalsa20::processInPlace(QByteArray& data)
+bool SymmetricCipherSalsa20::processInPlace(QByteArray& data)
{
Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0));
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast(data.constData()),
reinterpret_cast(data.data()), data.size());
+
+ return true;
}
-void SymmetricCipherSalsa20::processInPlace(QByteArray& data, quint64 rounds)
+bool SymmetricCipherSalsa20::processInPlace(QByteArray& data, quint64 rounds)
{
Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0));
@@ -78,14 +90,23 @@ void SymmetricCipherSalsa20::processInPlace(QByteArray& data, quint64 rounds)
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast(data.constData()),
reinterpret_cast(data.data()), data.size());
}
+
+ return true;
}
-void SymmetricCipherSalsa20::reset()
+bool SymmetricCipherSalsa20::reset()
{
ECRYPT_ivsetup(&m_ctx, reinterpret_cast(m_iv.constData()));
+
+ return true;
}
int SymmetricCipherSalsa20::blockSize() const
{
return 64;
}
+
+QString SymmetricCipherSalsa20::errorString() const
+{
+ return QString();
+}
diff --git a/src/crypto/SymmetricCipherSalsa20.h b/src/crypto/SymmetricCipherSalsa20.h
index e3e4726f..443d4ec8 100644
--- a/src/crypto/SymmetricCipherSalsa20.h
+++ b/src/crypto/SymmetricCipherSalsa20.h
@@ -28,19 +28,22 @@ public:
SymmetricCipherSalsa20(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
SymmetricCipher::Direction direction);
~SymmetricCipherSalsa20();
+ bool init();
void setAlgorithm(SymmetricCipher::Algorithm algo);
void setMode(SymmetricCipher::Mode mode);
void setDirection(SymmetricCipher::Direction direction);
- void setKey(const QByteArray& key);
- void setIv(const QByteArray& iv);
+ bool setKey(const QByteArray& key);
+ bool setIv(const QByteArray& iv);
- QByteArray process(const QByteArray& data);
- void processInPlace(QByteArray& data);
- void processInPlace(QByteArray& data, quint64 rounds);
+ QByteArray process(const QByteArray& data, bool* ok);
+ bool processInPlace(QByteArray& data);
+ bool processInPlace(QByteArray& data, quint64 rounds);
- void reset();
+ bool reset();
int blockSize() const;
+ QString errorString() const;
+
private:
ECRYPT_ctx m_ctx;
QByteArray m_key;
diff --git a/src/format/KeePass1Reader.cpp b/src/format/KeePass1Reader.cpp
index d4760b9e..929632bf 100644
--- a/src/format/KeePass1Reader.cpp
+++ b/src/format/KeePass1Reader.cpp
@@ -49,7 +49,12 @@ private:
KeePass1Reader::KeePass1Reader()
- : m_error(false)
+ : m_db(Q_NULLPTR)
+ , m_tmpParent(Q_NULLPTR)
+ , m_device(Q_NULLPTR)
+ , m_encryptionFlags(0)
+ , m_transformRounds(0)
+ , m_error(false)
{
}
@@ -154,14 +159,16 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
raiseError("Invalid number of transform rounds");
return Q_NULLPTR;
}
- m_db->setTransformRounds(m_transformRounds);
+ if (!m_db->setTransformRounds(m_transformRounds)) {
+ raiseError(tr("Unable to calculate master key"));
+ return Q_NULLPTR;
+ }
qint64 contentPos = m_device->pos();
QScopedPointer cipherStream(testKeys(password, keyfileData, contentPos));
if (!cipherStream) {
- raiseError("Unable to create cipher stream");
return Q_NULLPTR;
}
@@ -234,7 +241,10 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
key.addKey(newFileKey);
}
- db->setKey(key);
+ if (!db->setKey(key)) {
+ raiseError(tr("Unable to calculate master key"));
+ return Q_NULLPTR;
+ }
return db.take();
}
@@ -326,16 +336,26 @@ SymmetricCipherStream* KeePass1Reader::testKeys(const QString& password, const Q
}
QByteArray finalKey = key(passwordData, keyfileData);
+ if (finalKey.isEmpty()) {
+ return Q_NULLPTR;
+ }
if (m_encryptionFlags & KeePass1::Rijndael) {
cipherStream.reset(new SymmetricCipherStream(m_device, SymmetricCipher::Aes256,
- SymmetricCipher::Cbc, SymmetricCipher::Decrypt, finalKey, m_encryptionIV));
+ SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
}
else {
cipherStream.reset(new SymmetricCipherStream(m_device, SymmetricCipher::Twofish,
- SymmetricCipher::Cbc, SymmetricCipher::Decrypt, finalKey, m_encryptionIV));
+ SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
}
- cipherStream->open(QIODevice::ReadOnly);
+ if (!cipherStream->init(finalKey, m_encryptionIV)) {
+ raiseError(cipherStream->errorString());
+ return Q_NULLPTR;
+ }
+ if (!cipherStream->open(QIODevice::ReadOnly)) {
+ raiseError(cipherStream->errorString());
+ return Q_NULLPTR;
+ }
bool success = verifyKey(cipherStream.data());
@@ -372,9 +392,18 @@ QByteArray KeePass1Reader::key(const QByteArray& password, const QByteArray& key
key.setPassword(password);
key.setKeyfileData(keyfileData);
+ bool ok;
+ QString errorString;
+ QByteArray transformedKey = key.transform(m_transformSeed, m_transformRounds, &ok, &errorString);
+
+ if (!ok) {
+ raiseError(errorString);
+ return QByteArray();
+ }
+
CryptoHash hash(CryptoHash::Sha256);
hash.addData(m_masterSeed);
- hash.addData(key.transform(m_transformSeed, m_transformRounds));
+ hash.addData(transformedKey);
return hash.result();
}
diff --git a/src/format/KeePass2RandomStream.cpp b/src/format/KeePass2RandomStream.cpp
index 8b4a7c8e..1944e5d5 100644
--- a/src/format/KeePass2RandomStream.cpp
+++ b/src/format/KeePass2RandomStream.cpp
@@ -20,14 +20,19 @@
#include "crypto/CryptoHash.h"
#include "format/KeePass2.h"
-KeePass2RandomStream::KeePass2RandomStream(const QByteArray& key)
- : m_cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt,
- CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV)
+KeePass2RandomStream::KeePass2RandomStream()
+ : m_cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt)
, m_offset(0)
{
}
-QByteArray KeePass2RandomStream::randomBytes(int size)
+bool KeePass2RandomStream::init(const QByteArray& key)
+{
+ return m_cipher.init(CryptoHash::hash(key, CryptoHash::Sha256),
+ KeePass2::INNER_STREAM_SALSA20_IV);
+}
+
+QByteArray KeePass2RandomStream::randomBytes(int size, bool* ok)
{
QByteArray result;
@@ -35,7 +40,10 @@ QByteArray KeePass2RandomStream::randomBytes(int size)
while (bytesRemaining > 0) {
if (m_buffer.size() == m_offset) {
- loadBlock();
+ if (!loadBlock()) {
+ *ok = false;
+ return QByteArray();
+ }
}
int bytesToCopy = qMin(bytesRemaining, m_buffer.size() - m_offset);
@@ -44,12 +52,20 @@ QByteArray KeePass2RandomStream::randomBytes(int size)
bytesRemaining -= bytesToCopy;
}
+ *ok = true;
return result;
}
-QByteArray KeePass2RandomStream::process(const QByteArray& data)
+QByteArray KeePass2RandomStream::process(const QByteArray& data, bool* ok)
{
- QByteArray randomData = randomBytes(data.size());
+ bool randomBytesOk;
+
+ QByteArray randomData = randomBytes(data.size(), &randomBytesOk);
+ if (!randomBytesOk) {
+ *ok = false;
+ return QByteArray();
+ }
+
QByteArray result;
result.resize(data.size());
@@ -57,23 +73,39 @@ QByteArray KeePass2RandomStream::process(const QByteArray& data)
result[i] = data[i] ^ randomData[i];
}
+ *ok = true;
return result;
}
-void KeePass2RandomStream::processInPlace(QByteArray& data)
+bool KeePass2RandomStream::processInPlace(QByteArray& data)
{
- QByteArray randomData = randomBytes(data.size());
+ bool ok;
+ QByteArray randomData = randomBytes(data.size(), &ok);
+ if (!ok) {
+ return false;
+ }
for (int i = 0; i < data.size(); i++) {
data[i] = data[i] ^ randomData[i];
}
+
+ return true;
}
-void KeePass2RandomStream::loadBlock()
+QString KeePass2RandomStream::errorString() const
+{
+ return m_cipher.errorString();
+}
+
+bool KeePass2RandomStream::loadBlock()
{
Q_ASSERT(m_offset == m_buffer.size());
m_buffer.fill('\0', m_cipher.blockSize());
- m_cipher.processInPlace(m_buffer);
+ if (!m_cipher.processInPlace(m_buffer)) {
+ return false;
+ }
m_offset = 0;
+
+ return true;
}
diff --git a/src/format/KeePass2RandomStream.h b/src/format/KeePass2RandomStream.h
index c5bcf8cd..022c8399 100644
--- a/src/format/KeePass2RandomStream.h
+++ b/src/format/KeePass2RandomStream.h
@@ -25,13 +25,15 @@
class KeePass2RandomStream
{
public:
- explicit KeePass2RandomStream(const QByteArray& key);
- QByteArray randomBytes(int size);
- QByteArray process(const QByteArray& data);
- void processInPlace(QByteArray& data);
+ KeePass2RandomStream();
+ bool init(const QByteArray& key);
+ QByteArray randomBytes(int size, bool* ok);
+ QByteArray process(const QByteArray& data, bool* ok);
+ bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT;
+ QString errorString() const;
private:
- void loadBlock();
+ bool loadBlock();
SymmetricCipher m_cipher;
QByteArray m_buffer;
diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp
index 72b4d8e0..2a25001c 100644
--- a/src/format/KeePass2Reader.cpp
+++ b/src/format/KeePass2Reader.cpp
@@ -33,8 +33,12 @@
#include "streams/SymmetricCipherStream.h"
KeePass2Reader::KeePass2Reader()
- : m_error(false)
+ : m_device(Q_NULLPTR)
+ , m_headerStream(Q_NULLPTR)
+ , m_error(false)
+ , m_headerEnd(false)
, m_saveXml(false)
+ , m_db(Q_NULLPTR)
{
}
@@ -96,16 +100,26 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
return Q_NULLPTR;
}
- m_db->setKey(key, m_transformSeed, false);
+ if (!m_db->setKey(key, m_transformSeed, false)) {
+ raiseError(tr("Unable to calculate master key"));
+ return Q_NULLPTR;
+ }
CryptoHash hash(CryptoHash::Sha256);
hash.addData(m_masterSeed);
hash.addData(m_db->transformedMasterKey());
QByteArray finalKey = hash.result();
- SymmetricCipherStream cipherStream(m_device, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
- SymmetricCipher::Decrypt, finalKey, m_encryptionIV);
- cipherStream.open(QIODevice::ReadOnly);
+ SymmetricCipherStream cipherStream(m_device, SymmetricCipher::Aes256,
+ SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
+ if (!cipherStream.init(finalKey, m_encryptionIV)) {
+ raiseError(cipherStream.errorString());
+ return Q_NULLPTR;
+ }
+ if (!cipherStream.open(QIODevice::ReadOnly)) {
+ raiseError(cipherStream.errorString());
+ return Q_NULLPTR;
+ }
QByteArray realStart = cipherStream.read(32);
@@ -115,7 +129,10 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
}
HashedBlockStream hashedStream(&cipherStream);
- hashedStream.open(QIODevice::ReadOnly);
+ if (!hashedStream.open(QIODevice::ReadOnly)) {
+ raiseError(hashedStream.errorString());
+ return Q_NULLPTR;
+ }
QIODevice* xmlDevice;
QScopedPointer ioCompressor;
@@ -126,11 +143,18 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
else {
ioCompressor.reset(new QtIOCompressor(&hashedStream));
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
- ioCompressor->open(QIODevice::ReadOnly);
+ if (!ioCompressor->open(QIODevice::ReadOnly)) {
+ raiseError(ioCompressor->errorString());
+ return Q_NULLPTR;
+ }
xmlDevice = ioCompressor.data();
}
- KeePass2RandomStream randomStream(m_protectedStreamKey);
+ KeePass2RandomStream randomStream;
+ if (!randomStream.init(m_protectedStreamKey)) {
+ raiseError(randomStream.errorString());
+ return Q_NULLPTR;
+ }
QScopedPointer buffer;
@@ -340,7 +364,9 @@ void KeePass2Reader::setTansformRounds(const QByteArray& data)
raiseError("Invalid transform rounds size");
}
else {
- m_db->setTransformRounds(Endian::bytesToUInt64(data, KeePass2::BYTEORDER));
+ if (!m_db->setTransformRounds(Endian::bytesToUInt64(data, KeePass2::BYTEORDER))) {
+ raiseError(tr("Unable to calculate master key"));
+ }
}
}
diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp
index 69e24746..f233ac73 100644
--- a/src/format/KeePass2Writer.cpp
+++ b/src/format/KeePass2Writer.cpp
@@ -88,13 +88,20 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
CHECK_RETURN(writeData(header.data()));
SymmetricCipherStream cipherStream(device, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
- SymmetricCipher::Encrypt, finalKey, encryptionIV);
- cipherStream.open(QIODevice::WriteOnly);
+ SymmetricCipher::Encrypt);
+ cipherStream.init(finalKey, encryptionIV);
+ if (!cipherStream.open(QIODevice::WriteOnly)) {
+ raiseError(cipherStream.errorString());
+ return;
+ }
m_device = &cipherStream;
CHECK_RETURN(writeData(startBytes));
HashedBlockStream hashedStream(&cipherStream);
- hashedStream.open(QIODevice::WriteOnly);
+ if (!hashedStream.open(QIODevice::WriteOnly)) {
+ raiseError(hashedStream.errorString());
+ return;
+ }
QScopedPointer ioCompressor;
@@ -104,14 +111,25 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
else {
ioCompressor.reset(new QtIOCompressor(&hashedStream));
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
- ioCompressor->open(QIODevice::WriteOnly);
+ if (!ioCompressor->open(QIODevice::WriteOnly)) {
+ raiseError(ioCompressor->errorString());
+ return;
+ }
m_device = ioCompressor.data();
}
- KeePass2RandomStream randomStream(protectedStreamKey);
+ KeePass2RandomStream randomStream;
+ if (!randomStream.init(protectedStreamKey)) {
+ raiseError(randomStream.errorString());
+ return;
+ }
KeePass2XmlWriter xmlWriter;
xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash);
+
+ if (xmlWriter.hasError()) {
+ raiseError(xmlWriter.errorString());
+ }
}
bool KeePass2Writer::writeData(const QByteArray& data)
diff --git a/src/format/KeePass2XmlReader.cpp b/src/format/KeePass2XmlReader.cpp
index a5ceb218..9af89db7 100644
--- a/src/format/KeePass2XmlReader.cpp
+++ b/src/format/KeePass2XmlReader.cpp
@@ -34,6 +34,7 @@ KeePass2XmlReader::KeePass2XmlReader()
: m_randomStream(Q_NULLPTR)
, m_db(Q_NULLPTR)
, m_meta(Q_NULLPTR)
+ , m_tmpParent(Q_NULLPTR)
, m_error(false)
, m_strictMode(false)
{
@@ -809,7 +810,16 @@ void KeePass2XmlReader::parseEntryString(Entry* entry)
if (isProtected && !value.isEmpty()) {
if (m_randomStream) {
- value = QString::fromUtf8(m_randomStream->process(QByteArray::fromBase64(value.toLatin1())));
+ QByteArray ciphertext = QByteArray::fromBase64(value.toLatin1());
+ bool ok;
+ QByteArray plaintext = m_randomStream->process(ciphertext, &ok);
+ if (!ok) {
+ value.clear();
+ raiseError(m_randomStream->errorString());
+ }
+ else {
+ value = QString::fromUtf8(plaintext);
+ }
}
else {
raiseError("Unable to decrypt entry string");
@@ -868,7 +878,9 @@ QPair KeePass2XmlReader::parseEntryBinary(Entry* entry)
&& (attr.value("Protected") == "True");
if (isProtected && !value.isEmpty()) {
- m_randomStream->processInPlace(value);
+ if (!m_randomStream->processInPlace(value)) {
+ raiseError(m_randomStream->errorString());
+ }
}
}
diff --git a/src/format/KeePass2XmlWriter.cpp b/src/format/KeePass2XmlWriter.cpp
index 60364981..dda98011 100644
--- a/src/format/KeePass2XmlWriter.cpp
+++ b/src/format/KeePass2XmlWriter.cpp
@@ -28,6 +28,7 @@ KeePass2XmlWriter::KeePass2XmlWriter()
: m_db(Q_NULLPTR)
, m_meta(Q_NULLPTR)
, m_randomStream(Q_NULLPTR)
+ , m_error(false)
{
m_xml.setAutoFormatting(true);
m_xml.setAutoFormattingIndent(-1); // 1 tab
@@ -65,6 +66,16 @@ void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db)
writeDatabase(&file, db);
}
+bool KeePass2XmlWriter::hasError()
+{
+ return m_error;
+}
+
+QString KeePass2XmlWriter::errorString()
+{
+ return m_errorStr;
+}
+
void KeePass2XmlWriter::generateIdMap()
{
QList allEntries = m_db->rootGroup()->entriesRecursive(true);
@@ -340,7 +351,11 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry)
if (protect) {
if (m_randomStream) {
m_xml.writeAttribute("Protected", "True");
- QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8());
+ bool ok;
+ QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8(), &ok);
+ if (!ok) {
+ raiseError(m_randomStream->errorString());
+ }
value = QString::fromLatin1(rawData.toBase64());
}
else {
@@ -527,3 +542,9 @@ QString KeePass2XmlWriter::colorPartToString(int value)
return str;
}
+
+void KeePass2XmlWriter::raiseError(const QString& errorMessage)
+{
+ m_error = true;
+ m_errorStr = errorMessage;
+}
diff --git a/src/format/KeePass2XmlWriter.h b/src/format/KeePass2XmlWriter.h
index 16812fb9..ea621244 100644
--- a/src/format/KeePass2XmlWriter.h
+++ b/src/format/KeePass2XmlWriter.h
@@ -39,7 +39,7 @@ public:
void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = Q_NULLPTR,
const QByteArray& headerHash = QByteArray());
void writeDatabase(const QString& filename, Database* db);
- bool error();
+ bool hasError();
QString errorString();
private:
@@ -74,12 +74,16 @@ private:
void writeTriState(const QString& qualifiedName, Group::TriState triState);
QString colorPartToString(int value);
+ void raiseError(const QString& errorMessage);
+
QXmlStreamWriter m_xml;
Database* m_db;
Metadata* m_meta;
KeePass2RandomStream* m_randomStream;
QByteArray m_headerHash;
QHash m_idMap;
+ bool m_error;
+ QString m_errorStr;
};
#endif // KEEPASSX_KEEPASS2XMLWRITER_H
diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp
index e0dd06fd..3e346bc1 100644
--- a/src/gui/ChangeMasterKeyWidget.cpp
+++ b/src/gui/ChangeMasterKeyWidget.cpp
@@ -120,7 +120,10 @@ void ChangeMasterKeyWidget::generateKey()
FileKey fileKey;
QString errorMsg;
if (!fileKey.load(m_ui->keyFileCombo->currentText(), &errorMsg)) {
- // TODO: error handling
+ MessageBox::critical(this, tr("Failed to set key file"),
+ tr("Failed to set %1 as the Key file:\n%2")
+ .arg(m_ui->keyFileCombo->currentText(), errorMsg));
+ return;
}
m_key.addKey(fileKey);
}
diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp
index bba11f9a..007c44a0 100644
--- a/src/gui/DatabaseSettingsWidget.cpp
+++ b/src/gui/DatabaseSettingsWidget.cpp
@@ -130,7 +130,10 @@ void DatabaseSettingsWidget::reject()
void DatabaseSettingsWidget::transformRoundsBenchmark()
{
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
- m_ui->transformRoundsSpinBox->setValue(CompositeKey::transformKeyBenchmark(1000));
+ int rounds = CompositeKey::transformKeyBenchmark(1000);
+ if (rounds != -1) {
+ m_ui->transformRoundsSpinBox->setValue(rounds);
+ }
QApplication::restoreOverrideCursor();
}
diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp
index f55269fd..dbf458e0 100644
--- a/src/gui/DatabaseTabWidget.cpp
+++ b/src/gui/DatabaseTabWidget.cpp
@@ -36,6 +36,7 @@
DatabaseManagerStruct::DatabaseManagerStruct()
: dbWidget(Q_NULLPTR)
+ , lockFile(Q_NULLPTR)
, saveToFilename(false)
, modified(false)
, readOnly(false)
@@ -142,8 +143,35 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
}
file.close();
+ QLockFile* lockFile = new QLockFile(QString("%1/.%2.lock").arg(fileInfo.canonicalPath(), fileInfo.fileName()));
+ lockFile->setStaleLockTime(0);
+
+ if (!dbStruct.readOnly && !lockFile->tryLock()) {
+ // for now silently ignore if we can't create a lock file
+ // due to lack of permissions
+ if (lockFile->error() != QLockFile::PermissionError) {
+ QMessageBox::StandardButton result = MessageBox::question(this, tr("Open database"),
+ tr("The database you are trying to open is locked by another instance of KeePassX.\n"
+ "Do you want to open it anyway? Alternatively the database is opened read-only."),
+ QMessageBox::Yes | QMessageBox::No);
+
+ if (result == QMessageBox::No) {
+ dbStruct.readOnly = true;
+ delete lockFile;
+ lockFile = Q_NULLPTR;
+ }
+ else {
+ // take over the lock file if possible
+ if (lockFile->removeStaleLockFile()) {
+ lockFile->tryLock();
+ }
+ }
+ }
+ }
+
Database* db = new Database();
dbStruct.dbWidget = new DatabaseWidget(db, this);
+ dbStruct.lockFile = lockFile;
dbStruct.saveToFilename = !dbStruct.readOnly;
dbStruct.filePath = fileInfo.absoluteFilePath();
@@ -238,6 +266,7 @@ void DatabaseTabWidget::deleteDatabase(Database* db)
removeTab(index);
toggleTabbar();
m_dbList.remove(db);
+ delete dbStruct.lockFile;
delete dbStruct.dbWidget;
delete db;
@@ -311,6 +340,11 @@ bool DatabaseTabWidget::saveDatabaseAs(Database* db)
dbStruct.canonicalFilePath = fileInfo.canonicalFilePath();
dbStruct.fileName = fileInfo.fileName();
dbStruct.dbWidget->updateFilename(dbStruct.filePath);
+ QString lockFileName = QString("%1/.%2.lock")
+ .arg(fileInfo.canonicalPath(), fileInfo.fileName());
+ dbStruct.lockFile = new QLockFile(lockFileName);
+ dbStruct.lockFile->setStaleLockTime(0);
+ dbStruct.lockFile->tryLock();
updateTabName(db);
updateLastDatabases(dbStruct.filePath);
return true;
diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h
index dc5f1b13..2ad3010d 100644
--- a/src/gui/DatabaseTabWidget.h
+++ b/src/gui/DatabaseTabWidget.h
@@ -21,6 +21,7 @@
#include
#include
+#include "core/qlockfile.h"
#include "format/KeePass2Writer.h"
#include "gui/DatabaseWidget.h"
@@ -34,6 +35,7 @@ struct DatabaseManagerStruct
DatabaseManagerStruct();
DatabaseWidget* dbWidget;
+ QLockFile* lockFile;
QString filePath;
QString canonicalFilePath;
QString fileName;
diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp
index 369c6903..c5b7b514 100644
--- a/src/gui/DatabaseWidget.cpp
+++ b/src/gui/DatabaseWidget.cpp
@@ -599,8 +599,13 @@ void DatabaseWidget::updateMasterKey(bool accepted)
{
if (accepted) {
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
- m_db->setKey(m_changeMasterKeyWidget->newMasterKey());
+ bool result = m_db->setKey(m_changeMasterKeyWidget->newMasterKey());
QApplication::restoreOverrideCursor();
+
+ if (!result) {
+ MessageBox::critical(this, tr("Error"), tr("Unable to calculate master key"));
+ return;
+ }
}
else if (!m_db->hasKey()) {
Q_EMIT closeRequest();
@@ -906,3 +911,53 @@ bool DatabaseWidget::isGroupSelected() const
{
return m_groupView->currentGroup() != Q_NULLPTR;
}
+
+bool DatabaseWidget::currentEntryHasTitle()
+{
+ Entry* currentEntry = m_entryView->currentEntry();
+ if (!currentEntry) {
+ Q_ASSERT(false);
+ return false;
+ }
+ return !currentEntry->title().isEmpty();
+}
+
+bool DatabaseWidget::currentEntryHasUsername()
+{
+ Entry* currentEntry = m_entryView->currentEntry();
+ if (!currentEntry) {
+ Q_ASSERT(false);
+ return false;
+ }
+ return !currentEntry->username().isEmpty();
+}
+
+bool DatabaseWidget::currentEntryHasPassword()
+{
+ Entry* currentEntry = m_entryView->currentEntry();
+ if (!currentEntry) {
+ Q_ASSERT(false);
+ return false;
+ }
+ return !currentEntry->password().isEmpty();
+}
+
+bool DatabaseWidget::currentEntryHasUrl()
+{
+ Entry* currentEntry = m_entryView->currentEntry();
+ if (!currentEntry) {
+ Q_ASSERT(false);
+ return false;
+ }
+ return !currentEntry->url().isEmpty();
+}
+
+bool DatabaseWidget::currentEntryHasNotes()
+{
+ Entry* currentEntry = m_entryView->currentEntry();
+ if (!currentEntry) {
+ Q_ASSERT(false);
+ return false;
+ }
+ return !currentEntry->notes().isEmpty();
+}
diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h
index a38af731..1ba3dd1f 100644
--- a/src/gui/DatabaseWidget.h
+++ b/src/gui/DatabaseWidget.h
@@ -80,6 +80,11 @@ public:
QList entryHeaderViewSizes() const;
void setEntryViewHeaderSizes(const QList& sizes);
void clearAllWidgets();
+ bool currentEntryHasTitle();
+ bool currentEntryHasUsername();
+ bool currentEntryHasPassword();
+ bool currentEntryHasUrl();
+ bool currentEntryHasNotes();
Q_SIGNALS:
void closeRequest();
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
index 5b8e9e48..e873ce13 100644
--- a/src/gui/MainWindow.cpp
+++ b/src/gui/MainWindow.cpp
@@ -323,14 +323,14 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionEntryClone->setEnabled(singleEntrySelected && !inSearch);
m_ui->actionEntryEdit->setEnabled(singleEntrySelected);
m_ui->actionEntryDelete->setEnabled(entriesSelected);
- m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected);
- m_ui->actionEntryCopyUsername->setEnabled(singleEntrySelected);
- m_ui->actionEntryCopyPassword->setEnabled(singleEntrySelected);
- m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected);
- m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected);
+ m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTitle());
+ m_ui->actionEntryCopyUsername->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUsername());
+ m_ui->actionEntryCopyPassword->setEnabled(singleEntrySelected && dbWidget->currentEntryHasPassword());
+ m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
+ m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected);
m_ui->actionEntryAutoType->setEnabled(singleEntrySelected);
- m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected);
+ m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
m_ui->actionGroupNew->setEnabled(groupSelected);
m_ui->actionGroupEdit->setEnabled(groupSelected);
m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup());
diff --git a/src/gui/group/EditGroupWidget.cpp b/src/gui/group/EditGroupWidget.cpp
index 092af4a0..ebe5cb97 100644
--- a/src/gui/group/EditGroupWidget.cpp
+++ b/src/gui/group/EditGroupWidget.cpp
@@ -38,6 +38,8 @@ EditGroupWidget::EditGroupWidget(QWidget* parent)
add(tr("Properties"), m_editWidgetProperties);
connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool)));
+ connect(m_mainUi->autoTypeSequenceCustomRadio, SIGNAL(toggled(bool)),
+ m_mainUi->autoTypeSequenceCustomEdit, SLOT(setEnabled(bool)));
connect(this, SIGNAL(accepted()), SLOT(save()));
connect(this, SIGNAL(rejected()), SLOT(cancel()));
@@ -74,6 +76,13 @@ void EditGroupWidget::loadGroup(Group* group, bool create, Database* database)
m_mainUi->expireDatePicker->setDateTime(group->timeInfo().expiryTime().toLocalTime());
m_mainUi->searchComboBox->setCurrentIndex(indexFromTriState(group->searchingEnabled()));
m_mainUi->autotypeComboBox->setCurrentIndex(indexFromTriState(group->autoTypeEnabled()));
+ if (group->defaultAutoTypeSequence().isEmpty()) {
+ m_mainUi->autoTypeSequenceInherit->setChecked(true);
+ }
+ else {
+ m_mainUi->autoTypeSequenceCustomRadio->setChecked(true);
+ }
+ m_mainUi->autoTypeSequenceCustomEdit->setText(group->defaultAutoTypeSequence());
IconStruct iconStruct;
iconStruct.uuid = group->iconUuid();
@@ -97,6 +106,13 @@ void EditGroupWidget::save()
m_group->setSearchingEnabled(triStateFromIndex(m_mainUi->searchComboBox->currentIndex()));
m_group->setAutoTypeEnabled(triStateFromIndex(m_mainUi->autotypeComboBox->currentIndex()));
+ if (m_mainUi->autoTypeSequenceInherit->isChecked()) {
+ m_group->setDefaultAutoTypeSequence(QString());
+ }
+ else {
+ m_group->setDefaultAutoTypeSequence(m_mainUi->autoTypeSequenceCustomEdit->text());
+ }
+
IconStruct iconStruct = m_editGroupWidgetIcons->save();
if (iconStruct.number < 0) {
diff --git a/src/gui/group/EditGroupWidgetMain.ui b/src/gui/group/EditGroupWidgetMain.ui
index fdbf054d..b8abf762 100644
--- a/src/gui/group/EditGroupWidgetMain.ui
+++ b/src/gui/group/EditGroupWidgetMain.ui
@@ -7,7 +7,7 @@
0
0
676
- 334
+ 356
@@ -36,6 +36,13 @@
-
+ -
+
+
+ Expires
+
+
+
-
@@ -46,13 +53,6 @@
- -
-
-
- Expires
-
-
-
-
@@ -73,6 +73,47 @@
-
+ -
+
+
+ Use default auto-type sequence of parent group
+
+
+
+ -
+
+
+ Set default auto-type sequence
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 1
+
+
+
+
+ -
+
+
+ false
+
+
+
+
+
diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp
index 8f6c5319..a0f2373c 100644
--- a/src/keys/CompositeKey.cpp
+++ b/src/keys/CompositeKey.cpp
@@ -81,34 +81,62 @@ QByteArray CompositeKey::rawKey() const
return cryptoHash.result();
}
-QByteArray CompositeKey::transform(const QByteArray& seed, quint64 rounds) const
+QByteArray CompositeKey::transform(const QByteArray& seed, quint64 rounds,
+ bool* ok, QString* errorString) const
{
Q_ASSERT(seed.size() == 32);
Q_ASSERT(rounds > 0);
+ bool okLeft;
+ QString errorStringLeft;
+ bool okRight;
+ QString errorStringRight;
+
QByteArray key = rawKey();
- QFuture future = QtConcurrent::run(transformKeyRaw, key.left(16), seed, rounds);
- QByteArray result2 = transformKeyRaw(key.right(16), seed, rounds);
+ QFuture future = QtConcurrent::run(transformKeyRaw, key.left(16), seed, rounds, &okLeft, &errorStringLeft);
+ QByteArray result2 = transformKeyRaw(key.right(16), seed, rounds, &okRight, &errorStringRight);
QByteArray transformed;
transformed.append(future.result());
transformed.append(result2);
+ *ok = (okLeft && okRight);
+
+ if (!okLeft) {
+ *errorString = errorStringLeft;
+ return QByteArray();
+ }
+
+ if (!okRight) {
+ *errorString = errorStringRight;
+ return QByteArray();
+ }
+
return CryptoHash::hash(transformed, CryptoHash::Sha256);
}
QByteArray CompositeKey::transformKeyRaw(const QByteArray& key, const QByteArray& seed,
- quint64 rounds)
+ quint64 rounds, bool* ok, QString* errorString)
{
QByteArray iv(16, 0);
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb,
- SymmetricCipher::Encrypt, seed, iv);
+ SymmetricCipher::Encrypt);
+ if (!cipher.init(seed, iv)) {
+ *ok = false;
+ *errorString = cipher.errorString();
+ return QByteArray();
+ }
QByteArray result = key;
- cipher.processInPlace(result, rounds);
+ if (!cipher.processInPlace(result, rounds)) {
+ *ok = false;
+ *errorString = cipher.errorString();
+ return QByteArray();
+ }
+ *ok = true;
return result;
}
@@ -151,13 +179,17 @@ void TransformKeyBenchmarkThread::run()
QByteArray iv(16, 0);
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb,
- SymmetricCipher::Encrypt, seed, iv);
+ SymmetricCipher::Encrypt);
+ cipher.init(seed, iv);
QTime t;
t.start();
do {
- cipher.processInPlace(key, 100);
+ if (!cipher.processInPlace(key, 100)) {
+ m_rounds = -1;
+ return;
+ }
m_rounds += 100;
} while (t.elapsed() < m_msec);
}
diff --git a/src/keys/CompositeKey.h b/src/keys/CompositeKey.h
index ff738e61..3290d367 100644
--- a/src/keys/CompositeKey.h
+++ b/src/keys/CompositeKey.h
@@ -34,14 +34,15 @@ public:
CompositeKey& operator=(const CompositeKey& key);
QByteArray rawKey() const;
- QByteArray transform(const QByteArray& seed, quint64 rounds) const;
+ QByteArray transform(const QByteArray& seed, quint64 rounds,
+ bool* ok, QString* errorString) const;
void addKey(const Key& key);
static int transformKeyBenchmark(int msec);
private:
static QByteArray transformKeyRaw(const QByteArray& key, const QByteArray& seed,
- quint64 rounds);
+ quint64 rounds, bool* ok, QString* errorString);
QList m_keys;
};
diff --git a/src/streams/HashedBlockStream.cpp b/src/streams/HashedBlockStream.cpp
index 39e16de2..cd43dc0b 100644
--- a/src/streams/HashedBlockStream.cpp
+++ b/src/streams/HashedBlockStream.cpp
@@ -150,6 +150,7 @@ bool HashedBlockStream::readHashedBlock()
if (m_blockSize == 0) {
if (hash.count('\0') != 32) {
m_error = true;
+ setErrorString("Invalid hash of final block.");
return false;
}
@@ -166,6 +167,7 @@ bool HashedBlockStream::readHashedBlock()
if (hash != CryptoHash::hash(m_buffer, CryptoHash::Sha256)) {
m_error = true;
+ setErrorString("Mismatch between hash and data.");
return false;
}
@@ -213,6 +215,7 @@ bool HashedBlockStream::writeHashedBlock()
{
if (!Endian::writeInt32(m_blockIndex, m_baseDevice, ByteOrder)) {
m_error = true;
+ setErrorString(m_baseDevice->errorString());
return false;
}
m_blockIndex++;
@@ -227,17 +230,20 @@ bool HashedBlockStream::writeHashedBlock()
if (m_baseDevice->write(hash) != hash.size()) {
m_error = true;
+ setErrorString(m_baseDevice->errorString());
return false;
}
if (!Endian::writeInt32(m_buffer.size(), m_baseDevice, ByteOrder)) {
m_error = true;
+ setErrorString(m_baseDevice->errorString());
return false;
}
if (!m_buffer.isEmpty()) {
if (m_baseDevice->write(m_buffer) != m_buffer.size()) {
m_error = true;
+ setErrorString(m_baseDevice->errorString());
return false;
}
diff --git a/src/streams/LayeredStream.cpp b/src/streams/LayeredStream.cpp
index b71eb228..9f7783bd 100644
--- a/src/streams/LayeredStream.cpp
+++ b/src/streams/LayeredStream.cpp
@@ -34,11 +34,6 @@ bool LayeredStream::isSequential() const
return true;
}
-QString LayeredStream::errorString() const
-{
- return m_baseDevice->errorString();
-}
-
bool LayeredStream::open(QIODevice::OpenMode mode)
{
if (isOpen()) {
diff --git a/src/streams/LayeredStream.h b/src/streams/LayeredStream.h
index 285c16dd..b243e55b 100644
--- a/src/streams/LayeredStream.h
+++ b/src/streams/LayeredStream.h
@@ -31,7 +31,6 @@ public:
virtual ~LayeredStream();
bool isSequential() const Q_DECL_OVERRIDE;
- virtual QString errorString() const;
bool open(QIODevice::OpenMode mode) Q_DECL_OVERRIDE;
protected:
diff --git a/src/streams/StoreDataStream.cpp b/src/streams/StoreDataStream.cpp
index da94b851..18d7db98 100644
--- a/src/streams/StoreDataStream.cpp
+++ b/src/streams/StoreDataStream.cpp
@@ -41,6 +41,10 @@ QByteArray StoreDataStream::storedData() const
qint64 StoreDataStream::readData(char* data, qint64 maxSize)
{
qint64 bytesRead = LayeredStream::readData(data, maxSize);
+ if (bytesRead == -1) {
+ setErrorString(m_baseDevice->errorString());
+ return -1;
+ }
m_storedData.append(data, bytesRead);
diff --git a/src/streams/SymmetricCipherStream.cpp b/src/streams/SymmetricCipherStream.cpp
index a6f5b1f5..c713e522 100644
--- a/src/streams/SymmetricCipherStream.cpp
+++ b/src/streams/SymmetricCipherStream.cpp
@@ -18,13 +18,13 @@
#include "SymmetricCipherStream.h"
SymmetricCipherStream::SymmetricCipherStream(QIODevice* baseDevice, SymmetricCipher::Algorithm algo,
- SymmetricCipher::Mode mode, SymmetricCipher::Direction direction,
- const QByteArray& key, const QByteArray& iv)
+ SymmetricCipher::Mode mode, SymmetricCipher::Direction direction)
: LayeredStream(baseDevice)
- , m_cipher(new SymmetricCipher(algo, mode, direction, key, iv))
+ , m_cipher(new SymmetricCipher(algo, mode, direction))
, m_bufferPos(0)
, m_bufferFilling(false)
, m_error(false)
+ , m_isInitalized(false)
{
}
@@ -33,6 +33,25 @@ SymmetricCipherStream::~SymmetricCipherStream()
close();
}
+bool SymmetricCipherStream::init(const QByteArray& key, const QByteArray& iv)
+{
+ m_isInitalized = m_cipher->init(key, iv);
+ if (!m_isInitalized) {
+ setErrorString(m_cipher->errorString());
+ }
+
+ return m_isInitalized;
+}
+
+bool SymmetricCipherStream::open(QIODevice::OpenMode mode)
+{
+ if (!m_isInitalized) {
+ return false;
+ }
+
+ return LayeredStream::open(mode);
+}
+
bool SymmetricCipherStream::reset()
{
if (isWritable()) {
@@ -108,7 +127,11 @@ bool SymmetricCipherStream::readBlock()
return false;
}
else {
- m_cipher->processInPlace(m_buffer);
+ if (!m_cipher->processInPlace(m_buffer)) {
+ m_error = true;
+ setErrorString(m_cipher->errorString());
+ return false;
+ }
m_bufferPos = 0;
m_bufferFilling = false;
@@ -125,6 +148,7 @@ bool SymmetricCipherStream::readBlock()
else if (padLength > m_cipher->blockSize()) {
// invalid padding
m_error = true;
+ setErrorString("Invalid padding.");
return false;
}
else {
@@ -187,11 +211,15 @@ bool SymmetricCipherStream::writeBlock(bool lastBlock)
return true;
}
- m_cipher->processInPlace(m_buffer);
+ if (!m_cipher->processInPlace(m_buffer)) {
+ m_error = true;
+ setErrorString(m_cipher->errorString());
+ return false;
+ }
if (m_baseDevice->write(m_buffer) != m_buffer.size()) {
m_error = true;
- // TODO: copy error string
+ setErrorString(m_cipher->errorString());
return false;
}
else {
diff --git a/src/streams/SymmetricCipherStream.h b/src/streams/SymmetricCipherStream.h
index 53e252f2..c1865bab 100644
--- a/src/streams/SymmetricCipherStream.h
+++ b/src/streams/SymmetricCipherStream.h
@@ -29,11 +29,13 @@ class SymmetricCipherStream : public LayeredStream
Q_OBJECT
public:
- SymmetricCipherStream(QIODevice* baseDevice, SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
- SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv);
+ SymmetricCipherStream(QIODevice* baseDevice, SymmetricCipher::Algorithm algo,
+ SymmetricCipher::Mode mode, SymmetricCipher::Direction direction);
~SymmetricCipherStream();
- bool reset();
- void close();
+ bool init(const QByteArray& key, const QByteArray& iv);
+ bool open(QIODevice::OpenMode mode) Q_DECL_OVERRIDE;
+ bool reset() Q_DECL_OVERRIDE;
+ void close() Q_DECL_OVERRIDE;
protected:
qint64 readData(char* data, qint64 maxSize) Q_DECL_OVERRIDE;
@@ -48,6 +50,7 @@ private:
int m_bufferPos;
bool m_bufferFilling;
bool m_error;
+ bool m_isInitalized;
};
#endif // KEEPASSX_SYMMETRICCIPHERSTREAM_H
diff --git a/tests/TestHashedBlockStream.cpp b/tests/TestHashedBlockStream.cpp
index 09179fef..2a37d0aa 100644
--- a/tests/TestHashedBlockStream.cpp
+++ b/tests/TestHashedBlockStream.cpp
@@ -36,15 +36,15 @@ void TestHashedBlockStream::testWriteRead()
QByteArray data = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
QBuffer buffer;
- buffer.open(QIODevice::ReadWrite);
+ QVERIFY(buffer.open(QIODevice::ReadWrite));
HashedBlockStream writer(&buffer, 16);
- writer.open(QIODevice::WriteOnly);
+ QVERIFY(writer.open(QIODevice::WriteOnly));
HashedBlockStream reader(&buffer);
- reader.open(QIODevice::ReadOnly);
+ QVERIFY(reader.open(QIODevice::ReadOnly));
- writer.write(data.left(16));
+ QCOMPARE(writer.write(data.left(16)), qint64(16));
QVERIFY(writer.reset());
buffer.reset();
QCOMPARE(reader.read(17), data.left(16));
@@ -52,7 +52,7 @@ void TestHashedBlockStream::testWriteRead()
buffer.reset();
buffer.buffer().clear();
- writer.write(data.left(10));
+ QCOMPARE(writer.write(data.left(10)), qint64(10));
QVERIFY(writer.reset());
buffer.reset();
QCOMPARE(reader.read(5), data.left(5));
@@ -62,7 +62,7 @@ void TestHashedBlockStream::testWriteRead()
buffer.reset();
buffer.buffer().clear();
- writer.write(data.left(20));
+ QCOMPARE(writer.write(data.left(20)), qint64(20));
QVERIFY(writer.reset());
buffer.reset();
QCOMPARE(reader.read(20), data.left(20));
diff --git a/tests/TestKeePass2RandomStream.cpp b/tests/TestKeePass2RandomStream.cpp
index 7963e9af..cb881337 100644
--- a/tests/TestKeePass2RandomStream.cpp
+++ b/tests/TestKeePass2RandomStream.cpp
@@ -39,8 +39,8 @@ void TestKeePass2RandomStream::test()
const int Size = 128;
- SymmetricCipher cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt,
- CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV);
+ SymmetricCipher cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
+ QVERIFY(cipher.init(CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV));
const QByteArray data(QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c5"
"2b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6"
@@ -49,7 +49,7 @@ void TestKeePass2RandomStream::test()
QByteArray cipherPad;
cipherPad.fill('\0', Size);
- cipher.processInPlace(cipherPad);
+ QVERIFY(cipher.processInPlace(cipherPad));
QByteArray cipherData;
cipherData.resize(Size);
@@ -59,20 +59,27 @@ void TestKeePass2RandomStream::test()
}
- KeePass2RandomStream randomStream(key);
+ KeePass2RandomStream randomStream;
+ bool ok;
+ QVERIFY(randomStream.init(key));
QByteArray randomStreamData;
- randomStreamData.append(randomStream.process(data.mid(0, 7)));
- randomStreamData.append(randomStream.process(data.mid(7, 1)));
+ randomStreamData.append(randomStream.process(data.mid(0, 7), &ok));
+ QVERIFY(ok);
+ randomStreamData.append(randomStream.process(data.mid(7, 1), &ok));
+ QVERIFY(ok);
QByteArray tmpData = data.mid(8, 12);
- randomStream.processInPlace(tmpData);
+ QVERIFY(randomStream.processInPlace(tmpData));
randomStreamData.append(tmpData);
- randomStreamData.append(randomStream.process(data.mid(20, 44)));
- randomStreamData.append(randomStream.process(data.mid(64, 64)));
+ randomStreamData.append(randomStream.process(data.mid(20, 44), &ok));
+ QVERIFY(ok);
+ randomStreamData.append(randomStream.process(data.mid(64, 64), &ok));
+ QVERIFY(ok);
- SymmetricCipher cipherEncrypt(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt,
- CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV);
- QByteArray cipherDataEncrypt = cipherEncrypt.process(data);
+ SymmetricCipher cipherEncrypt(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
+ QVERIFY(cipherEncrypt.init(CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV));
+ QByteArray cipherDataEncrypt = cipherEncrypt.process(data, &ok);
+ QVERIFY(ok);
QCOMPARE(randomStreamData.size(), Size);
diff --git a/tests/TestKeys.cpp b/tests/TestKeys.cpp
index d6758d65..770af52d 100644
--- a/tests/TestKeys.cpp
+++ b/tests/TestKeys.cpp
@@ -43,6 +43,8 @@ void TestKeys::testComposite()
CompositeKey* compositeKey1 = new CompositeKey();
PasswordKey* passwordKey1 = new PasswordKey();
PasswordKey* passwordKey2 = new PasswordKey("test");
+ bool ok;
+ QString errorString;
// make sure that addKey() creates a copy of the keys
compositeKey1->addKey(*passwordKey1);
@@ -50,13 +52,15 @@ void TestKeys::testComposite()
delete passwordKey1;
delete passwordKey2;
- QByteArray transformed = compositeKey1->transform(QByteArray(32, '\0'), 1);
+ QByteArray transformed = compositeKey1->transform(QByteArray(32, '\0'), 1, &ok, &errorString);
+ QVERIFY(ok);
QCOMPARE(transformed.size(), 32);
// make sure the subkeys are copied
CompositeKey* compositeKey2 = compositeKey1->clone();
delete compositeKey1;
- QCOMPARE(compositeKey2->transform(QByteArray(32, '\0'), 1), transformed);
+ QCOMPARE(compositeKey2->transform(QByteArray(32, '\0'), 1, &ok, &errorString), transformed);
+ QVERIFY(ok);
delete compositeKey2;
CompositeKey* compositeKey3 = new CompositeKey();
@@ -130,7 +134,7 @@ void TestKeys::testCreateFileKey()
compositeKey.addKey(fileKey);
Database* dbOrg = new Database();
- dbOrg->setKey(compositeKey);
+ QVERIFY(dbOrg->setKey(compositeKey));
dbOrg->metadata()->setName(dbName);
QBuffer dbBuffer;
@@ -182,7 +186,10 @@ void TestKeys::benchmarkTransformKey()
QByteArray seed(32, '\x4B');
+ bool ok;
+ QString errorString;
+
QBENCHMARK {
- compositeKey.transform(seed, 1e6);
+ compositeKey.transform(seed, 1e6, &ok, &errorString);
}
}
diff --git a/tests/TestSymmetricCipher.cpp b/tests/TestSymmetricCipher.cpp
index 6d4e94f6..55a1bbe3 100644
--- a/tests/TestSymmetricCipher.cpp
+++ b/tests/TestSymmetricCipher.cpp
@@ -42,24 +42,27 @@ void TestSymmetricCipher::testAes256CbcEncryption()
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6");
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
+ bool ok;
- SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt,
- key, iv);
+ SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
+ QVERIFY(cipher.init(key, iv));
QCOMPARE(cipher.blockSize(), 16);
- QCOMPARE(cipher.process(plainText),
+ QCOMPARE(cipher.process(plainText, &ok),
cipherText);
+ QVERIFY(ok);
QBuffer buffer;
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
- SymmetricCipher::Encrypt, key, iv);
+ SymmetricCipher::Encrypt);
+ QVERIFY(stream.init(key, iv));
buffer.open(QIODevice::WriteOnly);
- stream.open(QIODevice::WriteOnly);
+ QVERIFY(stream.open(QIODevice::WriteOnly));
QVERIFY(stream.reset());
buffer.reset();
buffer.buffer().clear();
- stream.write(plainText.left(16));
+ QCOMPARE(stream.write(plainText.left(16)), qint64(16));
QCOMPARE(buffer.data(), cipherText.left(16));
QVERIFY(stream.reset());
// make sure padding is written
@@ -67,13 +70,13 @@ void TestSymmetricCipher::testAes256CbcEncryption()
buffer.reset();
buffer.buffer().clear();
- stream.write(plainText.left(10));
+ QCOMPARE(stream.write(plainText.left(10)), qint64(10));
QVERIFY(buffer.data().isEmpty());
QVERIFY(stream.reset());
buffer.reset();
buffer.buffer().clear();
- stream.write(plainText.left(10));
+ QCOMPARE(stream.write(plainText.left(10)), qint64(10));
stream.close();
QCOMPARE(buffer.data().size(), 16);
}
@@ -86,33 +89,37 @@ void TestSymmetricCipher::testAes256CbcDecryption()
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
+ bool ok;
- SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt, key, iv);
+ SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
+ QVERIFY(cipher.init(key, iv));
QCOMPARE(cipher.blockSize(), 16);
- QCOMPARE(cipher.process(cipherText),
+ QCOMPARE(cipher.process(cipherText, &ok),
plainText);
+ QVERIFY(ok);
// padded with 16 0x16 bytes
QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("3a3aa5e0213db1a9901f9036cf5102d2");
QBuffer buffer(&cipherTextPadded);
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
- SymmetricCipher::Decrypt, key, iv);
+ SymmetricCipher::Decrypt);
+ QVERIFY(stream.init(key, iv));
buffer.open(QIODevice::ReadOnly);
- stream.open(QIODevice::ReadOnly);
+ QVERIFY(stream.open(QIODevice::ReadOnly));
QCOMPARE(stream.read(10),
plainText.left(10));
buffer.reset();
- stream.reset();
+ QVERIFY(stream.reset());
QCOMPARE(stream.read(20),
plainText.left(20));
buffer.reset();
- stream.reset();
+ QVERIFY(stream.reset());
QCOMPARE(stream.read(16),
plainText.left(16));
buffer.reset();
- stream.reset();
+ QVERIFY(stream.reset());
QCOMPARE(stream.read(100),
plainText);
}
@@ -123,16 +130,20 @@ void TestSymmetricCipher::testSalsa20()
QByteArray key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112");
QByteArray iv = QByteArray::fromHex("0000000000000000");
+ bool ok;
- SymmetricCipher cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt, key, iv);
+ SymmetricCipher cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
+ QVERIFY(cipher.init(key, iv));
QByteArray cipherTextA;
for (int i = 0; i < 8; i++) {
- cipherTextA.append(cipher.process(QByteArray(64, '\0')));
+ cipherTextA.append(cipher.process(QByteArray(64, '\0'), &ok));
+ QVERIFY(ok);
}
cipher.reset();
- QByteArray cipherTextB = cipher.process(QByteArray(512, '\0'));
+ QByteArray cipherTextB = cipher.process(QByteArray(512, '\0'), &ok);
+ QVERIFY(ok);
cipher.reset();
QByteArray expectedCipherText1;
@@ -180,7 +191,8 @@ void TestSymmetricCipher::testPadding()
buffer.open(QIODevice::ReadWrite);
SymmetricCipherStream streamEnc(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
- SymmetricCipher::Encrypt, key, iv);
+ SymmetricCipher::Encrypt);
+ QVERIFY(streamEnc.init(key, iv));
streamEnc.open(QIODevice::WriteOnly);
streamEnc.write(plainText);
streamEnc.close();
@@ -189,7 +201,8 @@ void TestSymmetricCipher::testPadding()
QCOMPARE(buffer.buffer().size(), 16);
SymmetricCipherStream streamDec(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
- SymmetricCipher::Decrypt, key, iv);
+ SymmetricCipher::Decrypt);
+ QVERIFY(streamDec.init(key, iv));
streamDec.open(QIODevice::ReadOnly);
QByteArray decrypted = streamDec.readAll();
QCOMPARE(decrypted, plainText);