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"
14 static void set_parse_error(git_config_parser
*reader
, int col
, const char *error_str
)
16 giterr_set(GITERR_CONFIG
, "failed to parse config file: %s (in %s:%"PRIuZ
", column %d)",
17 error_str
, reader
->file
->path
, reader
->ctx
.line_num
, col
);
21 GIT_INLINE(int) config_keychar(int c
)
23 return isalnum(c
) || c
== '-';
26 static int strip_comments(char *line
, int in_quotes
)
28 int quote_count
= in_quotes
, backslash_count
= 0;
31 for (ptr
= line
; *ptr
; ++ptr
) {
32 if (ptr
[0] == '"' && ptr
> line
&& ptr
[-1] != '\\')
35 if ((ptr
[0] == ';' || ptr
[0] == '#') &&
36 (quote_count
% 2) == 0 &&
37 (backslash_count
% 2) == 0) {
48 /* skip any space at the end */
49 while (ptr
> line
&& git__isspace(ptr
[-1])) {
58 static int parse_section_header_ext(git_config_parser
*reader
, const char *line
, const char *base_name
, char **section_name
)
61 char *first_quote
, *last_quote
;
62 git_buf buf
= GIT_BUF_INIT
;
63 size_t quoted_len
, alloc_len
, base_name_len
= strlen(base_name
);
66 * base_name is what came before the space. We should be at the
67 * first quotation mark, except for now, line isn't being kept in
68 * sync so we only really use it to calculate the length.
71 first_quote
= strchr(line
, '"');
72 if (first_quote
== NULL
) {
73 set_parse_error(reader
, 0, "Missing quotation marks in section header");
77 last_quote
= strrchr(line
, '"');
78 quoted_len
= last_quote
- first_quote
;
80 if (quoted_len
== 0) {
81 set_parse_error(reader
, 0, "Missing closing quotation mark in section header");
85 GITERR_CHECK_ALLOC_ADD(&alloc_len
, base_name_len
, quoted_len
);
86 GITERR_CHECK_ALLOC_ADD(&alloc_len
, alloc_len
, 2);
88 if (git_buf_grow(&buf
, alloc_len
) < 0 ||
89 git_buf_printf(&buf
, "%s.", base_name
) < 0)
98 * At the end of each iteration, whatever is stored in c will be
99 * added to the string. In case of error, jump to out
105 set_parse_error(reader
, 0, "Unexpected end-of-line in section header");
115 set_parse_error(reader
, rpos
, "Unexpected end-of-line in section header");
123 git_buf_putc(&buf
, (char)c
);
125 } while (line
+ rpos
< last_quote
);
128 if (git_buf_oom(&buf
))
131 if (line
[rpos
] != '"' || line
[rpos
+ 1] != ']') {
132 set_parse_error(reader
, rpos
, "Unexpected text after closing quotes");
137 *section_name
= git_buf_detach(&buf
);
146 static int parse_section_header(git_config_parser
*reader
, char **section_out
)
148 char *name
, *name_end
;
149 int name_length
, c
, pos
;
154 git_parse_advance_ws(&reader
->ctx
);
155 line
= git__strndup(reader
->ctx
.line
, reader
->ctx
.line_len
);
159 /* find the end of the variable's name */
160 name_end
= strrchr(line
, ']');
161 if (name_end
== NULL
) {
163 set_parse_error(reader
, 0, "Missing ']' in section header");
167 GITERR_CHECK_ALLOC_ADD(&line_len
, (size_t)(name_end
- line
), 1);
168 name
= git__malloc(line_len
);
169 GITERR_CHECK_ALLOC(name
);
174 /* Make sure we were given a section header */
181 if (git__isspace(c
)){
182 name
[name_length
] = '\0';
183 result
= parse_section_header_ext(reader
, line
, name
, section_out
);
189 if (!config_keychar(c
) && c
!= '.') {
190 set_parse_error(reader
, pos
, "Unexpected character in header");
194 name
[name_length
++] = (char)git__tolower(c
);
196 } while ((c
= line
[pos
++]) != ']');
198 if (line
[pos
- 1] != ']') {
199 set_parse_error(reader
, pos
, "Unexpected end of file");
205 name
[name_length
] = 0;
216 static int skip_bom(git_parse_ctx
*parser
)
218 git_buf buf
= GIT_BUF_INIT_CONST(parser
->content
, parser
->content_len
);
220 int bom_offset
= git_buf_text_detect_bom(&bom
, &buf
);
222 if (bom
== GIT_BOM_UTF8
)
223 git_parse_advance_chars(parser
, bom_offset
);
225 /* TODO: reference implementation is pretty stupid with BoM */
233 integer = digit { digit }
234 alphabet = "a".."z" + "A" .. "Z"
236 section_char = alphabet | "." | "-"
237 extension_char = (* any character except newline *)
238 any_char = (* any character *)
239 variable_char = "alphabet" | "-"
245 section = header { definition }
247 header = "[" section [subsection | subsection_ext] "]"
249 subsection = "." section
250 subsection_ext = "\"" extension "\""
252 section = section_char { section_char }
253 extension = extension_char { extension_char }
255 definition = variable_name ["=" variable_value] "\n"
257 variable_name = variable_char { variable_char }
258 variable_value = string | boolean | integer
260 string = quoted_string | plain_string
261 quoted_string = "\"" plain_string "\""
262 plain_string = { any_char }
264 boolean = boolean_true | boolean_false
265 boolean_true = "yes" | "1" | "true" | "on"
266 boolean_false = "no" | "0" | "false" | "off"
269 /* '\"' -> '"' etc */
270 static int unescape_line(
271 char **out
, bool *is_multi
, const char *ptr
, int quote_count
)
273 char *str
, *fixed
, *esc
;
274 size_t ptr_len
= strlen(ptr
), alloc_len
;
278 if (GIT_ADD_SIZET_OVERFLOW(&alloc_len
, ptr_len
, 1) ||
279 (str
= git__malloc(alloc_len
)) == NULL
) {
285 while (*ptr
!= '\0') {
288 } else if (*ptr
!= '\\') {
291 /* backslash, check the next char */
293 /* if we're at the end, it's a multiline, so keep the backslash */
298 if ((esc
= strchr(git_config_escapes
, *ptr
)) != NULL
) {
299 *fixed
++ = git_config_escaped
[esc
- git_config_escapes
];
302 giterr_set(GITERR_CONFIG
, "invalid escape at %s", ptr
);
316 static int parse_multiline_variable(git_config_parser
*reader
, git_buf
*value
, int in_quotes
)
319 bool multiline
= true;
322 char *line
= NULL
, *proc_line
= NULL
;
325 /* Check that the next line exists */
326 git_parse_advance_line(&reader
->ctx
);
327 line
= git__strndup(reader
->ctx
.line
, reader
->ctx
.line_len
);
328 GITERR_CHECK_ALLOC(line
);
331 * We've reached the end of the file, there is no continuation.
332 * (this is not an error).
334 if (line
[0] == '\0') {
339 /* If it was just a comment, pretend it didn't exist */
340 quote_count
= strip_comments(line
, !!in_quotes
);
344 if ((error
= unescape_line(&proc_line
, &multiline
,
345 line
, in_quotes
)) < 0)
348 /* Add this line to the multiline var */
349 if ((error
= git_buf_puts(value
, proc_line
)) < 0)
354 git__free(proc_line
);
355 in_quotes
= quote_count
;
360 git__free(proc_line
);
367 GIT_INLINE(bool) is_namechar(char c
)
369 return isalnum(c
) || c
== '-';
372 static int parse_name(
373 char **name
, const char **value
, git_config_parser
*reader
, const char *line
)
375 const char *name_end
= line
, *value_start
;
380 while (*name_end
&& is_namechar(*name_end
))
383 if (line
== name_end
) {
384 set_parse_error(reader
, 0, "Invalid configuration key");
388 value_start
= name_end
;
390 while (*value_start
&& git__isspace(*value_start
))
393 if (*value_start
== '=') {
394 *value
= value_start
+ 1;
395 } else if (*value_start
) {
396 set_parse_error(reader
, 0, "Invalid configuration key");
400 if ((*name
= git__strndup(line
, name_end
- line
)) == NULL
)
406 static int parse_variable(git_config_parser
*reader
, char **var_name
, char **var_value
)
408 const char *value_start
= NULL
;
413 git_parse_advance_ws(&reader
->ctx
);
414 line
= git__strndup(reader
->ctx
.line
, reader
->ctx
.line_len
);
418 quote_count
= strip_comments(line
, 0);
420 /* If there is no value, boolean true is assumed */
423 if (parse_name(var_name
, &value_start
, reader
, line
) < 0)
427 * Now, let's try to parse the value
429 if (value_start
!= NULL
) {
430 while (git__isspace(value_start
[0]))
433 if (unescape_line(var_value
, &multiline
, value_start
, 0) < 0)
437 git_buf multi_value
= GIT_BUF_INIT
;
438 git_buf_attach(&multi_value
, *var_value
, 0);
440 if (parse_multiline_variable(reader
, &multi_value
, quote_count
) < 0 ||
441 git_buf_oom(&multi_value
)) {
442 git_buf_free(&multi_value
);
446 *var_value
= git_buf_detach(&multi_value
);
454 git__free(*var_name
);
459 int git_config_parse(
460 git_config_parser
*parser
,
461 git_config_parser_section_cb on_section
,
462 git_config_parser_variable_cb on_variable
,
463 git_config_parser_comment_cb on_comment
,
464 git_config_parser_eof_cb on_eof
,
468 char *current_section
= NULL
, *var_name
, *var_value
;
475 for (; ctx
->remain_len
> 0; git_parse_advance_line(ctx
)) {
476 const char *line_start
= parser
->ctx
.line
;
477 size_t line_len
= parser
->ctx
.line_len
;
481 * Get either first non-whitespace character or, if that does
482 * not exist, the first whitespace character. This is required
483 * to preserve whitespaces when writing back the file.
485 if (git_parse_peek(&c
, ctx
, GIT_PARSE_PEEK_SKIP_WHITESPACE
) < 0 &&
486 git_parse_peek(&c
, ctx
, 0) < 0)
490 case '[': /* section header, new section begins */
491 git__free(current_section
);
492 current_section
= NULL
;
494 if ((result
= parse_section_header(parser
, ¤t_section
)) == 0 && on_section
) {
495 result
= on_section(parser
, current_section
, line_start
, line_len
, data
);
499 case '\n': /* comment or whitespace-only */
506 result
= on_comment(parser
, line_start
, line_len
, data
);
510 default: /* assume variable declaration */
511 if ((result
= parse_variable(parser
, &var_name
, &var_value
)) == 0 && on_variable
) {
512 result
= on_variable(parser
, current_section
, var_name
, var_value
, line_start
, line_len
, data
);
522 result
= on_eof(parser
, current_section
, data
);
525 git__free(current_section
);