]> git.proxmox.com Git - libgit2.git/blobdiff - src/config_parse.c
New upstream version 1.3.0+dfsg.1
[libgit2.git] / src / config_parse.c
index d4c1c2bbd00ffeecedbc3131229197dd63c5cc7d..9f95e67d78c59df575c6aa8bf72fe4397b16ba9b 100644 (file)
@@ -7,14 +7,21 @@
 
 #include "config_parse.h"
 
-#include "buf_text.h"
-
 #include <ctype.h>
 
+const char *git_config_escapes = "ntb\"\\";
+const char *git_config_escaped = "\n\t\b\"\\";
+
 static void set_parse_error(git_config_parser *reader, int col, const char *error_str)
 {
-       giterr_set(GITERR_CONFIG, "failed to parse config file: %s (in %s:%"PRIuZ", column %d)",
-               error_str, reader->file->path, reader->ctx.line_num, col);
+       if (col)
+               git_error_set(GIT_ERROR_CONFIG,
+                             "failed to parse config file: %s (in %s:%"PRIuZ", column %d)",
+                             error_str, reader->path, reader->ctx.line_num, col);
+       else
+               git_error_set(GIT_ERROR_CONFIG,
+                             "failed to parse config file: %s (in %s:%"PRIuZ")",
+                             error_str, reader->path, reader->ctx.line_num);
 }
 
 
@@ -55,35 +62,40 @@ static int strip_comments(char *line, int in_quotes)
 }
 
 
-static int parse_section_header_ext(git_config_parser *reader, const char *line, const char *base_name, char **section_name)
+static int parse_subsection_header(git_config_parser *reader, const char *line, size_t pos, const char *base_name, char **section_name)
 {
        int c, rpos;
-       char *first_quote, *last_quote;
+       const char *first_quote, *last_quote;
+       const char *line_start = line;
        git_buf buf = GIT_BUF_INIT;
        size_t quoted_len, alloc_len, base_name_len = strlen(base_name);
 
-       /*
-        * base_name is what came before the space. We should be at the
-        * first quotation mark, except for now, line isn't being kept in
-        * sync so we only really use it to calculate the length.
-        */
+       /* Skip any additional whitespace before our section name */
+       while (git__isspace(line[pos]))
+               pos++;
 
-       first_quote = strchr(line, '"');
-       if (first_quote == NULL) {
-               set_parse_error(reader, 0, "Missing quotation marks in section header");
+       /* We should be at the first quotation mark. */
+       if (line[pos] != '"') {
+               set_parse_error(reader, 0, "missing quotation marks in section header");
                goto end_error;
        }
 
+       first_quote = &line[pos];
        last_quote = strrchr(line, '"');
        quoted_len = last_quote - first_quote;
 
+       if ((last_quote - line) > INT_MAX) {
+               set_parse_error(reader, 0, "invalid section header, line too long");
+               goto end_error;
+       }
+
        if (quoted_len == 0) {
-               set_parse_error(reader, 0, "Missing closing quotation mark in section header");
+               set_parse_error(reader, 0, "missing closing quotation mark in section header");
                goto end_error;
        }
 
-       GITERR_CHECK_ALLOC_ADD(&alloc_len, base_name_len, quoted_len);
-       GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2);
+       GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, base_name_len, quoted_len);
+       GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2);
 
        if (git_buf_grow(&buf, alloc_len) < 0 ||
            git_buf_printf(&buf, "%s.", base_name) < 0)
@@ -102,7 +114,7 @@ static int parse_section_header_ext(git_config_parser *reader, const char *line,
 
                switch (c) {
                case 0:
-                       set_parse_error(reader, 0, "Unexpected end-of-line in section header");
+                       set_parse_error(reader, 0, "unexpected end-of-line in section header");
                        goto end_error;
 
                case '"':
@@ -112,7 +124,7 @@ static int parse_section_header_ext(git_config_parser *reader, const char *line,
                        c = line[++rpos];
 
                        if (c == 0) {
-                               set_parse_error(reader, rpos, "Unexpected end-of-line in section header");
+                               set_parse_error(reader, rpos, "unexpected end-of-line in section header");
                                goto end_error;
                        }
 
@@ -129,16 +141,16 @@ end_parse:
                goto end_error;
 
        if (line[rpos] != '"' || line[rpos + 1] != ']') {
-               set_parse_error(reader, rpos, "Unexpected text after closing quotes");
-               git_buf_free(&buf);
+               set_parse_error(reader, rpos, "unexpected text after closing quotes");
+               git_buf_dispose(&buf);
                return -1;
        }
 
        *section_name = git_buf_detach(&buf);
-       return 0;
+       return (int)(&line[rpos + 2] - line_start); /* rpos is at the closing quote */
 
 end_error:
-       git_buf_free(&buf);
+       git_buf_dispose(&buf);
 
        return -1;
 }
@@ -160,34 +172,34 @@ static int parse_section_header(git_config_parser *reader, char **section_out)
        name_end = strrchr(line, ']');
        if (name_end == NULL) {
                git__free(line);
-               set_parse_error(reader, 0, "Missing ']' in section header");
+               set_parse_error(reader, 0, "missing ']' in section header");
                return -1;
        }
 
-       GITERR_CHECK_ALLOC_ADD(&line_len, (size_t)(name_end - line), 1);
+       GIT_ERROR_CHECK_ALLOC_ADD(&line_len, (size_t)(name_end - line), 1);
        name = git__malloc(line_len);
-       GITERR_CHECK_ALLOC(name);
+       GIT_ERROR_CHECK_ALLOC(name);
 
        name_length = 0;
        pos = 0;
 
        /* Make sure we were given a section header */
        c = line[pos++];
-       assert(c == '[');
+       GIT_ASSERT(c == '[');
 
        c = line[pos++];
 
        do {
                if (git__isspace(c)){
                        name[name_length] = '\0';
-                       result = parse_section_header_ext(reader, line, name, section_out);
+                       result = parse_subsection_header(reader, line, pos, name, section_out);
                        git__free(line);
                        git__free(name);
                        return result;
                }
 
                if (!config_keychar(c) && c != '.') {
-                       set_parse_error(reader, pos, "Unexpected character in header");
+                       set_parse_error(reader, pos, "unexpected character in header");
                        goto fail_parse;
                }
 
@@ -196,7 +208,7 @@ static int parse_section_header(git_config_parser *reader, char **section_out)
        } while ((c = line[pos++]) != ']');
 
        if (line[pos - 1] != ']') {
-               set_parse_error(reader, pos, "Unexpected end of file");
+               set_parse_error(reader, pos, "unexpected end of file");
                goto fail_parse;
        }
 
@@ -205,7 +217,7 @@ static int parse_section_header(git_config_parser *reader, char **section_out)
        name[name_length] = 0;
        *section_out = name;
 
-       return 0;
+       return pos;
 
 fail_parse:
        git__free(line);
@@ -216,10 +228,10 @@ fail_parse:
 static int skip_bom(git_parse_ctx *parser)
 {
        git_buf buf = GIT_BUF_INIT_CONST(parser->content, parser->content_len);
-       git_bom_t bom;
-       int bom_offset = git_buf_text_detect_bom(&bom, &buf);
+       git_buf_bom_t bom;
+       int bom_offset = git_buf_detect_bom(&bom, &buf);
 
-       if (bom == GIT_BOM_UTF8)
+       if (bom == GIT_BUF_BOM_UTF8)
                git_parse_advance_chars(parser, bom_offset);
 
        /* TODO: reference implementation is pretty stupid with BoM */
@@ -299,7 +311,7 @@ static int unescape_line(
                                *fixed++ = git_config_escaped[esc - git_config_escapes];
                        } else {
                                git__free(str);
-                               giterr_set(GITERR_CONFIG, "invalid escape at %s", ptr);
+                               git_error_set(GIT_ERROR_CONFIG, "invalid escape at %s", ptr);
                                return -1;
                        }
                }
@@ -325,7 +337,7 @@ static int parse_multiline_variable(git_config_parser *reader, git_buf *value, i
                /* Check that the next line exists */
                git_parse_advance_line(&reader->ctx);
                line = git__strndup(reader->ctx.line, reader->ctx.line_len);
-               GITERR_CHECK_ALLOC(line);
+               GIT_ERROR_CHECK_ALLOC(line);
 
                /*
                 * We've reached the end of the file, there is no continuation.
@@ -337,7 +349,7 @@ static int parse_multiline_variable(git_config_parser *reader, git_buf *value, i
                }
 
                /* If it was just a comment, pretend it didn't exist */
-               quote_count = strip_comments(line, !!in_quotes);
+               quote_count = strip_comments(line, in_quotes);
                if (line[0] == '\0')
                        goto next;
 
@@ -381,7 +393,7 @@ static int parse_name(
                name_end++;
 
        if (line == name_end) {
-               set_parse_error(reader, 0, "Invalid configuration key");
+               set_parse_error(reader, 0, "invalid configuration key");
                return -1;
        }
 
@@ -393,7 +405,7 @@ static int parse_name(
        if (*value_start == '=') {
                *value = value_start + 1;
        } else if (*value_start) {
-               set_parse_error(reader, 0, "Invalid configuration key");
+               set_parse_error(reader, 0, "invalid configuration key");
                return -1;
        }
 
@@ -406,22 +418,21 @@ static int parse_name(
 static int parse_variable(git_config_parser *reader, char **var_name, char **var_value)
 {
        const char *value_start = NULL;
-       char *line;
-       int quote_count;
+       char *line = NULL, *name = NULL, *value = NULL;
+       int quote_count, error;
        bool multiline;
 
+       *var_name = NULL;
+       *var_value = NULL;
+
        git_parse_advance_ws(&reader->ctx);
        line = git__strndup(reader->ctx.line, reader->ctx.line_len);
-       if (line == NULL)
-               return -1;
+       GIT_ERROR_CHECK_ALLOC(line);
 
        quote_count = strip_comments(line, 0);
 
-       /* If there is no value, boolean true is assumed */
-       *var_value = NULL;
-
-       if (parse_name(var_name, &value_start, reader, line) < 0)
-               goto on_error;
+       if ((error = parse_name(&name, &value_start, reader, line)) < 0)
+               goto out;
 
        /*
         * Now, let's try to parse the value
@@ -430,30 +441,46 @@ static int parse_variable(git_config_parser *reader, char **var_name, char **var
                while (git__isspace(value_start[0]))
                        value_start++;
 
-               if (unescape_line(var_value, &multiline, value_start, 0) < 0)
-                       goto on_error;
+               if ((error = unescape_line(&value, &multiline, value_start, 0)) < 0)
+                       goto out;
 
                if (multiline) {
                        git_buf multi_value = GIT_BUF_INIT;
-                       git_buf_attach(&multi_value, *var_value, 0);
-
-                       if (parse_multiline_variable(reader, &multi_value, quote_count) < 0 ||
-                               git_buf_oom(&multi_value)) {
-                               git_buf_free(&multi_value);
-                               goto on_error;
+                       git_buf_attach(&multi_value, value, 0);
+                       value = NULL;
+
+                       if (parse_multiline_variable(reader, &multi_value, quote_count % 2) < 0 ||
+                           git_buf_oom(&multi_value)) {
+                               error = -1;
+                               git_buf_dispose(&multi_value);
+                               goto out;
                        }
 
-                       *var_value = git_buf_detach(&multi_value);
+                       value = git_buf_detach(&multi_value);
                }
        }
 
-       git__free(line);
-       return 0;
+       *var_name = name;
+       *var_value = value;
+       name = NULL;
+       value = NULL;
 
-on_error:
-       git__free(*var_name);
+out:
+       git__free(name);
+       git__free(value);
        git__free(line);
-       return -1;
+       return error;
+}
+
+int git_config_parser_init(git_config_parser *out, const char *path, const char *data, size_t datalen)
+{
+       out->path = path;
+       return git_parse_ctx_init(&out->ctx, data, datalen);
+}
+
+void git_config_parser_dispose(git_config_parser *parser)
+{
+       git_parse_ctx_clear(&parser->ctx);
 }
 
 int git_config_parse(
@@ -462,10 +489,10 @@ int git_config_parse(
        git_config_parser_variable_cb on_variable,
        git_config_parser_comment_cb on_comment,
        git_config_parser_eof_cb on_eof,
-       void *data)
+       void *payload)
 {
        git_parse_ctx *ctx;
-       char *current_section = NULL, *var_name, *var_value;
+       char *current_section = NULL, *var_name = NULL, *var_value = NULL;
        int result = 0;
 
        ctx = &parser->ctx;
@@ -473,10 +500,14 @@ int git_config_parse(
        skip_bom(ctx);
 
        for (; ctx->remain_len > 0; git_parse_advance_line(ctx)) {
-               const char *line_start = parser->ctx.line;
-               size_t line_len = parser->ctx.line_len;
+               const char *line_start;
+               size_t line_len;
                char c;
 
+       restart:
+               line_start = ctx->line;
+               line_len = ctx->line_len;
+
                /*
                 * Get either first non-whitespace character or, if that does
                 * not exist, the first whitespace character. This is required
@@ -491,9 +522,24 @@ int git_config_parse(
                        git__free(current_section);
                        current_section = NULL;
 
-                       if ((result = parse_section_header(parser, &current_section)) == 0 && on_section) {
-                               result = on_section(parser, current_section, line_start, line_len, data);
-                       }
+                       result = parse_section_header(parser, &current_section);
+                       if (result < 0)
+                               break;
+
+                       git_parse_advance_chars(ctx, result);
+
+                       if (on_section)
+                               result = on_section(parser, current_section, line_start, line_len, payload);
+                       /*
+                        * After we've parsed the section header we may not be
+                        * done with the line. If there's still data in there,
+                        * run the next loop with the rest of the current line
+                        * instead of moving forward.
+                        */
+
+                       if (!git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE))
+                               goto restart;
+
                        break;
 
                case '\n': /* comment or whitespace-only */
@@ -503,14 +549,17 @@ int git_config_parse(
                case ';':
                case '#':
                        if (on_comment) {
-                               result = on_comment(parser, line_start, line_len, data);
+                               result = on_comment(parser, line_start, line_len, payload);
                        }
                        break;
 
                default: /* assume variable declaration */
                        if ((result = parse_variable(parser, &var_name, &var_value)) == 0 && on_variable) {
-                               result = on_variable(parser, current_section, var_name, var_value, line_start, line_len, data);
+                               result = on_variable(parser, current_section, var_name, var_value, line_start, line_len, payload);
+                               git__free(var_name);
+                               git__free(var_value);
                        }
+
                        break;
                }
 
@@ -519,7 +568,7 @@ int git_config_parse(
        }
 
        if (on_eof)
-               result = on_eof(parser, current_section, data);
+               result = on_eof(parser, current_section, payload);
 
 out:
        git__free(current_section);