From 2c1b2eaaef8726c71648b4a115fbe628f28d6b4b Mon Sep 17 00:00:00 2001 From: "Rafael G. Martins" Date: Fri, 7 Oct 2016 01:48:16 +0200 Subject: git-receiver: splitted/reimplemented and tested shell command parser --- .gitignore | 1 + Makefile.am | 21 ++ src/blogc-git-receiver/shell-command-parser.c | 125 +++++++++++ src/blogc-git-receiver/shell-command-parser.h | 15 ++ src/blogc-git-receiver/shell.c | 97 ++++---- src/blogc-git-receiver/shell.h | 1 + .../check_shell_command_parser.c | 244 +++++++++++++++++++++ 7 files changed, 448 insertions(+), 56 deletions(-) create mode 100644 src/blogc-git-receiver/shell-command-parser.c create mode 100644 src/blogc-git-receiver/shell-command-parser.h create mode 100644 tests/blogc-git-receiver/check_shell_command_parser.c 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 + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#include +#include +#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 + * + * 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 #include #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 + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#include +#include +#include +#include +#include +#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); +} -- cgit v1.2.3-18-g5258