|
QtPass 2.0.0
Developer documentation
|
Static helpers that have no better home: setting up the process and the Git environment the way pass does, finding binaries and the default store, path and URL handling, and file operations that refuse to follow links. More...
#include <util.h>
Public Types | |
| using | Filler = std::function<QString(QFileDevice &)> |
| Fills the temporary of stageFileReplacing() through its open handle; returns why it could not (translated, for the user), or an empty string. | |
Static Public Member Functions | |
| static void | applyPassProcessDefaults () |
| Set up the process the way pass sets up its own, before anything runs git or gpg or writes into the store. | |
| static auto | gitCeiling (const QString &store) -> QString |
| GIT_CEILING_DIRECTORIES for a store, as pass sets it. | |
| static auto | repositoryPassClimbsTo (const QString &store) -> QString |
The repository above store that pass itself would commit to, if any. | |
| static auto | gitStoreVariables (const QString &store, const QString &git={}, std::optional< uint > asUser=std::nullopt) -> QList< std::pair< QString, QString > > |
The variables that keep git in store's own repository, never one above it: GIT_CEILING_DIRECTORIES (gitCeiling()), and, when that ceiling holds git's list separator (":", ";" on Windows) and so cannot be one entry of the list, GIT_DIR and GIT_WORK_TREE naming the store's own .git and the store. A store without a .git then has no repository to git, rather than the one above it. | |
| static void | setGitOwnerForTests (std::optional< uint > user) |
For tests: the user gitStoreVariables() compares owners with when no asUser is given, in place of the effective user. A file owned by another user cannot be made without root. | |
| static auto | mkpathForStore (const QString &path) -> bool |
Create path and any missing parents as pass would. | |
| static auto | passUmask (const QString &value) -> int |
The umask pass would use for value of PASSWORD_STORE_UMASK. | |
| static auto | findBinaryInPath (const QString &binary) -> QString |
| Locate an executable by searching the process PATH and (on Windows) falling back to WSL. | |
| static auto | findBinaryInPath (const QString &binary, const QStringList &searchPaths) -> QString |
| Locate an executable in an explicit list of directories. | |
| static auto | findPasswordStore () -> QString |
| Locate the password store directory. | |
| static auto | expandTilde (const QString &path) -> QString |
| Expand a leading current-user tilde in a path. | |
| static auto | normalizeFolderPath (const QString &path) -> QString |
| Ensure a folder path always ends with '/'. | |
| static auto | configIsValid (const AppSettings &s) -> bool |
| Verify that the required configuration is complete. | |
| static auto | endsWithGpg () -> const QRegularExpression & |
| Returns a regex to match .gpg file extensions. | |
| static auto | protocolRegex () -> const QRegularExpression & |
| Returns a regex to match URL protocols. | |
| static auto | isLaunchableWebUrl (const QString &value) -> bool |
| Check whether a value is a safe, launchable web URL. | |
| static auto | linkifyUrls (const QString &text, bool *linked=nullptr) -> QString |
| Turn plain text into HTML with clickable links for safe web URLs. | |
| static auto | newLinesRegex () -> const QRegularExpression & |
| Returns a regex to match newline characters. | |
| static auto | isValidKeyId (const QString &keyId) -> bool |
| Check whether a .gpg-id token may be passed to gpg as a key selector. Accepts any non-empty token that does not start with -, exactly as pass does: key IDs and fingerprints of every version, emails, =Exact
User ID, plain name substrings and the @, /, #, & routing prefixes. | |
| static auto | regularFilesUnder (const QString &dir, const QStringList &nameFilters, QStringList *skipped=nullptr, bool hiddenFiles=false) -> QStringList |
Regular files under dir whose name matches one of nameFilters, found by walking only real directories. | |
| static auto | directoriesUnder (const QString &dir) -> QStringList |
Every real, visible directory under dir, walked by the same rules as regularFilesUnder(): links, junctions and hidden directories are neither listed nor entered. | |
| static auto | isLinkedFolder (const QString &path) -> bool |
Whether path names a symbolic link or NTFS junction, with or without a trailing separator (which would otherwise make the check look at the target instead). Folder or file: a linked .gpg, .gpg-id or .gpg-id.sig is judged the same way. | |
| static auto | isUnderLink (const QString &path, const QString &storeRoot, bool includeSelf=true) -> bool |
Whether the way from storeRoot down to path passes through a symbolic link or NTFS junction: path itself or any folder between. | |
| static auto | removeTree (const QString &dir) -> bool |
Remove dir and everything in it, the way rm -rf does: a symbolic link or NTFS junction, dir itself or anything inside, is removed as an entry and what it points to is never entered. A trailing separator on dir does not change that. | |
| static auto | replaceFile (const QString &from, const QString &to, bool replace) -> bool |
Give the file at from the name to, atomically and without following anything: the operating system's rename (rename(2), MoveFileEx), never QFile::rename(), whose fallback when the plain rename fails is to copy by opening to for writing, through whatever link sits under that name by then. With replace, whatever entry is at to (a file, a link) is replaced as an entry; without it, the call fails when anything is there (linkat(2) without AT_SYMLINK_FOLLOW and unlink on POSIX, where a no-replace rename is not portable; plain link(2) follows a symlink at from on macOS and the BSDs). | |
| static auto | openRegularFile (const QString &path, QFile &file) -> bool |
Open path for reading as the regular file it is, not as what a link under that name points to: the object opened is the object judged. A check of the name before an open by name leaves a window in which a co-writer of the store makes the name a link; opening without following (O_NOFOLLOW; on Windows the reparse point itself) and judging the open handle closes it. A directory, a link, a FIFO or a device is refused. | |
| static auto | readRegularFile (const QString &path, qint64 maxBytes) -> std::optional< QByteArray > |
Read path whole, as the regular file it is (openRegularFile()), when it holds at most maxBytes. | |
| static auto | stageFileReplacing (const QString &path, bool replace, const Filler &fill, QString *error=nullptr) -> bool |
The staged write every file QtPass puts into a store goes through: a temporary created next to path (an opaque name, exclusive, owner-only), filled by fill through its open handle, synced, then replaceFile()d under the name, and the object under the name afterwards checked to be the very file that was filled (opened without following, compared by device and inode). The name is never opened for writing, so a link a co-writer plants under it between the caller's check and the write is replaced as an entry rather than written through (QSaveFile resolves such a link at open); a file swapped under the temporary's name before the rename (a hard link to something of the user's, say) is caught by the comparison and reported, and what is under the name is left (removing it by name could take another writer's file that landed there since). Where the system cannot give both identities the write stands: on FAT and exFAT a rename moves the file ID, and those filesystems have no hard links to swap in either. | |
| static auto | writeFileReplacing (const QString &path, const QByteArray &bytes, bool replace, QString *error=nullptr) -> bool |
Write bytes as the file path through stageFileReplacing(). | |
| static auto | copyFileReplacing (const QString &src, const QString &dst, bool replace, QString *error=nullptr) -> bool |
Copy the regular file src to the name dst through stageFileReplacing(): src is read through a handle opened without following (openRegularFile()). The copy is owner-only whatever src's mode, as pass's cp under its umask 077 makes it. | |
| static auto | syncToDisk (QFileDevice &file) -> bool |
Flush file's bytes to the device (fsync, FlushFileBuffers) before it is renamed into place, so that a crash right after the rename does not leave the name pointing at an empty file. QSaveFile did this on commit; the staged writes here do it themselves. | |
Static Public Attributes | |
| static constexpr qint64 | kMaxGpgIdBytes = 1024 * 1024 |
| Most bytes of a .gpg-id QtPass reads. | |
Static Private Member Functions | |
| static void | initialiseEnvironment () |
| Fill _env once: the process environment with the usual GPG install directories appended to PATH (MacGPG2 and /usr/local/bin on macOS, WinGPG and GnuPG on Windows) when they exist. | |
Static Private Attributes | |
| static QProcessEnvironment | _env |
| static bool | _envInitialised = false |
| Whether initialiseEnvironment() has run. | |
Static helpers that have no better home: setting up the process and the Git environment the way pass does, finding binaries and the default store, path and URL handling, and file operations that refuse to follow links.
Nothing here keeps state apart from the cached process environment, and nothing depends on a widget or a model. Store-boundary checks live in PathValidator, SSH agent discovery in SshAuthSock and template files in TemplateIO.
| using Util::Filler = std::function<QString(QFileDevice &)> |
Fills the temporary of stageFileReplacing() through its open handle; returns why it could not (translated, for the user), or an empty string.
|
static |
Set up the process the way pass sets up its own, before anything runs git or gpg or writes into the store.
Clears the variables that point git at another repository (GIT_DIR, GIT_WORK_TREE and the rest pass clears), and on Unix gives the processes QtPass starts (git, gpg, pass) the umask from PASSWORD_STORE_UMASK, 077 by default (Executor::setChildUmask()): what they create in the store is then the user's alone, as with pass. QtPass's own umask stays the session's, so a browser or file manager it opens is not affected. Call it first thing in main().
Definition at line 106 of file util.cpp.
|
static |
Verify that the required configuration is complete.
| s | Application settings snapshot (passStore, usePass, executables). |
Definition at line 556 of file util.cpp.
|
static |
Copy the regular file src to the name dst through stageFileReplacing(): src is read through a handle opened without following (openRegularFile()). The copy is owner-only whatever src's mode, as pass's cp under its umask 077 makes it.
| src | The file to copy; a link, a directory or a special file under that name is refused. |
| dst | The name to give the copy. |
| replace | Whether an existing entry under dst may go. |
| error | Receives why not, if not null. |
dst now holds a copy of src. Definition at line 1179 of file util.cpp.
|
static |
Every real, visible directory under dir, walked by the same rules as regularFilesUnder(): links, junctions and hidden directories are neither listed nor entered.
| dir | Directory to walk. |
Definition at line 749 of file util.cpp.
|
static |
|
static |
Expand a leading current-user tilde in a path.
Environment variables set outside a shell (systemd units, .desktop entries, quoted assignments) keep a literal "~", which no filesystem call resolves. "~" and "~/..." become the home directory; "~user" forms and everything else are returned unchanged.
| path | Path that may start with a tilde. |
Definition at line 88 of file util.cpp.
|
static |
Locate an executable by searching the process PATH and (on Windows) falling back to WSL.
| binary | Executable name or relative path to locate (e.g., "gpg" or "pass"). |
Definition at line 474 of file util.cpp.
|
static |
Locate an executable in an explicit list of directories.
Only regular, executable files match; a directory named like the binary is skipped. Empty entries are ignored (they never resolve to the current working directory) and an empty list finds nothing. Only bare names are searched: an absolute path, or a name containing a directory separator, finds nothing rather than escaping searchPaths.
| binary | Executable name to locate (no directory separators). |
| searchPaths | Directories to search, in order. |
|
static |
|
static |
GIT_CEILING_DIRECTORIES for a store, as pass sets it.
The store's parent, so git never takes a repository above the store for the store's own. git compares ceilings with its real working directory, so this is the real parent when the store path is a link; a store that does not exist yet gets the parent of its absolute path.
| store | The store's path. |
store. Definition at line 135 of file util.cpp.
|
static |
The variables that keep git in store's own repository, never one above it: GIT_CEILING_DIRECTORIES (gitCeiling()), and, when that ceiling holds git's list separator (":", ";" on Windows) and so cannot be one entry of the list, GIT_DIR and GIT_WORK_TREE naming the store's own .git and the store. A store without a .git then has no repository to git, rather than the one above it.
An explicit GIT_DIR skips git's ownership check, which runs only when git discovers a repository. On Unix the store is therefore pinned only when git's check would pass: it, its .git and a gitfile's target are the user's own, or git's safe.directory names the store (as git reads it: only from system, global and command-line configuration, never a repository's own; *, an exact path, or a path ending in a slash and * for what lies below it, an empty value clearing the entries before it). Otherwise GIT_DIR names a path that cannot exist, and git refuses rather than run another user's repository and hooks.
| store | The store's path. |
| git | The git executable that reads safe.directory; "git" from PATH when empty. Run only for a store that is not the user's own. |
| asUser | The user to compare owners with; the effective user when not given (a test passes another). |
store. Definition at line 376 of file util.cpp.
|
staticprivate |
|
static |
Check whether a value is a safe, launchable web URL.
Stricter than protocolRegex(): intended to gate an "open in browser" action, where launching a non-web scheme would be a security risk. Returns true only when the trimmed value:
Deliberately rejects file://, javascript:, data:, ftp/ssh/webdav and scheme-less inputs (e.g. "www.example.com").
| value | Candidate URL string (typically a password-file field). |
Definition at line 596 of file util.cpp.
|
static |
Whether path names a symbolic link or NTFS junction, with or without a trailing separator (which would otherwise make the check look at the target instead). Folder or file: a linked .gpg, .gpg-id or .gpg-id.sig is judged the same way.
The store tree shows linked entries like any other; an operation on one (show, edit, re-encrypt, recipients, delete) must not reach through it.
| path | Path as the tree or a caller names it. |
Definition at line 761 of file util.cpp.
|
static |
Whether the way from storeRoot down to path passes through a symbolic link or NTFS junction: path itself or any folder between.
The tree shows a linked folder's children too, and a pick of one of them names a place outside the store as much as the link does. The root itself is not checked: a linked store root is the user's setup.
| path | File or folder under the store, as the tree names it. |
| storeRoot | The configured store. |
| includeSelf | Whether path itself counts; false for a removal, which unlinks a link and touches nothing behind it. |
Definition at line 765 of file util.cpp.
|
static |
Check whether a .gpg-id token may be passed to gpg as a key selector. Accepts any non-empty token that does not start with -, exactly as pass does: key IDs and fingerprints of every version, emails, =Exact User ID, plain name substrings and the @, /, #, & routing prefixes.
| keyId | The string to validate. |
Definition at line 664 of file util.cpp.
|
static |
Turn plain text into HTML with clickable links for safe web URLs.
The single rule for every place a value is rendered into an <a href>: the text is HTML-escaped, and only the protocolRegex() matches that also pass isLaunchableWebUrl() become anchors. Everything else (ssh://, ftp://, URLs carrying user:pass@ credentials, ...) stays escaped plain text, so a click can never hand a non-web scheme or a secret to the OS URL handler via QTextBrowser::setOpenExternalLinks().
| text | Plain (unescaped) text, possibly containing URLs. |
| linked | Optional out parameter, set to true when at least one anchor was produced. |
Definition at line 627 of file util.cpp.
|
static |
Create path and any missing parents as pass would.
What pass's mkdir -p does under its umask, for the folders QtPass makes in the store itself: on Unix the mode is 0777 less the store umask (Executor::childUmask(), from PASSWORD_STORE_UMASK, 077 by default and when none is set), whatever QtPass's own umask. Existing folders are left as they are.
| path | The folder to create. |
path is a folder afterwards. Definition at line 438 of file util.cpp.
|
static |
|
static |
Ensure a folder path always ends with '/'.
Qt normalises paths to forward slashes internally, so this function appends '/' unconditionally rather than the platform-native separator. Callers that need native separators can call QDir::toNativeSeparators() themselves.
| path | The folder path to normalize. |
Definition at line 98 of file util.cpp.
|
static |
Open path for reading as the regular file it is, not as what a link under that name points to: the object opened is the object judged. A check of the name before an open by name leaves a window in which a co-writer of the store makes the name a link; opening without following (O_NOFOLLOW; on Windows the reparse point itself) and judging the open handle closes it. A directory, a link, a FIFO or a device is refused.
| path | The file, as the caller names it. |
| file | Receives the open file on success; untouched otherwise. |
file is open on a regular file. Definition at line 913 of file util.cpp.
|
static |
|
static |
|
static |
Read path whole, as the regular file it is (openRegularFile()), when it holds at most maxBytes.
| path | The file. |
| maxBytes | The most bytes accepted. |
Definition at line 973 of file util.cpp.
|
static |
Regular files under dir whose name matches one of nameFilters, found by walking only real directories.
Symbolic links and NTFS junctions are never entered and never listed: either can point outside the tree, and QDirIterator's own recursion treats a junction as an ordinary directory (Qt marks it JunctionType, not LinkType, so QDir::NoSymLinks does not see it). Hidden directories (.git, a sync tool's version folder) are not entered either, as with QDirIterator without QDir::Hidden.
dir itself is followed whatever it is: the configured store root is commonly a symlink (~/.password-store to a synced folder), and it is the user's choice, not something found inside the store. A caller that gets its folder from the store tree decides with isLinkedFolder() first.
The walk is a listing, not a lock: what is a regular file when listed can be replaced before it is opened. That is the store's trust boundary (SECURITY.md), not something a walk can close.
| dir | Directory to walk. |
| nameFilters | Wildcards, as for QDir::setNameFilters(). |
| skipped | Receives what was left out although it is in the way: every linked directory, and every link or special file (FIFO, socket, device) whose name matches nameFilters. Null to not care. |
| hiddenFiles | List hidden files too (.gpg-id); hidden directories stay closed regardless. |
Definition at line 721 of file util.cpp.
|
static |
Remove dir and everything in it, the way rm -rf does: a symbolic link or NTFS junction, dir itself or anything inside, is removed as an entry and what it points to is never entered. A trailing separator on dir does not change that.
QDir::removeRecursively() descends into junctions (it stops only at what Qt calls a symlink) and would empty the junction's target.
| dir | Directory to remove. |
dir is gone. Definition at line 1239 of file util.cpp.
|
static |
Give the file at from the name to, atomically and without following anything: the operating system's rename (rename(2), MoveFileEx), never QFile::rename(), whose fallback when the plain rename fails is to copy by opening to for writing, through whatever link sits under that name by then. With replace, whatever entry is at to (a file, a link) is replaced as an entry; without it, the call fails when anything is there (linkat(2) without AT_SYMLINK_FOLLOW and unlink on POSIX, where a no-replace rename is not portable; plain link(2) follows a symlink at from on macOS and the BSDs).
| from | An existing regular file, on the same filesystem as to. |
| to | The name to give it. |
| replace | Whether an existing entry at to may go. |
to is now that file. Definition at line 873 of file util.cpp.
|
static |
The repository above store that pass itself would commit to, if any.
pass fences git in with GIT_CEILING_DIRECTORIES="$PREFIX/..", spelled as the store path is given, and unsets GIT_DIR and GIT_WORK_TREE. git splits that list on ":", so a colon anywhere in the path (above the store or in its own name) leaves no ceiling at the parent; a piece can still be a ceiling further up, which git keeps to, and so does this. A store without its own .git then finds a repository above it, and pass adds and commits there on every change, whether or not QtPass uses Git. QtPass cannot fence pass in from outside, so this is for a warning. Always empty on Windows, where pass runs in WSL.
| store | The store's path, as PASSWORD_STORE_DIR gets it. |
Definition at line 146 of file util.cpp.
|
static |
For tests: the user gitStoreVariables() compares owners with when no asUser is given, in place of the effective user. A file owned by another user cannot be made without root.
| user | The user, or nothing to go back to the effective user. |
|
static |
The staged write every file QtPass puts into a store goes through: a temporary created next to path (an opaque name, exclusive, owner-only), filled by fill through its open handle, synced, then replaceFile()d under the name, and the object under the name afterwards checked to be the very file that was filled (opened without following, compared by device and inode). The name is never opened for writing, so a link a co-writer plants under it between the caller's check and the write is replaced as an entry rather than written through (QSaveFile resolves such a link at open); a file swapped under the temporary's name before the rename (a hard link to something of the user's, say) is caught by the comparison and reported, and what is under the name is left (removing it by name could take another writer's file that landed there since). Where the system cannot give both identities the write stands: on FAT and exFAT a rename moves the file ID, and those filesystems have no hard links to swap in either.
| path | The file to write. |
| replace | Whether an existing entry under the name may go. |
| fill | Writes the contents. |
| error | Receives why not, if not null. |
path now holds what fill wrote. Definition at line 1142 of file util.cpp.
|
static |
Flush file's bytes to the device (fsync, FlushFileBuffers) before it is renamed into place, so that a crash right after the rename does not leave the name pointing at an empty file. QSaveFile did this on commit; the staged writes here do it themselves.
| file | An open file. |
|
static |
Write bytes as the file path through stageFileReplacing().
| path | The file to write. |
| bytes | Its contents. |
| replace | Whether an existing entry under the name may go. |
| error | Receives why not, if not null. |
path now holds bytes. Definition at line 1165 of file util.cpp.
|
staticprivate |
The environment findBinaryInPath() and findPasswordStore() read, as initialiseEnvironment() built it.
|
staticprivate |
Whether initialiseEnvironment() has run.
|
staticconstexpr |