#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);
}
}
-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)
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 '"':
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;
}
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;
}
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;
}
} 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;
}
name[name_length] = 0;
*section_out = name;
- return 0;
+ return pos;
fail_parse:
git__free(line);
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 */
*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;
}
}
/* 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.
}
/* 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;
name_end++;
if (line == name_end) {
- set_parse_error(reader, 0, "Invalid configuration key");
+ set_parse_error(reader, 0, "invalid configuration key");
return -1;
}
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;
}
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
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(
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;
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
git__free(current_section);
current_section = NULL;
- if ((result = parse_section_header(parser, ¤t_section)) == 0 && on_section) {
- result = on_section(parser, current_section, line_start, line_len, data);
- }
+ result = parse_section_header(parser, ¤t_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 */
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;
}
}
if (on_eof)
- result = on_eof(parser, current_section, data);
+ result = on_eof(parser, current_section, payload);
out:
git__free(current_section);