Skip to content

Commit 6e43b2a

Browse files
committed
New exec_command() that takes command as array instead of string
This abstracts away the details of argument quoting, which differs between Unix and Windows. Also replace all uses of the system() library call with exec_command(). Although system() exists on Windows, it executes the command via cmd.exe, which has ridiculous escaping rules.
1 parent 0774ed0 commit 6e43b2a

5 files changed

Lines changed: 198 additions & 90 deletions

File tree

commands.cpp

Lines changed: 113 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -47,58 +47,63 @@
4747
#include <errno.h>
4848
#include <vector>
4949

50-
static void configure_git_filters ()
50+
static void git_config (const std::string& name, const std::string& value)
5151
{
52-
std::string git_crypt_path(our_exe_path());
53-
54-
// git config filter.git-crypt.smudge "/path/to/git-crypt smudge"
55-
std::string command("git config filter.git-crypt.smudge ");
56-
command += escape_shell_arg(escape_shell_arg(git_crypt_path) + " smudge");
57-
58-
if (!successful_exit(system(command.c_str()))) {
59-
throw Error("'git config' failed");
60-
}
52+
std::vector<std::string> command;
53+
command.push_back("git");
54+
command.push_back("config");
55+
command.push_back(name);
56+
command.push_back(value);
6157

62-
// git config filter.git-crypt.clean "/path/to/git-crypt clean"
63-
command = "git config filter.git-crypt.clean ";
64-
command += escape_shell_arg(escape_shell_arg(git_crypt_path) + " clean");
65-
66-
if (!successful_exit(system(command.c_str()))) {
58+
if (!successful_exit(exec_command(command))) {
6759
throw Error("'git config' failed");
6860
}
61+
}
6962

70-
// git config diff.git-crypt.textconv "/path/to/git-crypt diff"
71-
command = "git config diff.git-crypt.textconv ";
72-
command += escape_shell_arg(escape_shell_arg(git_crypt_path) + " diff");
63+
static void configure_git_filters ()
64+
{
65+
std::string escaped_git_crypt_path(escape_shell_arg(our_exe_path()));
7366

74-
if (!successful_exit(system(command.c_str()))) {
75-
throw Error("'git config' failed");
76-
}
67+
git_config("filter.git-crypt.smudge", escaped_git_crypt_path + " smudge");
68+
git_config("filter.git-crypt.clean", escaped_git_crypt_path + " clean");
69+
git_config("diff.git-crypt.textconv", escaped_git_crypt_path + " diff");
7770
}
7871

7972
static std::string get_internal_key_path ()
8073
{
81-
std::stringstream output;
74+
// git rev-parse --git-dir
75+
std::vector<std::string> command;
76+
command.push_back("git");
77+
command.push_back("rev-parse");
78+
command.push_back("--git-dir");
79+
80+
std::stringstream output;
8281

83-
if (!successful_exit(exec_command("git rev-parse --git-dir", output))) {
82+
if (!successful_exit(exec_command(command, output))) {
8483
throw Error("'git rev-parse --git-dir' failed - is this a Git repository?");
8584
}
8685

87-
std::string path;
86+
std::string path;
8887
std::getline(output, path);
8988
path += "/git-crypt/key";
9089
return path;
9190
}
9291

9392
static std::string get_repo_keys_path ()
9493
{
95-
std::stringstream output;
94+
// git rev-parse --show-toplevel
95+
std::vector<std::string> command;
96+
command.push_back("git");
97+
command.push_back("rev-parse");
98+
command.push_back("--show-toplevel");
99+
100+
std::stringstream output;
96101

97-
if (!successful_exit(exec_command("git rev-parse --show-toplevel", output))) {
102+
if (!successful_exit(exec_command(command, output))) {
98103
throw Error("'git rev-parse --show-toplevel' failed - is this a Git repository?");
99104
}
100105

101-
std::string path;
106+
std::string path;
102107
std::getline(output, path);
103108

104109
if (path.empty()) {
@@ -110,6 +115,52 @@ static std::string get_repo_keys_path ()
110115
return path;
111116
}
112117

118+
static std::string get_path_to_top ()
119+
{
120+
// git rev-parse --show-cdup
121+
std::vector<std::string> command;
122+
command.push_back("git");
123+
command.push_back("rev-parse");
124+
command.push_back("--show-cdup");
125+
126+
std::stringstream output;
127+
128+
if (!successful_exit(exec_command(command, output))) {
129+
throw Error("'git rev-parse --show-cdup' failed - is this a Git repository?");
130+
}
131+
132+
std::string path_to_top;
133+
std::getline(output, path_to_top);
134+
135+
return path_to_top;
136+
}
137+
138+
static void get_git_status (std::ostream& output)
139+
{
140+
// git status -uno --porcelain
141+
std::vector<std::string> command;
142+
command.push_back("git");
143+
command.push_back("status");
144+
command.push_back("-uno"); // don't show untracked files
145+
command.push_back("--porcelain");
146+
147+
if (!successful_exit(exec_command(command, output))) {
148+
throw Error("'git status' failed - is this a Git repository?");
149+
}
150+
}
151+
152+
static bool check_if_head_exists ()
153+
{
154+
// git rev-parse HEAD
155+
std::vector<std::string> command;
156+
command.push_back("git");
157+
command.push_back("rev-parse");
158+
command.push_back("HEAD");
159+
160+
std::stringstream output;
161+
return successful_exit(exec_command(command, output));
162+
}
163+
113164
static void load_key (Key_file& key_file, const char* legacy_path =0)
114165
{
115166
if (legacy_path) {
@@ -421,20 +472,20 @@ int unlock (int argc, char** argv)
421472
return 2;
422473
}
423474

424-
// 0. Check to see if HEAD exists. See below why we do this.
425-
bool head_exists = successful_exit(system("git rev-parse HEAD >/dev/null 2>/dev/null"));
426-
427-
// 1. Make sure working directory is clean (ignoring untracked files)
475+
// 0. Make sure working directory is clean (ignoring untracked files)
428476
// We do this because we run 'git checkout -f HEAD' later and we don't
429477
// want the user to lose any changes. 'git checkout -f HEAD' doesn't touch
430478
// untracked files so it's safe to ignore those.
431-
int status;
479+
480+
// Running 'git status' also serves as a check that the Git repo is accessible.
481+
432482
std::stringstream status_output;
433-
status = exec_command("git status -uno --porcelain", status_output);
434-
if (!successful_exit(status)) {
435-
std::clog << "Error: 'git status' failed - is this a git repository?" << std::endl;
436-
return 1;
437-
} else if (status_output.peek() != -1 && head_exists) {
483+
get_git_status(status_output);
484+
485+
// 1. Check to see if HEAD exists. See below why we do this.
486+
bool head_exists = check_if_head_exists();
487+
488+
if (status_output.peek() != -1 && head_exists) {
438489
// We only care that the working directory is dirty if HEAD exists.
439490
// If HEAD doesn't exist, we won't be resetting to it (see below) so
440491
// it doesn't matter that the working directory is dirty.
@@ -446,11 +497,7 @@ int unlock (int argc, char** argv)
446497
// 2. Determine the path to the top of the repository. We pass this as the argument
447498
// to 'git checkout' below. (Determine the path now so in case it fails we haven't already
448499
// mucked with the git config.)
449-
std::stringstream cdup_output;
450-
if (!successful_exit(exec_command("git rev-parse --show-cdup", cdup_output))) {
451-
std::clog << "Error: 'git rev-parse --show-cdup' failed" << std::endl;
452-
return 1;
453-
}
500+
std::string path_to_top(get_path_to_top());
454501

455502
// 3. Install the key
456503
Key_file key_file;
@@ -504,17 +551,20 @@ int unlock (int argc, char** argv)
504551
// If HEAD doesn't exist (perhaps because this repo doesn't have any files yet)
505552
// just skip the checkout.
506553
if (head_exists) {
507-
std::string path_to_top;
508-
std::getline(cdup_output, path_to_top);
509-
510-
std::string command("git checkout -f HEAD -- ");
554+
// git checkout -f HEAD -- path/to/top
555+
std::vector<std::string> command;
556+
command.push_back("git");
557+
command.push_back("checkout");
558+
command.push_back("-f");
559+
command.push_back("HEAD");
560+
command.push_back("--");
511561
if (path_to_top.empty()) {
512-
command += ".";
562+
command.push_back(".");
513563
} else {
514-
command += escape_shell_arg(path_to_top);
564+
command.push_back(path_to_top);
515565
}
516566

517-
if (!successful_exit(system(command.c_str()))) {
567+
if (!successful_exit(exec_command(command))) {
518568
std::clog << "Error: 'git checkout' failed" << std::endl;
519569
std::clog << "git-crypt has been set up but existing encrypted files have not been decrypted" << std::endl;
520570
return 1;
@@ -563,13 +613,12 @@ int add_collab (int argc, char** argv)
563613

564614
// add/commit the new files
565615
if (!new_files.empty()) {
566-
// git add ...
567-
std::string command("git add");
568-
for (std::vector<std::string>::const_iterator file(new_files.begin()); file != new_files.end(); ++file) {
569-
command += " ";
570-
command += escape_shell_arg(*file);
571-
}
572-
if (!successful_exit(system(command.c_str()))) {
616+
// git add NEW_FILE ...
617+
std::vector<std::string> command;
618+
command.push_back("git");
619+
command.push_back("add");
620+
command.insert(command.end(), new_files.begin(), new_files.end());
621+
if (!successful_exit(exec_command(command))) {
573622
std::clog << "Error: 'git add' failed" << std::endl;
574623
return 1;
575624
}
@@ -582,14 +631,15 @@ int add_collab (int argc, char** argv)
582631
commit_message_builder << '\t' << gpg_shorten_fingerprint(*collab) << ' ' << gpg_get_uid(*collab) << '\n';
583632
}
584633

585-
command = "git commit -m ";
586-
command += escape_shell_arg(commit_message_builder.str());
587-
for (std::vector<std::string>::const_iterator file(new_files.begin()); file != new_files.end(); ++file) {
588-
command += " ";
589-
command += escape_shell_arg(*file);
590-
}
634+
// git commit -m MESSAGE NEW_FILE ...
635+
command.clear();
636+
command.push_back("git");
637+
command.push_back("commit");
638+
command.push_back("-m");
639+
command.push_back(commit_message_builder.str());
640+
command.insert(command.end(), new_files.begin(), new_files.end());
591641

592-
if (!successful_exit(system(command.c_str()))) {
642+
if (!successful_exit(exec_command(command))) {
593643
std::clog << "Error: 'git commit' failed" << std::endl;
594644
return 1;
595645
}

gpg.cpp

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,15 @@ std::string gpg_shorten_fingerprint (const std::string& fingerprint)
6161
std::string gpg_get_uid (const std::string& fingerprint)
6262
{
6363
// gpg --batch --with-colons --fixed-list-mode --list-keys 0x7A399B2DB06D039020CD1CE1D0F3702D61489532
64-
std::string command("gpg --batch --with-colons --fixed-list-mode --list-keys ");
65-
command += escape_shell_arg("0x" + fingerprint);
64+
std::vector<std::string> command;
65+
command.push_back("gpg");
66+
command.push_back("--batch");
67+
command.push_back("--with-colons");
68+
command.push_back("--fixed-list-mode");
69+
command.push_back("--list-keys");
70+
command.push_back("0x" + fingerprint);
6671
std::stringstream command_output;
67-
if (!successful_exit(exec_command(command.c_str(), command_output))) {
72+
if (!successful_exit(exec_command(command, command_output))) {
6873
// This could happen if the keyring does not contain a public key with this fingerprint
6974
return "";
7075
}
@@ -88,10 +93,15 @@ std::vector<std::string> gpg_lookup_key (const std::string& query)
8893
std::vector<std::string> fingerprints;
8994

9095
// gpg --batch --with-colons --fingerprint --list-keys jsmith@example.com
91-
std::string command("gpg --batch --with-colons --fingerprint --list-keys ");
92-
command += escape_shell_arg(query);
96+
std::vector<std::string> command;
97+
command.push_back("gpg");
98+
command.push_back("--batch");
99+
command.push_back("--with-colons");
100+
command.push_back("--fingerprint");
101+
command.push_back("--list-keys");
102+
command.push_back(query);
93103
std::stringstream command_output;
94-
if (successful_exit(exec_command(command.c_str(), command_output))) {
104+
if (successful_exit(exec_command(command, command_output))) {
95105
while (command_output.peek() != -1) {
96106
std::string line;
97107
std::getline(command_output, line);
@@ -109,8 +119,14 @@ std::vector<std::string> gpg_lookup_key (const std::string& query)
109119
std::vector<std::string> gpg_list_secret_keys ()
110120
{
111121
// gpg --batch --with-colons --list-secret-keys --fingerprint
122+
std::vector<std::string> command;
123+
command.push_back("gpg");
124+
command.push_back("--batch");
125+
command.push_back("--with-colons");
126+
command.push_back("--list-secret-keys");
127+
command.push_back("--fingerprint");
112128
std::stringstream command_output;
113-
if (!successful_exit(exec_command("gpg --batch --with-colons --list-secret-keys --fingerprint", command_output))) {
129+
if (!successful_exit(exec_command(command, command_output))) {
114130
throw Gpg_error("gpg --list-secret-keys failed");
115131
}
116132

@@ -132,22 +148,28 @@ std::vector<std::string> gpg_list_secret_keys ()
132148
void gpg_encrypt_to_file (const std::string& filename, const std::string& recipient_fingerprint, const char* p, size_t len)
133149
{
134150
// gpg --batch -o FILENAME -r RECIPIENT -e
135-
std::string command("gpg --batch -o ");
136-
command += escape_shell_arg(filename);
137-
command += " -r ";
138-
command += escape_shell_arg("0x" + recipient_fingerprint);
139-
command += " -e";
140-
if (!successful_exit(exec_command_with_input(command.c_str(), p, len))) {
151+
std::vector<std::string> command;
152+
command.push_back("gpg");
153+
command.push_back("--batch");
154+
command.push_back("-o");
155+
command.push_back(filename);
156+
command.push_back("-r");
157+
command.push_back("0x" + recipient_fingerprint);
158+
command.push_back("-e");
159+
if (!successful_exit(exec_command_with_input(command, p, len))) {
141160
throw Gpg_error("Failed to encrypt");
142161
}
143162
}
144163

145164
void gpg_decrypt_from_file (const std::string& filename, std::ostream& output)
146165
{
147-
// gpg -q -d
148-
std::string command("gpg -q -d ");
149-
command += escape_shell_arg(filename);
150-
if (!successful_exit(exec_command(command.c_str(), output))) {
166+
// gpg -q -d FILENAME
167+
std::vector<std::string> command;
168+
command.push_back("gpg");
169+
command.push_back("-q");
170+
command.push_back("-d");
171+
command.push_back(filename);
172+
if (!successful_exit(exec_command(command, output))) {
151173
throw Gpg_error("Failed to decrypt");
152174
}
153175
}

0 commit comments

Comments
 (0)