2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
8 #include "config_parse.h"
12 const char *git_config_escapes
= "ntb\"\\";
13 const char *git_config_escaped
= "\n\t\b\"\\";
15 static void set_parse_error(git_config_parser
*reader
, int col
, const char *error_str
)
18 git_error_set(GIT_ERROR_CONFIG
,
19 "failed to parse config file: %s (in %s:%"PRIuZ
", column %d)",
20 error_str
, reader
->path
, reader
->ctx
.line_num
, col
);
22 git_error_set(GIT_ERROR_CONFIG
,
23 "failed to parse config file: %s (in %s:%"PRIuZ
")",
24 error_str
, reader
->path
, reader
->ctx
.line_num
);
28 GIT_INLINE(int) config_keychar(int c
)
30 return isalnum(c
) || c
== '-';
33 static int strip_comments(char *line
, int in_quotes
)
35 int quote_count
= in_quotes
, backslash_count
= 0;
38 for (ptr
= line
; *ptr
; ++ptr
) {
39 if (ptr
[0] == '"' && ptr
> line
&& ptr
[-1] != '\\')
42 if ((ptr
[0] == ';' || ptr
[0] == '#') &&
43 (quote_count
% 2) == 0 &&
44 (backslash_count
% 2) == 0) {
55 /* skip any space at the end */
56 while (ptr
> line
&& git__isspace(ptr
[-1])) {
65 static int parse_subsection_header(git_config_parser
*reader
, const char *line
, size_t pos
, const char *base_name
, char **section_name
)
68 const char *first_quote
, *last_quote
;
69 const char *line_start
= line
;
70 git_buf buf
= GIT_BUF_INIT
;
71 size_t quoted_len
, alloc_len
, base_name_len
= strlen(base_name
);
73 /* Skip any additional whitespace before our section name */
74 while (git__isspace(line
[pos
]))
77 /* We should be at the first quotation mark. */
78 if (line
[pos
] != '"') {
79 set_parse_error(reader
, 0, "missing quotation marks in section header");
83 first_quote
= &line
[pos
];
84 last_quote
= strrchr(line
, '"');
85 quoted_len
= last_quote
- first_quote
;
87 if ((last_quote
- line
) > INT_MAX
) {
88 set_parse_error(reader
, 0, "invalid section header, line too long");
92 if (quoted_len
== 0) {
93 set_parse_error(reader
, 0, "missing closing quotation mark in section header");
97 GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len
, base_name_len
, quoted_len
);
98 GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len
, alloc_len
, 2);
100 if (git_buf_grow(&buf
, alloc_len
) < 0 ||
101 git_buf_printf(&buf
, "%s.", base_name
) < 0)
110 * At the end of each iteration, whatever is stored in c will be
111 * added to the string. In case of error, jump to out
117 set_parse_error(reader
, 0, "unexpected end-of-line in section header");
127 set_parse_error(reader
, rpos
, "unexpected end-of-line in section header");
135 git_buf_putc(&buf
, (char)c
);
137 } while (line
+ rpos
< last_quote
);
140 if (git_buf_oom(&buf
))
143 if (line
[rpos
] != '"' || line
[rpos
+ 1] != ']') {
144 set_parse_error(reader
, rpos
, "unexpected text after closing quotes");
145 git_buf_dispose(&buf
);
149 *section_name
= git_buf_detach(&buf
);
150 return (int)(&line
[rpos
+ 2] - line_start
); /* rpos is at the closing quote */
153 git_buf_dispose(&buf
);
158 static int parse_section_header(git_config_parser
*reader
, char **section_out
)
160 char *name
, *name_end
;
161 int name_length
, c
, pos
;
166 git_parse_advance_ws(&reader
->ctx
);
167 line
= git__strndup(reader
->ctx
.line
, reader
->ctx
.line_len
);
171 /* find the end of the variable's name */
172 name_end
= strrchr(line
, ']');
173 if (name_end
== NULL
) {
175 set_parse_error(reader
, 0, "missing ']' in section header");
179 GIT_ERROR_CHECK_ALLOC_ADD(&line_len
, (size_t)(name_end
- line
), 1);
180 name
= git__malloc(line_len
);
181 GIT_ERROR_CHECK_ALLOC(name
);
186 /* Make sure we were given a section header */
188 GIT_ASSERT(c
== '[');
193 if (git__isspace(c
)){
194 name
[name_length
] = '\0';
195 result
= parse_subsection_header(reader
, line
, pos
, name
, section_out
);
201 if (!config_keychar(c
) && c
!= '.') {
202 set_parse_error(reader
, pos
, "unexpected character in header");
206 name
[name_length
++] = (char)git__tolower(c
);
208 } while ((c
= line
[pos
++]) != ']');
210 if (line
[pos
- 1] != ']') {
211 set_parse_error(reader
, pos
, "unexpected end of file");
217 name
[name_length
] = 0;
228 static int skip_bom(git_parse_ctx
*parser
)
230 git_buf buf
= GIT_BUF_INIT_CONST(parser
->content
, parser
->content_len
);
232 int bom_offset
= git_buf_detect_bom(&bom
, &buf
);
234 if (bom
== GIT_BUF_BOM_UTF8
)
235 git_parse_advance_chars(parser
, bom_offset
);
237 /* TODO: reference implementation is pretty stupid with BoM */
245 integer = digit { digit }
246 alphabet = "a".."z" + "A" .. "Z"
248 section_char = alphabet | "." | "-"
249 extension_char = (* any character except newline *)
250 any_char = (* any character *)
251 variable_char = "alphabet" | "-"
257 section = header { definition }
259 header = "[" section [subsection | subsection_ext] "]"
261 subsection = "." section
262 subsection_ext = "\"" extension "\""
264 section = section_char { section_char }
265 extension = extension_char { extension_char }
267 definition = variable_name ["=" variable_value] "\n"
269 variable_name = variable_char { variable_char }
270 variable_value = string | boolean | integer
272 string = quoted_string | plain_string
273 quoted_string = "\"" plain_string "\""
274 plain_string = { any_char }
276 boolean = boolean_true | boolean_false
277 boolean_true = "yes" | "1" | "true" | "on"
278 boolean_false = "no" | "0" | "false" | "off"
281 /* '\"' -> '"' etc */
282 static int unescape_line(
283 char **out
, bool *is_multi
, const char *ptr
, int quote_count
)
285 char *str
, *fixed
, *esc
;
286 size_t ptr_len
= strlen(ptr
), alloc_len
;
290 if (GIT_ADD_SIZET_OVERFLOW(&alloc_len
, ptr_len
, 1) ||
291 (str
= git__malloc(alloc_len
)) == NULL
) {
297 while (*ptr
!= '\0') {
300 } else if (*ptr
!= '\\') {
303 /* backslash, check the next char */
305 /* if we're at the end, it's a multiline, so keep the backslash */
310 if ((esc
= strchr(git_config_escapes
, *ptr
)) != NULL
) {
311 *fixed
++ = git_config_escaped
[esc
- git_config_escapes
];
314 git_error_set(GIT_ERROR_CONFIG
, "invalid escape at %s", ptr
);
328 static int parse_multiline_variable(git_config_parser
*reader
, git_buf
*value
, int in_quotes
)
331 bool multiline
= true;
334 char *line
= NULL
, *proc_line
= NULL
;
337 /* Check that the next line exists */
338 git_parse_advance_line(&reader
->ctx
);
339 line
= git__strndup(reader
->ctx
.line
, reader
->ctx
.line_len
);
340 GIT_ERROR_CHECK_ALLOC(line
);
343 * We've reached the end of the file, there is no continuation.
344 * (this is not an error).
346 if (line
[0] == '\0') {
351 /* If it was just a comment, pretend it didn't exist */
352 quote_count
= strip_comments(line
, in_quotes
);
356 if ((error
= unescape_line(&proc_line
, &multiline
,
357 line
, in_quotes
)) < 0)
360 /* Add this line to the multiline var */
361 if ((error
= git_buf_puts(value
, proc_line
)) < 0)
366 git__free(proc_line
);
367 in_quotes
= quote_count
;
372 git__free(proc_line
);
379 GIT_INLINE(bool) is_namechar(char c
)
381 return isalnum(c
) || c
== '-';
384 static int parse_name(
385 char **name
, const char **value
, git_config_parser
*reader
, const char *line
)
387 const char *name_end
= line
, *value_start
;
392 while (*name_end
&& is_namechar(*name_end
))
395 if (line
== name_end
) {
396 set_parse_error(reader
, 0, "invalid configuration key");
400 value_start
= name_end
;
402 while (*value_start
&& git__isspace(*value_start
))
405 if (*value_start
== '=') {
406 *value
= value_start
+ 1;
407 } else if (*value_start
) {
408 set_parse_error(reader
, 0, "invalid configuration key");
412 if ((*name
= git__strndup(line
, name_end
- line
)) == NULL
)
418 static int parse_variable(git_config_parser
*reader
, char **var_name
, char **var_value
)
420 const char *value_start
= NULL
;
421 char *line
= NULL
, *name
= NULL
, *value
= NULL
;
422 int quote_count
, error
;
428 git_parse_advance_ws(&reader
->ctx
);
429 line
= git__strndup(reader
->ctx
.line
, reader
->ctx
.line_len
);
430 GIT_ERROR_CHECK_ALLOC(line
);
432 quote_count
= strip_comments(line
, 0);
434 if ((error
= parse_name(&name
, &value_start
, reader
, line
)) < 0)
438 * Now, let's try to parse the value
440 if (value_start
!= NULL
) {
441 while (git__isspace(value_start
[0]))
444 if ((error
= unescape_line(&value
, &multiline
, value_start
, 0)) < 0)
448 git_buf multi_value
= GIT_BUF_INIT
;
449 git_buf_attach(&multi_value
, value
, 0);
452 if (parse_multiline_variable(reader
, &multi_value
, quote_count
% 2) < 0 ||
453 git_buf_oom(&multi_value
)) {
455 git_buf_dispose(&multi_value
);
459 value
= git_buf_detach(&multi_value
);
475 int git_config_parser_init(git_config_parser
*out
, const char *path
, const char *data
, size_t datalen
)
478 return git_parse_ctx_init(&out
->ctx
, data
, datalen
);
481 void git_config_parser_dispose(git_config_parser
*parser
)
483 git_parse_ctx_clear(&parser
->ctx
);
486 int git_config_parse(
487 git_config_parser
*parser
,
488 git_config_parser_section_cb on_section
,
489 git_config_parser_variable_cb on_variable
,
490 git_config_parser_comment_cb on_comment
,
491 git_config_parser_eof_cb on_eof
,
495 char *current_section
= NULL
, *var_name
= NULL
, *var_value
= NULL
;
502 for (; ctx
->remain_len
> 0; git_parse_advance_line(ctx
)) {
503 const char *line_start
;
508 line_start
= ctx
->line
;
509 line_len
= ctx
->line_len
;
512 * Get either first non-whitespace character or, if that does
513 * not exist, the first whitespace character. This is required
514 * to preserve whitespaces when writing back the file.
516 if (git_parse_peek(&c
, ctx
, GIT_PARSE_PEEK_SKIP_WHITESPACE
) < 0 &&
517 git_parse_peek(&c
, ctx
, 0) < 0)
521 case '[': /* section header, new section begins */
522 git__free(current_section
);
523 current_section
= NULL
;
525 result
= parse_section_header(parser
, ¤t_section
);
529 git_parse_advance_chars(ctx
, result
);
532 result
= on_section(parser
, current_section
, line_start
, line_len
, payload
);
534 * After we've parsed the section header we may not be
535 * done with the line. If there's still data in there,
536 * run the next loop with the rest of the current line
537 * instead of moving forward.
540 if (!git_parse_peek(&c
, ctx
, GIT_PARSE_PEEK_SKIP_WHITESPACE
))
545 case '\n': /* comment or whitespace-only */
552 result
= on_comment(parser
, line_start
, line_len
, payload
);
556 default: /* assume variable declaration */
557 if ((result
= parse_variable(parser
, &var_name
, &var_value
)) == 0 && on_variable
) {
558 result
= on_variable(parser
, current_section
, var_name
, var_value
, line_start
, line_len
, payload
);
560 git__free(var_value
);
571 result
= on_eof(parser
, current_section
, payload
);
574 git__free(current_section
);