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

randombit / botan / 39195077976

18 Oct 2026 05:18PM UTC coverage: 92.258% (+0.06%) from 92.195%
39195077976

push

github

randombit
Update release notes [ci skip]

137358 of 148884 relevant lines covered (92.26%)

8967647.39 hits per line

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

76.38
/src/cli/cli.cpp
1
/*
2
* (C) 2015,2017 Jack Lloyd
3
*
4
* Botan is released under the Simplified BSD License (see license.txt)
5
*/
6

7
#include "cli.h"
8
#include "argparse.h"
9
#include <botan/assert.h>
10
#include <botan/rng.h>
11
#include <botan/internal/target_info.h>
12
#include <cstdio>
13
#include <fstream>
14
#include <iostream>
15

16
#if defined(BOTAN_HAS_HEX_CODEC)
17
   #include <botan/hex.h>
18
#endif
19

20
#if defined(BOTAN_HAS_BASE64_CODEC)
21
   #include <botan/base64.h>
22
#endif
23

24
#if defined(BOTAN_HAS_BASE58_CODEC)
25
   #include <botan/base58.h>
26
#endif
27

28
#if defined(BOTAN_HAS_OS_UTILS)
29
   #include <botan/internal/os_utils.h>
30
#endif
31

32
#ifdef _WIN32
33
   #include <fcntl.h>
34
   #include <io.h>
35
#endif
36

37
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
38
   #include <fcntl.h>
39
   #include <sys/stat.h>
40
   #include <unistd.h>
41
#endif
42

43
namespace Botan_CLI {
44

45
Command::Command(const std::string& cmd_spec) : m_spec(cmd_spec) {
1,972 ✔
46
   // for checking all spec strings at load time
47
   //m_args.reset(new Argument_Parser(m_spec));
48
}
1,972 ✔
49

50
Command::~Command() = default;
2,304 ✔
51

52
std::string Command::cmd_name() const {
75 ✔
53
   return m_spec.substr(0, m_spec.find(' '));
75 ✔
54
}
55

56
std::string Command::help_text() const {
29 ✔
57
   return "Usage: " + m_spec;
29 ✔
58
}
59

60
//static
61
std::vector<std::string> Command::split_on(const std::string& str, char delim) {
142 ✔
62
   return Argument_Parser::split_on(str, delim);
142 ✔
63
}
64

65
int Command::run(const std::vector<std::string>& params) {
1,896 ✔
66
   try {
1,896 ✔
67
      const std::vector<std::string> extra_flags = {"verbose", "help"};
1,896 ✔
68
      const std::vector<std::string> extra_opts = {"output", "error-output", "rng-type", "drbg-seed"};
1,896 ✔
69

70
      m_args = std::make_unique<Argument_Parser>(m_spec, extra_flags, extra_opts);
1,896 ✔
71

72
      m_args->parse_args(params);
1,896 ✔
73

74
      if(m_args->has_arg("output")) {
1,896 ✔
75
         m_output_file = get_arg("output");
3,790 ✔
76
      }
77

78
      if(m_args->has_arg("error-output")) {
1,896 ✔
79
         const std::string output_file = get_arg("error-output");
3,790 ✔
80

81
         if(!output_file.empty()) {
1,895 ✔
82
            m_error_output_stream = std::make_unique<std::ofstream>(output_file, std::ios::binary);
×
83
            if(!m_error_output_stream->good()) {
×
84
               throw CLI_IO_Error("opening", output_file);
×
85
            }
86
         }
87
      }
1,895 ✔
88

89
      if(flag_set("help")) {
1,896 ✔
90
         output() << help_text() << "\n";
2 ✔
91
         return 2;
1 ✔
92
      }
93

94
      this->go();
1,895 ✔
95
      finish_output();
1,857 ✔
96
      return m_return_code;
1,856 ✔
97
   } catch(CLI_Usage_Error& e) {
1,974 ✔
98
      error_output() << "Usage error: " << e.what() << "\n";
28 ✔
99
      error_output() << help_text() << "\n";
56 ✔
100
      return 1;
28 ✔
101
   } catch(std::exception& e) {
39 ✔
102
      error_output() << "Error: " << e.what() << "\n";
11 ✔
103
      return 2;
11 ✔
104
   } catch(...) {
11 ✔
105
      error_output() << "Error: unknown exception\n";
×
106
      return 2;
×
107
   }
×
108
}
109

110
bool Command::flag_set(const std::string& flag_name) const {
4,308 ✔
111
   return m_args->flag_set(flag_name);
1,896 ✔
112
}
113

114
std::string Command::get_arg(const std::string& opt_name) const {
8,726 ✔
115
   return m_args->get_arg(opt_name);
8,549 ✔
116
}
117

118
/*
119
* Like get_arg() but if the argument was not specified or is empty, returns otherwise
120
*/
121
std::string Command::get_arg_or(const std::string& opt_name, const std::string& otherwise) const {
4,273 ✔
122
   return m_args->get_arg_or(opt_name, otherwise);
4,273 ✔
123
}
124

125
std::optional<std::string> Command::get_arg_maybe(const std::string& opt_name) const {
196 ✔
126
   auto arg = m_args->get_arg(opt_name);
196 ✔
127
   if(arg.empty()) {
196 ✔
128
      return std::nullopt;
136 ✔
129
   } else {
130
      return arg;
60 ✔
131
   }
132
}
196 ✔
133

134
size_t Command::get_arg_sz(const std::string& opt_name) const {
1,787 ✔
135
   return m_args->get_arg_sz(opt_name);
1,754 ✔
136
}
137

138
uint16_t Command::get_arg_u16(const std::string& opt_name) const {
33 ✔
139
   const size_t val = get_arg_sz(opt_name);
33 ✔
140
   if(static_cast<uint16_t>(val) != val) {
33 ✔
141
      throw CLI_Usage_Error("Argument " + opt_name + " has value out of allowed range");
×
142
   }
143
   return static_cast<uint16_t>(val);
33 ✔
144
}
145

146
uint32_t Command::get_arg_u32(const std::string& opt_name) const {
×
147
   const size_t val = get_arg_sz(opt_name);
×
148
   if(static_cast<uint32_t>(val) != val) {
×
149
      throw CLI_Usage_Error("Argument " + opt_name + " has value out of allowed range");
×
150
   }
151
   return static_cast<uint32_t>(val);
×
152
}
153

154
std::vector<std::string> Command::get_arg_list(const std::string& what) const {
121 ✔
155
   return m_args->get_arg_list(what);
121 ✔
156
}
157

158
std::ostream& Command::output() {
5,021 ✔
159
   if(!m_output_file.empty()) {
5,021 ✔
160
      /*
161
      * The output file is created only once something is written to it, so
162
      * that naming an input file as the output does not truncate the input
163
      * before the command has read it.
164
      */
165
      if(!m_output_stream) {
513 ✔
166
         m_output_stream = std::make_unique<std::ofstream>(open_output_file(m_output_file, output_contains_secrets()));
136 ✔
167
      }
168
      return *m_output_stream;
512 ✔
169
   }
170
   return std::cout;
171
}
172

173
std::ostream& Command::output_binary() {
1,428 ✔
174
   if(!m_output_file.empty()) {
1,428 ✔
175
      return output();
16 ✔
176
   }
177
#ifdef _WIN32
178
   _setmode(_fileno(stdout), _O_BINARY);
179
#endif
180
   return std::cout;
181
}
182

183
void Command::finish_output() {
1,857 ✔
184
   if(!m_output_file.empty()) {
1,857 ✔
185
      // Creates the file if the command produced no output at all
186
      output();
135 ✔
187
      m_output_stream->close();
135 ✔
188
      if(m_output_stream->fail()) {
135 ✔
189
         throw CLI_IO_Error("writing", m_output_file);
2 ✔
190
      }
191
   } else {
192
      std::cout.flush();
1,722 ✔
193
      if(!std::cout.good()) {
1,722 ✔
194
         throw CLI_IO_Error("writing", "stdout");
×
195
      }
196
   }
197

198
   if(m_error_output_stream) {
1,856 ✔
199
      m_error_output_stream->flush();
×
200
   }
201
}
1,856 ✔
202

203
std::ostream& Command::error_output() {
117 ✔
204
   if(m_error_output_stream) {
117 ✔
205
      return *m_error_output_stream;
×
206
   }
207
   return std::cerr;
208
}
209

210
//static
211
std::ofstream Command::open_output_file(const std::string& path, bool private_file) {
143 ✔
212
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
213
   if(private_file) {
143 ✔
214
      // Create the file with owner-only permissions before the stream opens it
215
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
216
      const int fd = ::open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
46 ✔
217
      if(fd < 0) {
46 ✔
218
         throw CLI_IO_Error("opening", path);
×
219
      }
220
      ::close(fd);
46 ✔
221
   }
222
#else
223
   BOTAN_UNUSED(private_file);
224
#endif
225

226
   std::ofstream out(path, std::ios::binary);
143 ✔
227
   if(!out) {
143 ✔
228
      throw CLI_IO_Error("opening", path);
2 ✔
229
   }
230
   return out;
142 ✔
231
}
1 ✔
232

233
//static
234
void Command::replace_file_contents(const std::string& path, std::string_view contents, bool private_file) {
×
235
   const std::string tmp_path = path + ".tmp";
×
236

237
   std::ofstream out = open_output_file(tmp_path, private_file);
×
238
   out.write(contents.data(), static_cast<std::streamsize>(contents.size()));
×
239
   out.close();
×
240

241
   if(!out) {
×
242
      static_cast<void>(std::remove(tmp_path.c_str()));
×
243
      throw CLI_IO_Error("writing", tmp_path);
×
244
   }
245

246
#if defined(_WIN32)
247
   // rename does not replace an existing file on Windows
248
   static_cast<void>(std::remove(path.c_str()));
249
#endif
250

251
   if(std::rename(tmp_path.c_str(), path.c_str()) != 0) {
×
252
      static_cast<void>(std::remove(tmp_path.c_str()));
×
253
      throw CLI_IO_Error("replacing", path);
×
254
   }
255
}
×
256

257
//static
258
std::vector<uint8_t> Command::slurp_file(const std::string& input_file, size_t buf_size) {
1,482 ✔
259
   std::vector<uint8_t> buf;
1,482 ✔
260
   const auto insert_fn = [&](const uint8_t b[], size_t l) { buf.insert(buf.end(), b, b + l); };
1,496 ✔
261
   Command::read_file(input_file, insert_fn, buf_size);
1,482 ✔
262
   return buf;
1,482 ✔
263
}
×
264

265
//static
266
std::string Command::slurp_file_as_str(const std::string& input_file, size_t buf_size) {
39 ✔
267
   std::string str;
39 ✔
268
   const auto insert_fn = [&](const uint8_t b[], size_t l) { str.append(reinterpret_cast<const char*>(b), l); };
49 ✔
269
   Command::read_file(input_file, insert_fn, buf_size);
41 ✔
270
   return str;
37 ✔
271
}
2 ✔
272

273
void Command::read_file(const std::string& input_file,
1,576 ✔
274
                        const std::function<void(uint8_t[], size_t)>& consumer_fn,
275
                        size_t buf_size) {
276
   if(input_file == "-") {
1,576 ✔
277
#ifdef _WIN32
278
      _setmode(_fileno(stdin), _O_BINARY);
279
#endif
280
      do_read_file(std::cin, consumer_fn, buf_size);
1,435 ✔
281
      if(std::cin.bad()) {
1,435 ✔
282
         throw CLI_IO_Error("reading", "stdin");
×
283
      }
284
   } else {
285
      std::ifstream in(input_file, std::ios::binary);
141 ✔
286
      if(!in) {
141 ✔
287
         throw CLI_IO_Error("reading file", input_file);
8 ✔
288
      }
289
      do_read_file(in, consumer_fn, buf_size);
137 ✔
290
      // A read error (as opposed to EOF) sets badbit
291
      if(in.bad()) {
137 ✔
292
         throw CLI_IO_Error("reading file", input_file);
×
293
      }
294
   }
141 ✔
295
}
1,572 ✔
296

297
//static
298
void Command::do_read_file(std::istream& in,
1,574 ✔
299
                           const std::function<void(uint8_t[], size_t)>& consumer_fn,
300
                           size_t buf_size) {
301
   // Avoid an infinite loop on --buf-size=0
302
   std::vector<uint8_t> buf(buf_size == 0 ? 4096 : buf_size);
1,707 ✔
303

304
   while(in.good()) {
3,189 ✔
305
      in.read(reinterpret_cast<char*>(buf.data()), buf.size());
1,615 ✔
306
      const size_t got = static_cast<size_t>(in.gcount());
1,615 ✔
307
      consumer_fn(buf.data(), got);
3,230 ✔
308
   }
309
}
1,574 ✔
310

311
Botan::RandomNumberGenerator& Command::rng() {
254 ✔
312
   return *rng_as_shared();
254 ✔
313
}
314

315
std::shared_ptr<Botan::RandomNumberGenerator> Command::rng_as_shared() {
334 ✔
316
   if(m_rng == nullptr) {
334 ✔
317
      m_rng = cli_make_rng(get_arg("rng-type"), get_arg("drbg-seed"));
591 ✔
318
   }
319

320
   return m_rng;
334 ✔
321
}
322

323
std::string Command::get_passphrase_arg(const std::string& prompt, const std::string& opt_name) {
177 ✔
324
   std::string s = get_arg(opt_name);
177 ✔
325
   if(s != "-") {
177 ✔
326
      return s;
177 ✔
327
   }
328
   return get_passphrase(prompt);
×
329
}
177 ✔
330

331
std::string Command::get_passphrase(const std::string& prompt) {
×
332
#if defined(BOTAN_HAS_OS_UTILS)
333
   const auto echo_suppress = Botan::OS::suppress_echo_on_terminal();
×
334
   const bool echo_suppressed = (echo_suppress != nullptr);
×
335
#else
336
   const bool echo_suppressed = false;
337
#endif
338

339
   if(!echo_suppressed) {
×
340
      error_output() << "Warning: terminal echo suppression not enabled for this platform\n";
×
341
   }
342

343
   error_output() << prompt << ": " << std::flush;
×
344

345
   std::string pass;
×
346

347
   // EOF (eg Ctrl-D) must not be mistaken for an empty passphrase
348
   if(!std::getline(std::cin, pass)) {
×
349
      throw CLI_Error("No passphrase was entered");
×
350
   }
351

352
   return pass;
×
353
}
×
354

355
//static
356
std::string Command::format_blob(const std::string& format, const uint8_t bits[], size_t len) {
41 ✔
357
#if defined(BOTAN_HAS_HEX_CODEC)
358
   if(format == "hex") {
41 ✔
359
      return Botan::hex_encode(bits, len);
35 ✔
360
   }
361
#endif
362

363
#if defined(BOTAN_HAS_BASE64_CODEC)
364
   if(format == "base64") {
6 ✔
365
      return Botan::base64_encode(bits, len);
2 ✔
366
   }
367
#endif
368

369
#if defined(BOTAN_HAS_BASE58_CODEC)
370
   if(format == "base58") {
4 ✔
371
      return Botan::base58_encode(bits, len);
2 ✔
372
   }
373
   if(format == "base58check") {
2 ✔
374
      return Botan::base58_check_encode(bits, len);
2 ✔
375
   }
376
#endif
377

378
   // If we supported format, we would have already returned
379
   throw CLI_Usage_Error("Unknown or unsupported format type");
×
380
}
381

382
// Registration code
383

384
Command::Registration::Registration(const std::string& name, const Command::cmd_maker_fn& maker_fn) {
144,096 ✔
385
   std::map<std::string, Command::cmd_maker_fn>& reg = Command::global_registry();
144,096 ✔
386

387
   if(reg.contains(name)) {
144,096 ✔
388
      throw CLI_Error("Duplicated registration of command " + name);
×
389
   }
390

391
   reg.insert(std::make_pair(name, maker_fn));
144,096 ✔
392
}
144,096 ✔
393

394
//static
395
std::map<std::string, Command::cmd_maker_fn>& Command::global_registry() {
146,083 ✔
396
   static std::map<std::string, Command::cmd_maker_fn> g_cmds;
146,083 ✔
397
   return g_cmds;
146,083 ✔
398
}
399

400
//static
401
std::vector<std::string> Command::registered_cmds() {
15 ✔
402
   std::vector<std::string> cmds;
15 ✔
403
   for(const auto& cmd : Command::global_registry()) {
1,155 ✔
404
      cmds.push_back(cmd.first);
1,140 ✔
405
   }
406
   return cmds;
15 ✔
407
}
×
408

409
//static
410
std::unique_ptr<Command> Command::get_cmd(const std::string& name) {
1,972 ✔
411
   const auto& reg = Command::global_registry();
1,972 ✔
412

413
   const auto i = reg.find(name);
1,972 ✔
414
   if(i != reg.end()) {
1,972 ✔
415
      return i->second();
1,972 ✔
416
   }
417

418
   return nullptr;
×
419
}
420

421
}  // namespace Botan_CLI
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