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