aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRafael G. Martins <rafael@rafaelmartins.eng.br>2016-10-07 01:48:16 +0200
committerRafael G. Martins <rafael@rafaelmartins.eng.br>2016-10-07 01:48:16 +0200
commit2c1b2eaaef8726c71648b4a115fbe628f28d6b4b (patch)
tree3d34829130ee8870e268d3fcd19d4149e66b1be6
parent13be5bd50238daa2be6b42104ba20aeea427655b (diff)
downloadblogc-2c1b2eaaef8726c71648b4a115fbe628f28d6b4b.tar.gz
blogc-2c1b2eaaef8726c71648b4a115fbe628f28d6b4b.tar.bz2
blogc-2c1b2eaaef8726c71648b4a115fbe628f28d6b4b.zip
git-receiver: splitted/reimplemented and tested shell command parser
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am21
-rw-r--r--src/blogc-git-receiver/shell-command-parser.c125
-rw-r--r--src/blogc-git-receiver/shell-command-parser.h15
-rw-r--r--src/blogc-git-receiver/shell.c97
-rw-r--r--src/blogc-git-receiver/shell.h1
-rw-r--r--tests/blogc-git-receiver/check_shell_command_parser.c244
7 files changed, 448 insertions, 56 deletions
diff --git a/.gitignore b/.gitignore
index af2b5d8..98aee66 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,6 +58,7 @@ blogc*.html
/tests/blogc/check_source_parser
/tests/blogc/check_template_parser
/tests/blogc-git-receiver/check_pre_receive_parser
+/tests/blogc-git-receiver/check_shell_command_parser
/tests/blogc-runserver/check_httpd_utils
/tests/blogc-runserver/check_mime
/tests/common/check_config_parser
diff --git a/Makefile.am b/Makefile.am
index fff49b9..31c1dca 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -43,6 +43,7 @@ noinst_HEADERS = \
src/blogc-git-receiver/pre-receive.h \
src/blogc-git-receiver/pre-receive-parser.h \
src/blogc-git-receiver/shell.h \
+ src/blogc-git-receiver/shell-command-parser.h \
src/blogc-runserver/httpd.h \
src/blogc-runserver/httpd-utils.h \
src/blogc-runserver/mime.h \
@@ -156,6 +157,7 @@ libblogc_git_receiver_la_SOURCES = \
src/blogc-git-receiver/pre-receive.c \
src/blogc-git-receiver/pre-receive-parser.c \
src/blogc-git-receiver/shell.c \
+ src/blogc-git-receiver/shell-command-parser.c \
$(NULL)
libblogc_git_receiver_la_CFLAGS = \
@@ -571,6 +573,7 @@ endif
if BUILD_GIT_RECEIVER
check_PROGRAMS += \
tests/blogc-git-receiver/check_pre_receive_parser \
+ tests/blogc-git-receiver/check_shell_command_parser \
$(NULL)
tests_blogc_git_receiver_check_pre_receive_parser_SOURCES = \
@@ -590,6 +593,24 @@ tests_blogc_git_receiver_check_pre_receive_parser_LDADD = \
libblogc_git_receiver.la \
libblogc_common.la \
$(NULL)
+
+tests_blogc_git_receiver_check_shell_command_parser_SOURCES = \
+ tests/blogc-git-receiver/check_shell_command_parser.c \
+ $(NULL)
+
+tests_blogc_git_receiver_check_shell_command_parser_CFLAGS = \
+ $(CMOCKA_CFLAGS) \
+ $(NULL)
+
+tests_blogc_git_receiver_check_shell_command_parser_LDFLAGS = \
+ -no-install \
+ $(NULL)
+
+tests_blogc_git_receiver_check_shell_command_parser_LDADD = \
+ $(CMOCKA_LIBS) \
+ libblogc_git_receiver.la \
+ libblogc_common.la \
+ $(NULL)
endif
endif
diff --git a/src/blogc-git-receiver/shell-command-parser.c b/src/blogc-git-receiver/shell-command-parser.c
new file mode 100644
index 0000000..cc8c537
--- /dev/null
+++ b/src/blogc-git-receiver/shell-command-parser.c
@@ -0,0 +1,125 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "../common/utils.h"
+#include "shell-command-parser.h"
+
+typedef enum {
+ START_COMMAND = 1,
+ START_REPO,
+ START_REPO2,
+ REPO,
+ START_ESCAPED,
+} command_state_t;
+
+
+char*
+bgr_shell_command_parse(const char *command)
+{
+ command_state_t state = START_COMMAND;
+ size_t start = 0;
+ size_t command_len = strlen(command);
+
+ bc_string_t *rv = bc_string_new();
+
+ for (size_t current = 0; current < command_len; current++) {
+
+ char c = command[current];
+
+ switch (state) {
+ case START_COMMAND:
+ if (c == ' ') {
+ if (((current == 16) &&
+ (0 == strncmp("git-receive-pack", command, 16))) ||
+ ((current == 15) &&
+ (0 == strncmp("git-upload-pack", command, 15))) ||
+ ((current == 18) &&
+ (0 == strncmp("git-upload-archive", command, 18))))
+ {
+ state = START_REPO;
+ break;
+ }
+ goto error;
+ }
+ break;
+
+ case START_REPO:
+ if (c == '\'') { // never saw git using double-quotes
+ state = START_REPO2;
+ break;
+ }
+ if (c == '\\') { // escaped ! or '
+ state = START_ESCAPED;
+ break;
+ }
+ goto error;
+
+ case START_REPO2:
+ if (c == '\'') {
+ state = START_REPO;
+ break;
+ }
+ start = current;
+ if (rv->len == 0 && c == '/') { // no absolute urls
+ start = current + 1;
+ }
+ state = REPO;
+ break;
+
+ case START_ESCAPED:
+ if (c == '!' || c == '\'') {
+ bc_string_append_c(rv, c);
+ state = START_REPO;
+ break;
+ }
+ goto error;
+
+ case REPO:
+ if (c == '\'') {
+ bc_string_append_len(rv, command + start, current - start);
+ state = START_REPO;
+ break;
+ }
+ break;
+ }
+ }
+
+ if (rv->len > 0)
+ return bc_string_free(rv, false);
+
+error:
+ bc_string_free(rv, true);
+ return NULL;
+}
+
+
+char*
+bgr_shell_quote(const char *command)
+{
+ // this does not really belongs here, but function is very small
+ bc_string_t *rv = bc_string_new();
+ bc_string_append_c(rv, '\'');
+ if (command != NULL) {
+ for (size_t i = 0; i < strlen(command); i++) {
+ switch (command[i]) {
+ case '!':
+ bc_string_append(rv, "'\\!'");
+ break;
+ case '\'':
+ bc_string_append(rv, "'\\''");
+ break;
+ default:
+ bc_string_append_c(rv, command[i]);
+ }
+ }
+ }
+ bc_string_append_c(rv, '\'');
+ return bc_string_free(rv, false);
+}
diff --git a/src/blogc-git-receiver/shell-command-parser.h b/src/blogc-git-receiver/shell-command-parser.h
new file mode 100644
index 0000000..47054cb
--- /dev/null
+++ b/src/blogc-git-receiver/shell-command-parser.h
@@ -0,0 +1,15 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#ifndef _SHELL_COMMAND_PARSER_H
+#define _SHELL_COMMAND_PARSER_H
+
+char* bgr_shell_command_parse(const char *command);
+char* bgr_shell_quote(const char *command);
+
+#endif /* _SHELL_COMMAND_PARSER_H */
diff --git a/src/blogc-git-receiver/shell.c b/src/blogc-git-receiver/shell.c
index 290526b..581ec3d 100644
--- a/src/blogc-git-receiver/shell.c
+++ b/src/blogc-git-receiver/shell.c
@@ -16,12 +16,9 @@
#include <sys/stat.h>
#include <sys/types.h>
#include "../common/utils.h"
+#include "shell-command-parser.h"
#include "shell.h"
-#ifndef BUFFER_SIZE
-#define BUFFER_SIZE 4096
-#endif
-
int
bgr_shell(int argc, char *argv[])
@@ -29,22 +26,7 @@ bgr_shell(int argc, char *argv[])
int rv = 0;
char *repo = NULL;
- char *command_orig = NULL;
- char *command_name = NULL;
- char command_new[BUFFER_SIZE];
-
- bool exec_git = false;
-
- // validate git command
- size_t len = strlen(argv[2]);
- if (!((len > 17 && (0 == strncmp(argv[2], "git-receive-pack ", 17))) ||
- (len > 16 && (0 == strncmp(argv[2], "git-upload-pack ", 16))) ||
- (len > 19 && (0 == strncmp(argv[2], "git-upload-archive ", 19)))))
- {
- fprintf(stderr, "error: unsupported git command: %s\n", argv[2]);
- rv = 1;
- goto cleanup;
- }
+ char *quoted_repo = NULL;
// get shell path
char *self = getenv("SHELL");
@@ -62,23 +44,17 @@ bgr_shell(int argc, char *argv[])
goto cleanup;
}
- // get git repository
- command_orig = bc_strdup(argv[2]);
- char *p, *r;
- for (p = command_orig; *p != ' ' && *p != '\0'; p++);
- if (*p == ' ')
- p++;
- if (*p == '\'' || *p == '"')
- p++;
- if (*p == '/')
- p++;
- for (r = p; *p != '\'' && *p != '"' && *p != '\0'; p++);
- if (*p == '\'' || *p == '"')
- *p = '\0';
- if (*--p == '/')
- *p = '\0';
-
- repo = bc_strdup_printf("repos/%s", r);
+ // validate command and extract git repository
+ char *tmp_repo = bgr_shell_command_parse(argv[2]);
+ if (tmp_repo == NULL) {
+ fprintf(stderr, "error: invalid git-shell command: %s\n", argv[2]);
+ rv = 1;
+ goto cleanup;
+ }
+
+ repo = bc_strdup_printf("repos/%s", tmp_repo);
+ quoted_repo = bgr_shell_quote(repo);
+ free(tmp_repo);
// check if repository is sane
if (0 == strlen(repo)) {
@@ -99,7 +75,7 @@ bgr_shell(int argc, char *argv[])
if (0 != access(repo, F_OK)) {
char *git_init_cmd = bc_strdup_printf(
- "git init --bare \"%s\" > /dev/null", repo);
+ "git init --bare %s > /dev/null", quoted_repo);
if (0 != system(git_init_cmd)) {
fprintf(stderr, "error: failed to create git repository: %s\n",
repo);
@@ -175,32 +151,41 @@ bgr_shell(int argc, char *argv[])
}
git_exec:
- command_name = bc_strdup(argv[2]);
- for (p = command_name; *p != ' ' && *p != '\0'; p++);
- if (*p == ' ')
- *p = '\0';
- if (BUFFER_SIZE < (strlen(command_name) + strlen(repo) + 4)) {
- fprintf(stderr, "error: git-shell command is too big\n");
- rv = 1;
- goto cleanup;
- }
+ {
+ // static allocation instead of bc_strdup_printf to avoid leaks
+ char buffer[4096];
+ char *command = bc_strdup(argv[2]);
+ char *p;
+ for (p = command; *p != ' ' && *p != '\0'; p++);
+ if (*p == ' ')
+ *p = '\0';
+
+ if (sizeof(buffer) < (strlen(command) + strlen(quoted_repo) + 2)) {
+ fprintf(stderr, "error: git-shell command is too big\n");
+ rv = 1;
+ goto cleanup;
+ }
- if (snprintf(command_new, BUFFER_SIZE, "%s '%s'", command_name, repo))
- exec_git = true;
+ if (0 > snprintf(buffer, sizeof(buffer), "%s %s", command, quoted_repo)) {
+ fprintf(stderr, "error: failed to generate git-shell command\n");
+ rv = 1;
+ goto cleanup;
+ }
-cleanup:
- free(repo);
- free(command_orig);
- free(command_name);
+ free(command);
+ free(repo);
+ free(quoted_repo);
- if (exec_git) {
- execlp("git-shell", "git-shell", "-c", command_new, NULL);
+ execlp("git-shell", "git-shell", "-c", buffer, NULL);
// execlp only returns on error, then something bad happened
fprintf(stderr, "error: failed to execute git-shell\n");
- rv = 1;
+ return 1; // avoid freeing repo again
}
+cleanup:
+ free(repo);
+ free(quoted_repo);
return rv;
}
diff --git a/src/blogc-git-receiver/shell.h b/src/blogc-git-receiver/shell.h
index c05e4e1..ec77db1 100644
--- a/src/blogc-git-receiver/shell.h
+++ b/src/blogc-git-receiver/shell.h
@@ -9,6 +9,7 @@
#ifndef _SHELL_H
#define _SHELL_H
+char* bgr_shell_get_repo(const char *command);
int bgr_shell(int argc, char *argv[]);
#endif /* _SHELL_H */
diff --git a/tests/blogc-git-receiver/check_shell_command_parser.c b/tests/blogc-git-receiver/check_shell_command_parser.c
new file mode 100644
index 0000000..9d0e282
--- /dev/null
+++ b/tests/blogc-git-receiver/check_shell_command_parser.c
@@ -0,0 +1,244 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include "../../src/blogc-git-receiver/shell-command-parser.h"
+
+
+static void
+test_shell_command_parse(void **state)
+{
+ char *t;
+ assert_null(bgr_shell_command_parse(""));
+ assert_null(bgr_shell_command_parse("bola"));
+ assert_null(bgr_shell_command_parse("bola guda"));
+ assert_null(bgr_shell_command_parse("bola 'guda'"));
+ t = bgr_shell_command_parse("git-receive-pack 'bola.git'");
+ assert_string_equal(t, "bola.git");
+ free(t);
+ t = bgr_shell_command_parse("git-upload-pack 'bolaa.git'");
+ assert_string_equal(t, "bolaa.git");
+ free(t);
+ t = bgr_shell_command_parse("git-upload-archive 'bolab.git'");
+ assert_string_equal(t, "bolab.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/bola1.git'");
+ assert_string_equal(t, "bola1.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/bola2.git/'");
+ assert_string_equal(t, "bola2.git/");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack 'bola3.git/'");
+ assert_string_equal(t, "bola3.git/");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack 'foo/bola.git'");
+ assert_string_equal(t, "foo/bola.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/foo/bola1.git'");
+ assert_string_equal(t, "foo/bola1.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/foo/bola2.git/'");
+ assert_string_equal(t, "foo/bola2.git/");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack 'foo/bola3.git/'");
+ assert_string_equal(t, "foo/bola3.git/");
+ free(t);
+
+ t = bgr_shell_command_parse("git-receive-pack ''\\''bola.git'");
+ assert_string_equal(t, "'bola.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack ''\\''/bola1.git'");
+ assert_string_equal(t, "'/bola1.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack ''\\''/bola2.git/'");
+ assert_string_equal(t, "'/bola2.git/");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack ''\\''bola3.git/'");
+ assert_string_equal(t, "'bola3.git/");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack ''\\''foo/bola.git'");
+ assert_string_equal(t, "'foo/bola.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack ''\\''/foo/bola1.git'");
+ assert_string_equal(t, "'/foo/bola1.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack ''\\''/foo/bola2.git/'");
+ assert_string_equal(t, "'/foo/bola2.git/");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack ''\\''foo/bola3.git/'");
+ assert_string_equal(t, "'foo/bola3.git/");
+ free(t);
+
+ t = bgr_shell_command_parse("git-receive-pack 'bola.git'\\'''");
+ assert_string_equal(t, "bola.git'");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/bola1.git'\\'''");
+ assert_string_equal(t, "bola1.git'");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/bola2.git/'\\'''");
+ assert_string_equal(t, "bola2.git/'");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack 'bola3.git/'\\'''");
+ assert_string_equal(t, "bola3.git/'");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack 'foo/bola.git'\\'''");
+ assert_string_equal(t, "foo/bola.git'");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/foo/bola1.git'\\'''");
+ assert_string_equal(t, "foo/bola1.git'");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/foo/bola2.git/'\\'''");
+ assert_string_equal(t, "foo/bola2.git/'");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack 'foo/bola3.git/'\\'''");
+ assert_string_equal(t, "foo/bola3.git/'");
+ free(t);
+
+ t = bgr_shell_command_parse("git-receive-pack 'bo'\\''la.git'");
+ assert_string_equal(t, "bo'la.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/bo'\\''la1.git'");
+ assert_string_equal(t, "bo'la1.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/bo'\\''la2.git/'");
+ assert_string_equal(t, "bo'la2.git/");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack 'bo'\\''la3.git/'");
+ assert_string_equal(t, "bo'la3.git/");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack 'foo/bo'\\''la.git'");
+ assert_string_equal(t, "foo/bo'la.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/foo/bo'\\''la1.git'");
+ assert_string_equal(t, "foo/bo'la1.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/foo/bo'\\''la2.git/'");
+ assert_string_equal(t, "foo/bo'la2.git/");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack 'foo/bo'\\''la3.git/'");
+ assert_string_equal(t, "foo/bo'la3.git/");
+ free(t);
+
+ t = bgr_shell_command_parse("git-receive-pack ''\\!'bola.git'");
+ assert_string_equal(t, "!bola.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack ''\\!'/bola1.git'");
+ assert_string_equal(t, "!/bola1.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack ''\\!'/bola2.git/'");
+ assert_string_equal(t, "!/bola2.git/");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack ''\\!'bola3.git/'");
+ assert_string_equal(t, "!bola3.git/");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack ''\\!'foo/bola.git'");
+ assert_string_equal(t, "!foo/bola.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack ''\\!'/foo/bola1.git'");
+ assert_string_equal(t, "!/foo/bola1.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack ''\\!'/foo/bola2.git/'");
+ assert_string_equal(t, "!/foo/bola2.git/");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack ''\\!'foo/bola3.git/'");
+ assert_string_equal(t, "!foo/bola3.git/");
+ free(t);
+
+ t = bgr_shell_command_parse("git-receive-pack 'bola.git'\\!''");
+ assert_string_equal(t, "bola.git!");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/bola1.git'\\!''");
+ assert_string_equal(t, "bola1.git!");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/bola2.git/'\\!''");
+ assert_string_equal(t, "bola2.git/!");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack 'bola3.git/'\\!''");
+ assert_string_equal(t, "bola3.git/!");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack 'foo/bola.git'\\!''");
+ assert_string_equal(t, "foo/bola.git!");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/foo/bola1.git'\\!''");
+ assert_string_equal(t, "foo/bola1.git!");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/foo/bola2.git/'\\!''");
+ assert_string_equal(t, "foo/bola2.git/!");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack 'foo/bola3.git/'\\!''");
+ assert_string_equal(t, "foo/bola3.git/!");
+ free(t);
+
+ t = bgr_shell_command_parse("git-receive-pack 'bo'\\!'la.git'");
+ assert_string_equal(t, "bo!la.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/bo'\\!'la1.git'");
+ assert_string_equal(t, "bo!la1.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/bo'\\!'la2.git/'");
+ assert_string_equal(t, "bo!la2.git/");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack 'bo'\\!'la3.git/'");
+ assert_string_equal(t, "bo!la3.git/");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack 'foo/bo'\\!'la.git'");
+ assert_string_equal(t, "foo/bo!la.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/foo/bo'\\!'la1.git'");
+ assert_string_equal(t, "foo/bo!la1.git");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack '/foo/bo'\\!'la2.git/'");
+ assert_string_equal(t, "foo/bo!la2.git/");
+ free(t);
+ t = bgr_shell_command_parse("git-receive-pack 'foo/bo'\\!'la3.git/'");
+ assert_string_equal(t, "foo/bo!la3.git/");
+ free(t);
+}
+
+
+static void
+test_shell_quote(void **state)
+{
+ char *t;
+ t = bgr_shell_quote(NULL);
+ assert_string_equal(t, "''");
+ free(t);
+ t = bgr_shell_quote("!bola");
+ assert_string_equal(t, "''\\!'bola'");
+ free(t);
+ t = bgr_shell_quote("'bola");
+ assert_string_equal(t, "''\\''bola'");
+ free(t);
+ t = bgr_shell_quote("bo!bola");
+ assert_string_equal(t, "'bo'\\!'bola'");
+ free(t);
+ t = bgr_shell_quote("bo'bola");
+ assert_string_equal(t, "'bo'\\''bola'");
+ free(t);
+ t = bgr_shell_quote("bola!");
+ assert_string_equal(t, "'bola'\\!''");
+ free(t);
+ t = bgr_shell_quote("bola'");
+ assert_string_equal(t, "'bola'\\'''");
+ free(t);
+}
+
+
+int
+main(void)
+{
+ const UnitTest tests[] = {
+ unit_test(test_shell_command_parse),
+ unit_test(test_shell_quote),
+ };
+ return run_tests(tests);
+}