• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IJHack / QtPass / 24939776555

25 Apr 2026 08:18PM UTC coverage: 28.037% (+0.4%) from 27.609%
24939776555

Pull #1170

github

web-flow
Merge ebe5c61e3 into 9048d2bb3
Pull Request #1170: feat: add Import key dialog to UsersDialog for issue #1167

25 of 110 new or added lines in 3 files covered. (22.73%)

36 existing lines in 2 files now uncovered.

1805 of 6438 relevant lines covered (28.04%)

27.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

29.76
/src/importkeydialog.cpp
1
// SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
2
// SPDX-License-Identifier: GPL-3.0-or-later
3
#include "importkeydialog.h"
4

5
#include "executor.h"
6
#include "qtpasssettings.h"
7
#include "ui_importkeydialog.h"
8

9
#include <QApplication>
10
#include <QClipboard>
11
#include <QFile>
12
#include <QFileDialog>
13
#include <QMessageBox>
14
#include <QPlainTextEdit>
15
#include <QRegularExpression>
16
#include <QString>
17

18
// Locale-independent: gpg's machine-readable status output via --status-fd 1.
19
// See doc/DETAILS in the GnuPG source for IMPORT_OK / IMPORTED grammar.
20
static const QRegularExpression
21
    IMPORT_OK_RE(QStringLiteral(R"(\[GNUPG:\] IMPORT_OK \d+ ([0-9A-Fa-f]+))"));
22
static const QRegularExpression
23
    IMPORTED_RE(QStringLiteral(R"(\[GNUPG:\] IMPORTED ([0-9A-Fa-f]+))"));
24
// Fallback for the human-readable (English-locale) line.
25
static const QRegularExpression KEY_IMPORTED_FALLBACK(
26
    QStringLiteral(R"(gpg: key ([0-9A-Fa-f]+):.*imported)"));
27

28
ImportKeyDialog::ImportKeyDialog(QWidget *parent)
2✔
29
    : QDialog(parent), ui(new Ui::ImportKeyDialog) {
2✔
30
  ui->setupUi(this);
2✔
31
  ui->importButton->setEnabled(false);
2✔
32
}
2✔
33

34
ImportKeyDialog::~ImportKeyDialog() = default;
2✔
35

36
auto ImportKeyDialog::importedKeyId() const -> QString {
1✔
37
  return m_importedKeyId;
1✔
38
}
39

NEW
40
void ImportKeyDialog::on_fileButton_clicked() {
×
41
  const QString fileName = QFileDialog::getOpenFileName(
NEW
42
      this, tr("Import GPG Key"), QString(),
×
NEW
43
      tr("ASCII-armored GPG key") + " (*.asc);;" + tr("All Files") + " (*)");
×
44

NEW
45
  if (fileName.isEmpty()) {
×
46
    return;
47
  }
48

NEW
49
  QFile file(fileName);
×
NEW
50
  if (!file.open(QIODevice::ReadOnly)) {
×
NEW
51
    QMessageBox::warning(this, tr("Import Key"),
×
NEW
52
                         tr("Could not open file: %1").arg(fileName));
×
NEW
53
    return;
×
54
  }
55

NEW
56
  const QByteArray bytes = file.readAll();
×
NEW
57
  file.close();
×
58

59
  // Only ASCII-armored content is shown in the text edit; binary keyrings
60
  // would lose bytes through UTF-8 conversion. Reject anything that doesn't
61
  // start with the PGP armor header.
NEW
62
  if (!bytes.trimmed().startsWith("-----BEGIN PGP")) {
×
NEW
63
    QMessageBox::warning(
×
NEW
64
        this, tr("Import Key"),
×
NEW
65
        tr("%1 does not look like an ASCII-armored GPG key. Convert it with "
×
66
           "<code>gpg --armor --export</code> first, or paste the armored "
67
           "block via <b>From Clipboard</b>.")
NEW
68
            .arg(fileName));
×
69
    return;
70
  }
71

NEW
72
  ui->inputTextEdit->setPlainText(QString::fromUtf8(bytes));
×
NEW
73
}
×
74

NEW
75
void ImportKeyDialog::on_pasteButton_clicked() {
×
NEW
76
  const QClipboard *clipboard = QApplication::clipboard();
×
NEW
77
  const QString text = clipboard->text();
×
NEW
78
  if (text.isEmpty()) {
×
79
    // Don't wipe whatever the user already typed/loaded for an empty
80
    // clipboard.
81
    return;
82
  }
NEW
83
  ui->inputTextEdit->setPlainText(text);
×
84
}
85

NEW
86
void ImportKeyDialog::on_importButton_clicked() {
×
NEW
87
  const QString input = ui->inputTextEdit->toPlainText().trimmed();
×
NEW
88
  if (input.isEmpty()) {
×
89
    return;
90
  }
91

NEW
92
  if (importFromString(input)) {
×
NEW
93
    accept();
×
94
  }
95
}
96

NEW
97
void ImportKeyDialog::on_inputTextEdit_textChanged() {
×
NEW
98
  const bool hasInput = !ui->inputTextEdit->toPlainText().trimmed().isEmpty();
×
NEW
99
  ui->importButton->setEnabled(hasInput);
×
NEW
100
}
×
101

NEW
102
bool ImportKeyDialog::importFromString(const QString &input) {
×
NEW
103
  QString gpgExe = QtPassSettings::getGpgExecutable();
×
NEW
104
  if (gpgExe.isEmpty()) {
×
NEW
105
    gpgExe = "gpg";
×
106
  }
NEW
107
  QStringList args = {"--status-fd", "1", "--import", "--batch", "--yes"};
×
108

NEW
109
  QString stdOut;
×
NEW
110
  QString stdErr;
×
111

NEW
112
  int result = Executor::executeBlocking(gpgExe, args, input, &stdOut, &stdErr);
×
113

NEW
114
  if (result != 0) {
×
NEW
115
    showError(tr("GPG import failed:\n%1").arg(stdErr));
×
NEW
116
    return false;
×
117
  }
118

NEW
119
  QString keyId = parseGpgImportOutput(stdOut);
×
NEW
120
  if (keyId.isEmpty()) {
×
NEW
121
    keyId = parseGpgImportOutput(stdErr);
×
122
  }
123

NEW
124
  if (keyId.isEmpty()) {
×
NEW
125
    showError(tr("Could not parse imported key id from GPG output."));
×
NEW
126
    return false;
×
127
  }
128

NEW
129
  m_importedKeyId = keyId;
×
NEW
130
  showSuccess(keyId);
×
131
  return true;
NEW
132
}
×
133

134
auto ImportKeyDialog::parseGpgImportOutput(const QString &output) -> QString {
12✔
135
  const QStringList lines = output.split('\n', Qt::SkipEmptyParts);
12✔
136
  // Prefer locale-independent status output. IMPORT_OK gives a full
137
  // fingerprint; IMPORTED gives a (long) key id. Try both before falling
138
  // back to the English-only human-readable line.
139
  for (const QString &line : lines) {
20✔
140
    const QRegularExpressionMatch match = IMPORT_OK_RE.match(line);
14✔
141
    if (match.hasMatch()) {
14✔
142
      return match.captured(1);
6✔
143
    }
144
  }
14✔
145
  for (const QString &line : lines) {
10✔
146
    const QRegularExpressionMatch match = IMPORTED_RE.match(line);
5✔
147
    if (match.hasMatch()) {
5✔
148
      return match.captured(1);
1✔
149
    }
150
  }
5✔
151
  for (const QString &line : lines) {
7✔
152
    const QRegularExpressionMatch match = KEY_IMPORTED_FALLBACK.match(line);
4✔
153
    if (match.hasMatch()) {
4✔
154
      return match.captured(1);
2✔
155
    }
156
  }
4✔
157
  return QString();
158
}
159

NEW
160
void ImportKeyDialog::showError(const QString &message) {
×
NEW
161
  QMessageBox::warning(this, tr("Import Key"), message);
×
NEW
162
}
×
163

NEW
164
void ImportKeyDialog::showSuccess(const QString &keyId) {
×
NEW
165
  QMessageBox::information(this, tr("Import Key"),
×
NEW
166
                           tr("Successfully imported key: %1").arg(keyId));
×
NEW
167
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc