14#include <QRegularExpression>
16#include <QStringConverter>
24constexpr qint64 kMaxTemplateFileBytes = 64 * 1024;
35auto readTemplateFile(
const QString &path) -> std::optional<QString> {
40 const QByteArray bytes = file.read(kMaxTemplateFileBytes + 1);
41 if (bytes.size() > kMaxTemplateFileBytes) {
42 qWarning() <<
"Ignoring" << path <<
"; a template file is at most"
43 << kMaxTemplateFileBytes <<
"bytes";
48 const QByteArray bom(
"\xEF\xBB\xBF");
49 return QString::fromUtf8(bytes.startsWith(bom) ? bytes.mid(bom.size())
55auto sectionHeader(
const QString &line) -> std::optional<QString> {
56 if (!line.startsWith(
'[') || !line.endsWith(
']')) {
59 return line.mid(1, line.length() - 2).trimmed();
63auto defaultTemplateIn(
const QDir &dir) -> QString {
64 const std::optional<QString> contents =
65 readTemplateFile(dir.filePath(
".default_template"));
69 const QString name = contents->section(QLatin1Char(
'\n'), 0, 0).trimmed();
70 return name.startsWith(
'#') ? QString() : name;
76 -> QHash<QString, QStringList> {
77 QHash<QString, QStringList> result;
78 std::optional<QString> contents =
79 readTemplateFile(QDir(storePath).filePath(
".templates"));
83 QTextStream in(&*contents, QIODevice::ReadOnly);
86 std::optional<QString> section;
88 const auto flush = [&] {
90 result.insert(*section, fields);
95 const QString line = in.readLine().trimmed();
96 if (
const std::optional<QString> header = sectionHeader(line)) {
98 section = header->isEmpty() ? std::nullopt : header;
101 <<
"Empty template section in .templates file, ignoring fields";
103 }
else if (section && !line.isEmpty() && !line.startsWith(
'#')) {
112 static const QRegularExpression marker(
113 QStringLiteral(
"^#\\s*secret\\s*:(.*)$"),
114 QRegularExpression::CaseInsensitiveOption);
116 const QStringList lines = templateText.split(QLatin1Char(
'\n'));
117 for (
const QString &line : lines) {
118 const QRegularExpressionMatch match = marker.match(line.trimmed());
119 if (!match.hasMatch()) {
122 const QStringList listed = match.captured(1).split(QLatin1Char(
','));
123 for (
const QString &name : listed) {
124 const QString trimmed = name.trimmed();
125 if (!trimmed.isEmpty()) {
134 const std::optional<QString> contents =
135 readTemplateFile(QDir(storePath).filePath(
".templates"));
140 QStringList fields = templateText.split(QLatin1Char(
'\n'));
141 fields.removeIf([](
const QString &line) {
142 return line.trimmed().startsWith(QLatin1Char(
'#'));
148 const QHash<QString, QStringList> &templates)
150 QSaveFile saveFile(QDir(storePath).filePath(
".templates"));
151 if (!saveFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
154 QTextStream out(&saveFile);
155 out.setEncoding(QStringConverter::Utf8);
156 out <<
"# QtPass templates configuration\n";
157 out <<
"# Format: INI-style with [template_name] sections,\n";
158 out <<
"# followed by field names (one per line)\n\n";
160 QStringList sortedKeys = templates.keys();
161 std::sort(sortedKeys.begin(), sortedKeys.end());
162 for (
const QString &key : sortedKeys) {
163 out <<
"[" << key <<
"]\n";
164 for (
const QString &field : templates.value(key)) {
165 out << field <<
"\n";
170 if (out.status() != QTextStream::Ok) {
173 return saveFile.commit();
177 const QString &storePath) -> QString {
178 const QString store = QDir::cleanPath(QDir(storePath).absolutePath());
181 const auto inStore = [&store](
const QString &path) {
182 return path == store ||
183 (path.startsWith(store) && path.length() > store.length() &&
184 path.at(store.length()) == QChar(
'/'));
186 QDir dir(folderPath);
187 while (inStore(QDir::cleanPath(dir.absolutePath()))) {
188 const QString name = defaultTemplateIn(dir);
189 if (!name.isEmpty()) {
192 if (QDir::cleanPath(dir.absolutePath()) == store || !dir.cdUp()) {
static auto writeTemplates(const QString &storePath, const QHash< QString, QStringList > &templates) -> bool
Write templates to .templates file in password store.
static auto readTemplates(const QString &storePath) -> QHash< QString, QStringList >
Read templates from .templates file in password store.
static auto fieldsIn(const QString &templateText) -> QStringList
The field names of the template in the settings: one per line, with comment lines (starting with #,...
static auto getFolderTemplate(const QString &folderPath, const QString &storePath) -> QString
Get default template for a folder. Looks in folder, then parent folders up to root.
static auto readSecretFields(const QString &storePath) -> QStringList
The field names the store's .templates file marks as secret, in any of its sections (see secretFields...
static auto secretFieldsIn(const QString &templateText) -> QStringList
The field names a template marks as secret.
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 ob...
TemplateIO: the store's .templates and .default_template files and the # secret: marker in template t...
Util: static helpers for the process environment, binary discovery, store paths, URL safety and the l...