]> git.proxmox.com Git - libgit2.git/blob - src/config_file.c
Merge pull request #968 from arrbee/diff-support-typechange
[libgit2.git] / src / config_file.c
1 /*
2 * Copyright (C) 2009-2012 the libgit2 contributors
3 *
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.
6 */
7
8 #include "common.h"
9 #include "config.h"
10 #include "fileops.h"
11 #include "filebuf.h"
12 #include "buffer.h"
13 #include "git2/config.h"
14 #include "git2/types.h"
15 #include "strmap.h"
16
17 #include <ctype.h>
18 #include <sys/types.h>
19 #include <regex.h>
20
21 GIT__USE_STRMAP;
22
23 typedef struct cvar_t {
24 struct cvar_t *next;
25 char *key; /* TODO: we might be able to get rid of this */
26 char *value;
27 } cvar_t;
28
29 typedef struct {
30 struct cvar_t *head;
31 struct cvar_t *tail;
32 } cvar_t_list;
33
34 #define CVAR_LIST_HEAD(list) ((list)->head)
35
36 #define CVAR_LIST_TAIL(list) ((list)->tail)
37
38 #define CVAR_LIST_NEXT(var) ((var)->next)
39
40 #define CVAR_LIST_EMPTY(list) ((list)->head == NULL)
41
42 #define CVAR_LIST_APPEND(list, var) do {\
43 if (CVAR_LIST_EMPTY(list)) {\
44 CVAR_LIST_HEAD(list) = CVAR_LIST_TAIL(list) = var;\
45 } else {\
46 CVAR_LIST_NEXT(CVAR_LIST_TAIL(list)) = var;\
47 CVAR_LIST_TAIL(list) = var;\
48 }\
49 } while(0)
50
51 #define CVAR_LIST_REMOVE_HEAD(list) do {\
52 CVAR_LIST_HEAD(list) = CVAR_LIST_NEXT(CVAR_LIST_HEAD(list));\
53 } while(0)
54
55 #define CVAR_LIST_REMOVE_AFTER(var) do {\
56 CVAR_LIST_NEXT(var) = CVAR_LIST_NEXT(CVAR_LIST_NEXT(var));\
57 } while(0)
58
59 #define CVAR_LIST_FOREACH(list, iter)\
60 for ((iter) = CVAR_LIST_HEAD(list);\
61 (iter) != NULL;\
62 (iter) = CVAR_LIST_NEXT(iter))
63
64 /*
65 * Inspired by the FreeBSD functions
66 */
67 #define CVAR_LIST_FOREACH_SAFE(start, iter, tmp)\
68 for ((iter) = CVAR_LIST_HEAD(vars);\
69 (iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\
70 (iter) = (tmp))
71
72 typedef struct {
73 git_config_file parent;
74
75 git_strmap *values;
76
77 struct {
78 git_buf buffer;
79 char *read_ptr;
80 int line_number;
81 int eof;
82 } reader;
83
84 char *file_path;
85 } diskfile_backend;
86
87 static int config_parse(diskfile_backend *cfg_file);
88 static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value);
89 static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value);
90 static char *escape_value(const char *ptr);
91
92 static void set_parse_error(diskfile_backend *backend, int col, const char *error_str)
93 {
94 giterr_set(GITERR_CONFIG, "Failed to parse config file: %s (in %s:%d, column %d)",
95 error_str, backend->file_path, backend->reader.line_number, col);
96 }
97
98 static void cvar_free(cvar_t *var)
99 {
100 if (var == NULL)
101 return;
102
103 git__free(var->key);
104 git__free(var->value);
105 git__free(var);
106 }
107
108 /* Take something the user gave us and make it nice for our hash function */
109 static int normalize_name(const char *in, char **out)
110 {
111 char *name, *fdot, *ldot;
112
113 assert(in && out);
114
115 name = git__strdup(in);
116 GITERR_CHECK_ALLOC(name);
117
118 fdot = strchr(name, '.');
119 ldot = strrchr(name, '.');
120
121 if (fdot == NULL || ldot == NULL) {
122 git__free(name);
123 giterr_set(GITERR_CONFIG,
124 "Invalid variable name: '%s'", in);
125 return -1;
126 }
127
128 /* Downcase up to the first dot and after the last one */
129 git__strntolower(name, fdot - name);
130 git__strtolower(ldot);
131
132 *out = name;
133 return 0;
134 }
135
136 static void free_vars(git_strmap *values)
137 {
138 cvar_t *var = NULL;
139
140 if (values == NULL)
141 return;
142
143 git_strmap_foreach_value(values, var,
144 while (var != NULL) {
145 cvar_t *next = CVAR_LIST_NEXT(var);
146 cvar_free(var);
147 var = next;
148 });
149
150 git_strmap_free(values);
151 }
152
153 static int config_open(git_config_file *cfg)
154 {
155 int res;
156 diskfile_backend *b = (diskfile_backend *)cfg;
157
158 b->values = git_strmap_alloc();
159 GITERR_CHECK_ALLOC(b->values);
160
161 git_buf_init(&b->reader.buffer, 0);
162 res = git_futils_readbuffer(&b->reader.buffer, b->file_path);
163
164 /* It's fine if the file doesn't exist */
165 if (res == GIT_ENOTFOUND)
166 return 0;
167
168 if (res < 0 || config_parse(b) < 0) {
169 free_vars(b->values);
170 b->values = NULL;
171 git_buf_free(&b->reader.buffer);
172 return -1;
173 }
174
175 git_buf_free(&b->reader.buffer);
176 return 0;
177 }
178
179 static void backend_free(git_config_file *_backend)
180 {
181 diskfile_backend *backend = (diskfile_backend *)_backend;
182
183 if (backend == NULL)
184 return;
185
186 git__free(backend->file_path);
187 free_vars(backend->values);
188 git__free(backend);
189 }
190
191 static int file_foreach(
192 git_config_file *backend,
193 const char *regexp,
194 int (*fn)(const char *, const char *, void *),
195 void *data)
196 {
197 diskfile_backend *b = (diskfile_backend *)backend;
198 cvar_t *var, *next_var;
199 const char *key;
200 regex_t regex;
201 int result = 0;
202
203 if (!b->values)
204 return 0;
205
206 if (regexp != NULL) {
207 if ((result = regcomp(&regex, regexp, REG_EXTENDED)) < 0) {
208 giterr_set_regex(&regex, result);
209 regfree(&regex);
210 return -1;
211 }
212 }
213
214 git_strmap_foreach(b->values, key, var,
215 for (; var != NULL; var = next_var) {
216 next_var = CVAR_LIST_NEXT(var);
217
218 /* skip non-matching keys if regexp was provided */
219 if (regexp && regexec(&regex, key, 0, NULL, 0) != 0)
220 continue;
221
222 /* abort iterator on non-zero return value */
223 if (fn(key, var->value, data)) {
224 giterr_clear();
225 result = GIT_EUSER;
226 goto cleanup;
227 }
228 }
229 );
230
231 cleanup:
232 if (regexp != NULL)
233 regfree(&regex);
234
235 return result;
236 }
237
238 static int config_set(git_config_file *cfg, const char *name, const char *value)
239 {
240 cvar_t *var = NULL, *old_var;
241 diskfile_backend *b = (diskfile_backend *)cfg;
242 char *key, *esc_value = NULL;
243 khiter_t pos;
244 int rval, ret;
245
246 if (normalize_name(name, &key) < 0)
247 return -1;
248
249 /*
250 * Try to find it in the existing values and update it if it
251 * only has one value.
252 */
253 pos = git_strmap_lookup_index(b->values, key);
254 if (git_strmap_valid_index(b->values, pos)) {
255 cvar_t *existing = git_strmap_value_at(b->values, pos);
256 char *tmp = NULL;
257
258 git__free(key);
259
260 if (existing->next != NULL) {
261 giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set");
262 return -1;
263 }
264
265 /* don't update if old and new values already match */
266 if ((!existing->value && !value) ||
267 (existing->value && value && !strcmp(existing->value, value)))
268 return 0;
269
270 if (value) {
271 tmp = git__strdup(value);
272 GITERR_CHECK_ALLOC(tmp);
273 esc_value = escape_value(value);
274 GITERR_CHECK_ALLOC(esc_value);
275 }
276
277 git__free(existing->value);
278 existing->value = tmp;
279
280 ret = config_write(b, existing->key, NULL, esc_value);
281
282 git__free(esc_value);
283 return ret;
284 }
285
286 var = git__malloc(sizeof(cvar_t));
287 GITERR_CHECK_ALLOC(var);
288
289 memset(var, 0x0, sizeof(cvar_t));
290
291 var->key = key;
292 var->value = NULL;
293
294 if (value) {
295 var->value = git__strdup(value);
296 GITERR_CHECK_ALLOC(var->value);
297 esc_value = escape_value(value);
298 GITERR_CHECK_ALLOC(esc_value);
299 }
300
301 if (config_write(b, key, NULL, esc_value) < 0) {
302 git__free(esc_value);
303 cvar_free(var);
304 return -1;
305 }
306
307 git__free(esc_value);
308 git_strmap_insert2(b->values, key, var, old_var, rval);
309 if (rval < 0)
310 return -1;
311 if (old_var != NULL)
312 cvar_free(old_var);
313
314 return 0;
315 }
316
317 /*
318 * Internal function that actually gets the value in string form
319 */
320 static int config_get(git_config_file *cfg, const char *name, const char **out)
321 {
322 diskfile_backend *b = (diskfile_backend *)cfg;
323 char *key;
324 khiter_t pos;
325
326 if (normalize_name(name, &key) < 0)
327 return -1;
328
329 pos = git_strmap_lookup_index(b->values, key);
330 git__free(key);
331
332 /* no error message; the config system will write one */
333 if (!git_strmap_valid_index(b->values, pos))
334 return GIT_ENOTFOUND;
335
336 *out = ((cvar_t *)git_strmap_value_at(b->values, pos))->value;
337
338 return 0;
339 }
340
341 static int config_get_multivar(
342 git_config_file *cfg,
343 const char *name,
344 const char *regex_str,
345 int (*fn)(const char *, void *),
346 void *data)
347 {
348 cvar_t *var;
349 diskfile_backend *b = (diskfile_backend *)cfg;
350 char *key;
351 khiter_t pos;
352
353 if (normalize_name(name, &key) < 0)
354 return -1;
355
356 pos = git_strmap_lookup_index(b->values, key);
357 git__free(key);
358
359 if (!git_strmap_valid_index(b->values, pos))
360 return GIT_ENOTFOUND;
361
362 var = git_strmap_value_at(b->values, pos);
363
364 if (regex_str != NULL) {
365 regex_t regex;
366 int result;
367
368 /* regex matching; build the regex */
369 result = regcomp(&regex, regex_str, REG_EXTENDED);
370 if (result < 0) {
371 giterr_set_regex(&regex, result);
372 regfree(&regex);
373 return -1;
374 }
375
376 /* and throw the callback only on the variables that
377 * match the regex */
378 do {
379 if (regexec(&regex, var->value, 0, NULL, 0) == 0) {
380 /* early termination by the user is not an error;
381 * just break and return successfully */
382 if (fn(var->value, data) < 0)
383 break;
384 }
385
386 var = var->next;
387 } while (var != NULL);
388 regfree(&regex);
389 } else {
390 /* no regex; go through all the variables */
391 do {
392 /* early termination by the user is not an error;
393 * just break and return successfully */
394 if (fn(var->value, data) < 0)
395 break;
396
397 var = var->next;
398 } while (var != NULL);
399 }
400
401 return 0;
402 }
403
404 static int config_set_multivar(
405 git_config_file *cfg, const char *name, const char *regexp, const char *value)
406 {
407 int replaced = 0;
408 cvar_t *var, *newvar;
409 diskfile_backend *b = (diskfile_backend *)cfg;
410 char *key;
411 regex_t preg;
412 int result;
413 khiter_t pos;
414
415 assert(regexp);
416
417 if (normalize_name(name, &key) < 0)
418 return -1;
419
420 pos = git_strmap_lookup_index(b->values, key);
421 if (!git_strmap_valid_index(b->values, pos)) {
422 git__free(key);
423 return GIT_ENOTFOUND;
424 }
425
426 var = git_strmap_value_at(b->values, pos);
427
428 result = regcomp(&preg, regexp, REG_EXTENDED);
429 if (result < 0) {
430 git__free(key);
431 giterr_set_regex(&preg, result);
432 regfree(&preg);
433 return -1;
434 }
435
436 for (;;) {
437 if (regexec(&preg, var->value, 0, NULL, 0) == 0) {
438 char *tmp = git__strdup(value);
439 GITERR_CHECK_ALLOC(tmp);
440
441 git__free(var->value);
442 var->value = tmp;
443 replaced = 1;
444 }
445
446 if (var->next == NULL)
447 break;
448
449 var = var->next;
450 }
451
452 /* If we've reached the end of the variables and we haven't found it yet, we need to append it */
453 if (!replaced) {
454 newvar = git__malloc(sizeof(cvar_t));
455 GITERR_CHECK_ALLOC(newvar);
456
457 memset(newvar, 0x0, sizeof(cvar_t));
458
459 newvar->key = git__strdup(var->key);
460 GITERR_CHECK_ALLOC(newvar->key);
461
462 newvar->value = git__strdup(value);
463 GITERR_CHECK_ALLOC(newvar->value);
464
465 var->next = newvar;
466 }
467
468 result = config_write(b, key, &preg, value);
469
470 git__free(key);
471 regfree(&preg);
472
473 return result;
474 }
475
476 static int config_delete(git_config_file *cfg, const char *name)
477 {
478 cvar_t *var;
479 diskfile_backend *b = (diskfile_backend *)cfg;
480 char *key;
481 int result;
482 khiter_t pos;
483
484 if (normalize_name(name, &key) < 0)
485 return -1;
486
487 pos = git_strmap_lookup_index(b->values, key);
488 git__free(key);
489
490 if (!git_strmap_valid_index(b->values, pos)) {
491 giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name);
492 return GIT_ENOTFOUND;
493 }
494
495 var = git_strmap_value_at(b->values, pos);
496
497 if (var->next != NULL) {
498 giterr_set(GITERR_CONFIG, "Cannot delete multivar with a single delete");
499 return -1;
500 }
501
502 git_strmap_delete_at(b->values, pos);
503
504 result = config_write(b, var->key, NULL, NULL);
505
506 cvar_free(var);
507 return result;
508 }
509
510 int git_config_file__ondisk(git_config_file **out, const char *path)
511 {
512 diskfile_backend *backend;
513
514 backend = git__malloc(sizeof(diskfile_backend));
515 GITERR_CHECK_ALLOC(backend);
516
517 memset(backend, 0x0, sizeof(diskfile_backend));
518
519 backend->file_path = git__strdup(path);
520 GITERR_CHECK_ALLOC(backend->file_path);
521
522 backend->parent.open = config_open;
523 backend->parent.get = config_get;
524 backend->parent.get_multivar = config_get_multivar;
525 backend->parent.set = config_set;
526 backend->parent.set_multivar = config_set_multivar;
527 backend->parent.del = config_delete;
528 backend->parent.foreach = file_foreach;
529 backend->parent.free = backend_free;
530
531 *out = (git_config_file *)backend;
532
533 return 0;
534 }
535
536 static int cfg_getchar_raw(diskfile_backend *cfg)
537 {
538 int c;
539
540 c = *cfg->reader.read_ptr++;
541
542 /*
543 Win 32 line breaks: if we find a \r\n sequence,
544 return only the \n as a newline
545 */
546 if (c == '\r' && *cfg->reader.read_ptr == '\n') {
547 cfg->reader.read_ptr++;
548 c = '\n';
549 }
550
551 if (c == '\n')
552 cfg->reader.line_number++;
553
554 if (c == 0) {
555 cfg->reader.eof = 1;
556 c = '\n';
557 }
558
559 return c;
560 }
561
562 #define SKIP_WHITESPACE (1 << 1)
563 #define SKIP_COMMENTS (1 << 2)
564
565 static int cfg_getchar(diskfile_backend *cfg_file, int flags)
566 {
567 const int skip_whitespace = (flags & SKIP_WHITESPACE);
568 const int skip_comments = (flags & SKIP_COMMENTS);
569 int c;
570
571 assert(cfg_file->reader.read_ptr);
572
573 do c = cfg_getchar_raw(cfg_file);
574 while (skip_whitespace && git__isspace(c) &&
575 !cfg_file->reader.eof);
576
577 if (skip_comments && (c == '#' || c == ';')) {
578 do c = cfg_getchar_raw(cfg_file);
579 while (c != '\n');
580 }
581
582 return c;
583 }
584
585 /*
586 * Read the next char, but don't move the reading pointer.
587 */
588 static int cfg_peek(diskfile_backend *cfg, int flags)
589 {
590 void *old_read_ptr;
591 int old_lineno, old_eof;
592 int ret;
593
594 assert(cfg->reader.read_ptr);
595
596 old_read_ptr = cfg->reader.read_ptr;
597 old_lineno = cfg->reader.line_number;
598 old_eof = cfg->reader.eof;
599
600 ret = cfg_getchar(cfg, flags);
601
602 cfg->reader.read_ptr = old_read_ptr;
603 cfg->reader.line_number = old_lineno;
604 cfg->reader.eof = old_eof;
605
606 return ret;
607 }
608
609 /*
610 * Read and consume a line, returning it in newly-allocated memory.
611 */
612 static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace)
613 {
614 char *line = NULL;
615 char *line_src, *line_end;
616 size_t line_len;
617
618 line_src = cfg->reader.read_ptr;
619
620 if (skip_whitespace) {
621 /* Skip empty empty lines */
622 while (git__isspace(*line_src))
623 ++line_src;
624 }
625
626 line_end = strchr(line_src, '\n');
627
628 /* no newline at EOF */
629 if (line_end == NULL)
630 line_end = strchr(line_src, 0);
631
632 line_len = line_end - line_src;
633
634 line = git__malloc(line_len + 1);
635 if (line == NULL)
636 return NULL;
637
638 memcpy(line, line_src, line_len);
639
640 do line[line_len] = '\0';
641 while (line_len-- > 0 && git__isspace(line[line_len]));
642
643 if (*line_end == '\n')
644 line_end++;
645
646 if (*line_end == '\0')
647 cfg->reader.eof = 1;
648
649 cfg->reader.line_number++;
650 cfg->reader.read_ptr = line_end;
651
652 return line;
653 }
654
655 /*
656 * Consume a line, without storing it anywhere
657 */
658 static void cfg_consume_line(diskfile_backend *cfg)
659 {
660 char *line_start, *line_end;
661
662 line_start = cfg->reader.read_ptr;
663 line_end = strchr(line_start, '\n');
664 /* No newline at EOF */
665 if(line_end == NULL){
666 line_end = strchr(line_start, '\0');
667 }
668
669 if (*line_end == '\n')
670 line_end++;
671
672 if (*line_end == '\0')
673 cfg->reader.eof = 1;
674
675 cfg->reader.line_number++;
676 cfg->reader.read_ptr = line_end;
677 }
678
679 GIT_INLINE(int) config_keychar(int c)
680 {
681 return isalnum(c) || c == '-';
682 }
683
684 static int parse_section_header_ext(diskfile_backend *cfg, const char *line, const char *base_name, char **section_name)
685 {
686 int c, rpos;
687 char *first_quote, *last_quote;
688 git_buf buf = GIT_BUF_INIT;
689 int quote_marks;
690 /*
691 * base_name is what came before the space. We should be at the
692 * first quotation mark, except for now, line isn't being kept in
693 * sync so we only really use it to calculate the length.
694 */
695
696 first_quote = strchr(line, '"');
697 last_quote = strrchr(line, '"');
698
699 if (last_quote - first_quote == 0) {
700 set_parse_error(cfg, 0, "Missing closing quotation mark in section header");
701 return -1;
702 }
703
704 git_buf_grow(&buf, strlen(base_name) + last_quote - first_quote + 2);
705 git_buf_printf(&buf, "%s.", base_name);
706
707 rpos = 0;
708 quote_marks = 0;
709
710 line = first_quote;
711 c = line[rpos++];
712
713 /*
714 * At the end of each iteration, whatever is stored in c will be
715 * added to the string. In case of error, jump to out
716 */
717 do {
718 if (quote_marks == 2) {
719 set_parse_error(cfg, rpos, "Unexpected text after closing quotes");
720 git_buf_free(&buf);
721 return -1;
722 }
723
724 switch (c) {
725 case '"':
726 ++quote_marks;
727 continue;
728
729 case '\\':
730 c = line[rpos++];
731
732 switch (c) {
733 case '"':
734 case '\\':
735 break;
736
737 default:
738 set_parse_error(cfg, rpos, "Unsupported escape sequence");
739 git_buf_free(&buf);
740 return -1;
741 }
742
743 default:
744 break;
745 }
746
747 git_buf_putc(&buf, c);
748 } while ((c = line[rpos++]) != ']');
749
750 *section_name = git_buf_detach(&buf);
751 return 0;
752 }
753
754 static int parse_section_header(diskfile_backend *cfg, char **section_out)
755 {
756 char *name, *name_end;
757 int name_length, c, pos;
758 int result;
759 char *line;
760
761 line = cfg_readline(cfg, true);
762 if (line == NULL)
763 return -1;
764
765 /* find the end of the variable's name */
766 name_end = strchr(line, ']');
767 if (name_end == NULL) {
768 git__free(line);
769 set_parse_error(cfg, 0, "Missing ']' in section header");
770 return -1;
771 }
772
773 name = (char *)git__malloc((size_t)(name_end - line) + 1);
774 GITERR_CHECK_ALLOC(name);
775
776 name_length = 0;
777 pos = 0;
778
779 /* Make sure we were given a section header */
780 c = line[pos++];
781 assert(c == '[');
782
783 c = line[pos++];
784
785 do {
786 if (git__isspace(c)){
787 name[name_length] = '\0';
788 result = parse_section_header_ext(cfg, line, name, section_out);
789 git__free(line);
790 git__free(name);
791 return result;
792 }
793
794 if (!config_keychar(c) && c != '.') {
795 set_parse_error(cfg, pos, "Unexpected character in header");
796 goto fail_parse;
797 }
798
799 name[name_length++] = (char) tolower(c);
800
801 } while ((c = line[pos++]) != ']');
802
803 if (line[pos - 1] != ']') {
804 set_parse_error(cfg, pos, "Unexpected end of file");
805 goto fail_parse;
806 }
807
808 git__free(line);
809
810 name[name_length] = 0;
811 *section_out = name;
812
813 return 0;
814
815 fail_parse:
816 git__free(line);
817 git__free(name);
818 return -1;
819 }
820
821 static int skip_bom(diskfile_backend *cfg)
822 {
823 static const char utf8_bom[] = { '\xef', '\xbb', '\xbf' };
824
825 if (cfg->reader.buffer.size < sizeof(utf8_bom))
826 return 0;
827
828 if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0)
829 cfg->reader.read_ptr += sizeof(utf8_bom);
830
831 /* TODO: the reference implementation does pretty stupid
832 shit with the BoM
833 */
834
835 return 0;
836 }
837
838 /*
839 (* basic types *)
840 digit = "0".."9"
841 integer = digit { digit }
842 alphabet = "a".."z" + "A" .. "Z"
843
844 section_char = alphabet | "." | "-"
845 extension_char = (* any character except newline *)
846 any_char = (* any character *)
847 variable_char = "alphabet" | "-"
848
849
850 (* actual grammar *)
851 config = { section }
852
853 section = header { definition }
854
855 header = "[" section [subsection | subsection_ext] "]"
856
857 subsection = "." section
858 subsection_ext = "\"" extension "\""
859
860 section = section_char { section_char }
861 extension = extension_char { extension_char }
862
863 definition = variable_name ["=" variable_value] "\n"
864
865 variable_name = variable_char { variable_char }
866 variable_value = string | boolean | integer
867
868 string = quoted_string | plain_string
869 quoted_string = "\"" plain_string "\""
870 plain_string = { any_char }
871
872 boolean = boolean_true | boolean_false
873 boolean_true = "yes" | "1" | "true" | "on"
874 boolean_false = "no" | "0" | "false" | "off"
875 */
876
877 static int strip_comments(char *line, int in_quotes)
878 {
879 int quote_count = in_quotes;
880 char *ptr;
881
882 for (ptr = line; *ptr; ++ptr) {
883 if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\')
884 quote_count++;
885
886 if ((ptr[0] == ';' || ptr[0] == '#') && (quote_count % 2) == 0) {
887 ptr[0] = '\0';
888 break;
889 }
890 }
891
892 /* skip any space at the end */
893 if (git__isspace(ptr[-1])) {
894 ptr--;
895 }
896 ptr[0] = '\0';
897
898 return quote_count;
899 }
900
901 static int config_parse(diskfile_backend *cfg_file)
902 {
903 int c;
904 char *current_section = NULL;
905 char *var_name;
906 char *var_value;
907 cvar_t *var, *existing;
908 git_buf buf = GIT_BUF_INIT;
909 int result = 0;
910 khiter_t pos;
911
912 /* Initialize the reading position */
913 cfg_file->reader.read_ptr = cfg_file->reader.buffer.ptr;
914 cfg_file->reader.eof = 0;
915
916 /* If the file is empty, there's nothing for us to do */
917 if (*cfg_file->reader.read_ptr == '\0')
918 return 0;
919
920 skip_bom(cfg_file);
921
922 while (result == 0 && !cfg_file->reader.eof) {
923
924 c = cfg_peek(cfg_file, SKIP_WHITESPACE);
925
926 switch (c) {
927 case '\n': /* EOF when peeking, set EOF in the reader to exit the loop */
928 cfg_file->reader.eof = 1;
929 break;
930
931 case '[': /* section header, new section begins */
932 git__free(current_section);
933 current_section = NULL;
934 result = parse_section_header(cfg_file, &current_section);
935 break;
936
937 case ';':
938 case '#':
939 cfg_consume_line(cfg_file);
940 break;
941
942 default: /* assume variable declaration */
943 result = parse_variable(cfg_file, &var_name, &var_value);
944 if (result < 0)
945 break;
946
947 var = git__malloc(sizeof(cvar_t));
948 GITERR_CHECK_ALLOC(var);
949
950 memset(var, 0x0, sizeof(cvar_t));
951
952 git__strtolower(var_name);
953 git_buf_printf(&buf, "%s.%s", current_section, var_name);
954 git__free(var_name);
955
956 if (git_buf_oom(&buf))
957 return -1;
958
959 var->key = git_buf_detach(&buf);
960 var->value = var_value;
961
962 /* Add or append the new config option */
963 pos = git_strmap_lookup_index(cfg_file->values, var->key);
964 if (!git_strmap_valid_index(cfg_file->values, pos)) {
965 git_strmap_insert(cfg_file->values, var->key, var, result);
966 if (result < 0)
967 break;
968 result = 0;
969 } else {
970 existing = git_strmap_value_at(cfg_file->values, pos);
971 while (existing->next != NULL) {
972 existing = existing->next;
973 }
974 existing->next = var;
975 }
976
977 break;
978 }
979 }
980
981 git__free(current_section);
982 return result;
983 }
984
985 static int write_section(git_filebuf *file, const char *key)
986 {
987 int result;
988 const char *dot;
989 git_buf buf = GIT_BUF_INIT;
990
991 /* All of this just for [section "subsection"] */
992 dot = strchr(key, '.');
993 git_buf_putc(&buf, '[');
994 if (dot == NULL) {
995 git_buf_puts(&buf, key);
996 } else {
997 char *escaped;
998 git_buf_put(&buf, key, dot - key);
999 escaped = escape_value(dot + 1);
1000 GITERR_CHECK_ALLOC(escaped);
1001 git_buf_printf(&buf, " \"%s\"", escaped);
1002 git__free(escaped);
1003 }
1004 git_buf_puts(&buf, "]\n");
1005
1006 if (git_buf_oom(&buf))
1007 return -1;
1008
1009 result = git_filebuf_write(file, git_buf_cstr(&buf), buf.size);
1010 git_buf_free(&buf);
1011
1012 return result;
1013 }
1014
1015 /*
1016 * This is pretty much the parsing, except we write out anything we don't have
1017 */
1018 static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char* value)
1019 {
1020 int result, c;
1021 int section_matches = 0, last_section_matched = 0, preg_replaced = 0, write_trailer = 0;
1022 const char *pre_end = NULL, *post_start = NULL, *data_start;
1023 char *current_section = NULL, *section, *name, *ldot;
1024 git_filebuf file = GIT_FILEBUF_INIT;
1025
1026 /* We need to read in our own config file */
1027 result = git_futils_readbuffer(&cfg->reader.buffer, cfg->file_path);
1028
1029 /* Initialise the reading position */
1030 if (result == GIT_ENOTFOUND) {
1031 cfg->reader.read_ptr = NULL;
1032 cfg->reader.eof = 1;
1033 data_start = NULL;
1034 git_buf_clear(&cfg->reader.buffer);
1035 } else if (result == 0) {
1036 cfg->reader.read_ptr = cfg->reader.buffer.ptr;
1037 cfg->reader.eof = 0;
1038 data_start = cfg->reader.read_ptr;
1039 } else {
1040 return -1; /* OS error when reading the file */
1041 }
1042
1043 /* Lock the file */
1044 if (git_filebuf_open(&file, cfg->file_path, 0) < 0)
1045 return -1;
1046
1047 skip_bom(cfg);
1048 ldot = strrchr(key, '.');
1049 name = ldot + 1;
1050 section = git__strndup(key, ldot - key);
1051
1052 while (!cfg->reader.eof) {
1053 c = cfg_peek(cfg, SKIP_WHITESPACE);
1054
1055 if (c == '\0') { /* We've arrived at the end of the file */
1056 break;
1057
1058 } else if (c == '[') { /* section header, new section begins */
1059 /*
1060 * We set both positions to the current one in case we
1061 * need to add a variable to the end of a section. In that
1062 * case, we want both variables to point just before the
1063 * new section. If we actually want to replace it, the
1064 * default case will take care of updating them.
1065 */
1066 pre_end = post_start = cfg->reader.read_ptr;
1067
1068 git__free(current_section);
1069 current_section = NULL;
1070 if (parse_section_header(cfg, &current_section) < 0)
1071 goto rewrite_fail;
1072
1073 /* Keep track of when it stops matching */
1074 last_section_matched = section_matches;
1075 section_matches = !strcmp(current_section, section);
1076 }
1077
1078 else if (c == ';' || c == '#') {
1079 cfg_consume_line(cfg);
1080 }
1081
1082 else {
1083 /*
1084 * If the section doesn't match, but the last section did,
1085 * it means we need to add a variable (so skip the line
1086 * otherwise). If both the section and name match, we need
1087 * to overwrite the variable (so skip the line
1088 * otherwise). pre_end needs to be updated each time so we
1089 * don't loose that information, but we only need to
1090 * update post_start if we're going to use it in this
1091 * iteration.
1092 */
1093 if (!section_matches) {
1094 if (!last_section_matched) {
1095 cfg_consume_line(cfg);
1096 continue;
1097 }
1098 } else {
1099 int has_matched = 0;
1100 char *var_name, *var_value;
1101
1102 pre_end = cfg->reader.read_ptr;
1103 if (parse_variable(cfg, &var_name, &var_value) < 0)
1104 goto rewrite_fail;
1105
1106 /* First try to match the name of the variable */
1107 if (strcasecmp(name, var_name) == 0)
1108 has_matched = 1;
1109
1110 /* If the name matches, and we have a regex to match the
1111 * value, try to match it */
1112 if (has_matched && preg != NULL)
1113 has_matched = (regexec(preg, var_value, 0, NULL, 0) == 0);
1114
1115 git__free(var_name);
1116 git__free(var_value);
1117
1118 /* if there is no match, keep going */
1119 if (!has_matched)
1120 continue;
1121
1122 post_start = cfg->reader.read_ptr;
1123 }
1124
1125 /* We've found the variable we wanted to change, so
1126 * write anything up to it */
1127 git_filebuf_write(&file, data_start, pre_end - data_start);
1128 preg_replaced = 1;
1129
1130 /* Then replace the variable. If the value is NULL, it
1131 * means we want to delete it, so don't write anything. */
1132 if (value != NULL) {
1133 git_filebuf_printf(&file, "\t%s = %s\n", name, value);
1134 }
1135
1136 /* multiline variable? we need to keep reading lines to match */
1137 if (preg != NULL) {
1138 data_start = post_start;
1139 continue;
1140 }
1141
1142 write_trailer = 1;
1143 break; /* break from the loop */
1144 }
1145 }
1146
1147 /*
1148 * Being here can mean that
1149 *
1150 * 1) our section is the last one in the file and we're
1151 * adding a variable
1152 *
1153 * 2) we didn't find a section for us so we need to create it
1154 * ourselves.
1155 *
1156 * 3) we're setting a multivar with a regex, which means we
1157 * continue to search for matching values
1158 *
1159 * In the last case, if we've already replaced a value, we
1160 * want to write the rest of the file. Otherwise we need to write
1161 * out the whole file and then the new variable.
1162 */
1163 if (write_trailer) {
1164 /* Write out rest of the file */
1165 git_filebuf_write(&file, post_start, cfg->reader.buffer.size - (post_start - data_start));
1166 } else {
1167 if (preg_replaced) {
1168 git_filebuf_printf(&file, "\n%s", data_start);
1169 } else {
1170 git_filebuf_write(&file, cfg->reader.buffer.ptr, cfg->reader.buffer.size);
1171
1172 /* And now if we just need to add a variable */
1173 if (!section_matches && write_section(&file, section) < 0)
1174 goto rewrite_fail;
1175
1176 /* Sanity check: if we are here, and value is NULL, that means that somebody
1177 * touched the config file after our intial read. We should probably assert()
1178 * this, but instead we'll handle it gracefully with an error. */
1179 if (value == NULL) {
1180 giterr_set(GITERR_CONFIG,
1181 "Race condition when writing a config file (a cvar has been removed)");
1182 goto rewrite_fail;
1183 }
1184
1185 git_filebuf_printf(&file, "\t%s = %s\n", name, value);
1186 }
1187 }
1188
1189 git__free(section);
1190 git__free(current_section);
1191
1192 result = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE);
1193 git_buf_free(&cfg->reader.buffer);
1194 return result;
1195
1196 rewrite_fail:
1197 git__free(section);
1198 git__free(current_section);
1199
1200 git_filebuf_cleanup(&file);
1201 git_buf_free(&cfg->reader.buffer);
1202 return -1;
1203 }
1204
1205 static const char *escapes = "ntb\"\\";
1206 static const char *escaped = "\n\t\b\"\\";
1207
1208 /* Escape the values to write them to the file */
1209 static char *escape_value(const char *ptr)
1210 {
1211 git_buf buf = GIT_BUF_INIT;
1212 size_t len;
1213 const char *esc;
1214
1215 assert(ptr);
1216
1217 len = strlen(ptr);
1218 git_buf_grow(&buf, len);
1219
1220 while (*ptr != '\0') {
1221 if ((esc = strchr(escaped, *ptr)) != NULL) {
1222 git_buf_putc(&buf, '\\');
1223 git_buf_putc(&buf, escapes[esc - escaped]);
1224 } else {
1225 git_buf_putc(&buf, *ptr);
1226 }
1227 ptr++;
1228 }
1229
1230 if (git_buf_oom(&buf)) {
1231 git_buf_free(&buf);
1232 return NULL;
1233 }
1234
1235 return git_buf_detach(&buf);
1236 }
1237
1238 /* '\"' -> '"' etc */
1239 static char *fixup_line(const char *ptr, int quote_count)
1240 {
1241 char *str = git__malloc(strlen(ptr) + 1);
1242 char *out = str, *esc;
1243
1244 if (str == NULL)
1245 return NULL;
1246
1247 while (*ptr != '\0') {
1248 if (*ptr == '"') {
1249 quote_count++;
1250 } else if (*ptr != '\\') {
1251 *out++ = *ptr;
1252 } else {
1253 /* backslash, check the next char */
1254 ptr++;
1255 /* if we're at the end, it's a multiline, so keep the backslash */
1256 if (*ptr == '\0') {
1257 *out++ = '\\';
1258 goto out;
1259 }
1260 if ((esc = strchr(escapes, *ptr)) != NULL) {
1261 *out++ = escaped[esc - escapes];
1262 } else {
1263 git__free(str);
1264 giterr_set(GITERR_CONFIG, "Invalid escape at %s", ptr);
1265 return NULL;
1266 }
1267 }
1268 ptr++;
1269 }
1270
1271 out:
1272 *out = '\0';
1273
1274 return str;
1275 }
1276
1277 static int is_multiline_var(const char *str)
1278 {
1279 const char *end = str + strlen(str);
1280 return (end > str) && (end[-1] == '\\');
1281 }
1282
1283 static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int in_quotes)
1284 {
1285 char *line = NULL, *proc_line = NULL;
1286 int quote_count;
1287
1288 /* Check that the next line exists */
1289 line = cfg_readline(cfg, false);
1290 if (line == NULL)
1291 return -1;
1292
1293 /* We've reached the end of the file, there is input missing */
1294 if (line[0] == '\0') {
1295 set_parse_error(cfg, 0, "Unexpected end of file while parsing multine var");
1296 git__free(line);
1297 return -1;
1298 }
1299
1300 quote_count = strip_comments(line, !!in_quotes);
1301
1302 /* If it was just a comment, pretend it didn't exist */
1303 if (line[0] == '\0') {
1304 git__free(line);
1305 return parse_multiline_variable(cfg, value, quote_count);
1306 /* TODO: unbounded recursion. This **could** be exploitable */
1307 }
1308
1309 /* Drop the continuation character '\': to closely follow the UNIX
1310 * standard, this character **has** to be last one in the buf, with
1311 * no whitespace after it */
1312 assert(is_multiline_var(value->ptr));
1313 git_buf_truncate(value, git_buf_len(value) - 1);
1314
1315 proc_line = fixup_line(line, in_quotes);
1316 if (proc_line == NULL) {
1317 git__free(line);
1318 return -1;
1319 }
1320 /* add this line to the multiline var */
1321 git_buf_puts(value, proc_line);
1322 git__free(line);
1323 git__free(proc_line);
1324
1325 /*
1326 * If we need to continue reading the next line, let's just
1327 * keep putting stuff in the buffer
1328 */
1329 if (is_multiline_var(value->ptr))
1330 return parse_multiline_variable(cfg, value, quote_count);
1331
1332 return 0;
1333 }
1334
1335 static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value)
1336 {
1337 const char *var_end = NULL;
1338 const char *value_start = NULL;
1339 char *line;
1340 int quote_count;
1341
1342 line = cfg_readline(cfg, true);
1343 if (line == NULL)
1344 return -1;
1345
1346 quote_count = strip_comments(line, 0);
1347
1348 var_end = strchr(line, '=');
1349
1350 if (var_end == NULL)
1351 var_end = strchr(line, '\0');
1352 else
1353 value_start = var_end + 1;
1354
1355 do var_end--;
1356 while (git__isspace(*var_end));
1357
1358 *var_name = git__strndup(line, var_end - line + 1);
1359 GITERR_CHECK_ALLOC(*var_name);
1360
1361 /* If there is no value, boolean true is assumed */
1362 *var_value = NULL;
1363
1364 /*
1365 * Now, let's try to parse the value
1366 */
1367 if (value_start != NULL) {
1368 while (git__isspace(value_start[0]))
1369 value_start++;
1370
1371 if (is_multiline_var(value_start)) {
1372 git_buf multi_value = GIT_BUF_INIT;
1373 char *proc_line = fixup_line(value_start, 0);
1374 GITERR_CHECK_ALLOC(proc_line);
1375 git_buf_puts(&multi_value, proc_line);
1376 git__free(proc_line);
1377 if (parse_multiline_variable(cfg, &multi_value, quote_count) < 0 || git_buf_oom(&multi_value)) {
1378 git__free(*var_name);
1379 git__free(line);
1380 git_buf_free(&multi_value);
1381 return -1;
1382 }
1383
1384 *var_value = git_buf_detach(&multi_value);
1385
1386 }
1387 else if (value_start[0] != '\0') {
1388 *var_value = fixup_line(value_start, 0);
1389 GITERR_CHECK_ALLOC(*var_value);
1390 }
1391
1392 }
1393
1394 git__free(line);
1395 return 0;
1396 }