]> git.proxmox.com Git - libgit2.git/blob - src/config.c
config: store the section name separately
[libgit2.git] / src / config.c
1 /*
2 * This file is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License, version 2,
4 * as published by the Free Software Foundation.
5 *
6 * In addition to the permissions in the GNU General Public License,
7 * the authors give you unlimited permission to link the compiled
8 * version of this file into combinations with other programs,
9 * and to distribute those combinations without any restriction
10 * coming from the use of this file. (The General Public License
11 * restrictions do apply in other respects; for example, they cover
12 * modification of the file, and distribution when not linked into
13 * a combined executable.)
14 *
15 * This file is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; see the file COPYING. If not, write to
22 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
24 */
25
26 #include "common.h"
27 #include "fileops.h"
28 #include "hashtable.h"
29 #include "config.h"
30
31 #include <ctype.h>
32
33 /**********************
34 * Forward declarations
35 ***********************/
36 static int config_parse(git_config *cfg_file);
37 static int parse_variable(git_config *cfg, char **var_name, char **var_value);
38 void git_config_free(git_config *cfg);
39
40 static void cvar_free(git_cvar *var)
41 {
42 if (var == NULL)
43 return;
44
45 free(var->section);
46 free(var->name);
47 free(var->value);
48 free(var);
49 }
50
51 static void cvar_list_free(git_cvar_list *list)
52 {
53 git_cvar *cur;
54
55 while (!CVAR_LIST_EMPTY(list)) {
56 cur = CVAR_LIST_HEAD(list);
57 CVAR_LIST_REMOVE_HEAD(list);
58 cvar_free(cur);
59 }
60 }
61
62 /*
63 * Compare two strings according to the git section-subsection
64 * rules. The order of the strings is important because local is
65 * assumed to have the internal format (only the section name and with
66 * case information) and input the normalized one (only dots, no case
67 * information).
68 */
69 static int cvar_match_section(const char *local, const char *input)
70 {
71 char *first_dot, *last_dot;
72 char *local_sp = strchr(local, ' ');
73 int comparison_len;
74
75 /*
76 * If the local section name doesn't contain a space, then we can
77 * just do a case-insensitive compare.
78 */
79 if (local_sp == NULL)
80 return !strncasecmp(local, input, strlen(local));
81
82 /*
83 * From here onwards, there is a space diving the section and the
84 * subsection. Anything before the space in local is
85 * case-insensitive.
86 */
87 if (strncasecmp(local, input, local_sp - local))
88 return 0;
89
90 /*
91 * We compare starting from the first character after the
92 * quotation marks, which is two characters beyond the space. For
93 * the input, we start one character beyond the dot. If the names
94 * have different lengths, then we can fail early, as we know they
95 * can't be the same.
96 * The length is given by the length between the quotation marks.
97 */
98
99 first_dot = strchr(input, '.');
100 last_dot = strrchr(input, '.');
101 comparison_len = strlen(local_sp + 2) - 1;
102
103 if (last_dot == first_dot || last_dot - first_dot - 1 != comparison_len)
104 return 0;
105
106 return !strncmp(local_sp + 2, first_dot + 1, comparison_len);
107 }
108
109 static int cvar_match_name(const git_cvar *var, const char *str)
110 {
111 const char *name_start;
112
113 if (!cvar_match_section(var->section, str)) {
114 return 0;
115 }
116 /* Early exit if the lengths are different */
117 name_start = strrchr(str, '.') + 1;
118 if (strlen(var->name) != strlen(name_start))
119 return 0;
120
121 return !strcasecmp(var->name, name_start);
122 }
123
124 static git_cvar *cvar_list_find(git_cvar_list *list, const char *name)
125 {
126 git_cvar *iter;
127
128 CVAR_LIST_FOREACH (list, iter) {
129 if (cvar_match_name(iter, name))
130 return iter;
131 }
132
133 return NULL;
134 }
135
136 static int cvar_name_normalize(const char *input, char **output)
137 {
138 char *input_sp = strchr(input, ' ');
139 char *quote, *str;
140 int i;
141
142 /* We need to make a copy anyway */
143 str = git__strdup(input);
144 if (str == NULL)
145 return GIT_ENOMEM;
146
147 *output = str;
148
149 /* If there aren't any spaces, we don't need to do anything */
150 if (input_sp == NULL)
151 return GIT_SUCCESS;
152
153 /*
154 * If there are spaces, we replace the space by a dot, move the
155 * variable name so that the dot before it replaces the last
156 * quotation mark and repeat so that the first quotation mark
157 * disappears.
158 */
159 str[input_sp - input] = '.';
160
161 for (i = 0; i < 2; ++i) {
162 quote = strrchr(str, '"');
163 memmove(quote, quote + 1, strlen(quote));
164 }
165
166 return GIT_SUCCESS;
167 }
168
169 void git__strntolower(char *str, int len)
170 {
171 int i;
172
173 for (i = 0; i < len; ++i) {
174 str[i] = tolower(str[i]);
175 }
176 }
177
178 void git__strtolower(char *str)
179 {
180 git__strntolower(str, strlen(str));
181 }
182
183 int git_config_open(git_config **cfg_out, const char *path)
184 {
185 git_config *cfg;
186 int error = GIT_SUCCESS;
187
188 assert(cfg_out && path);
189
190 cfg = git__malloc(sizeof(git_config));
191 if (cfg == NULL)
192 return GIT_ENOMEM;
193
194 memset(cfg, 0x0, sizeof(git_config));
195
196 cfg->file_path = git__strdup(path);
197 if (cfg->file_path == NULL) {
198 error = GIT_ENOMEM;
199 goto cleanup;
200 }
201
202 error = gitfo_read_file(&cfg->reader.buffer, cfg->file_path);
203 if(error < GIT_SUCCESS)
204 goto cleanup;
205
206 error = config_parse(cfg);
207 if (error < GIT_SUCCESS)
208 goto cleanup;
209 else
210 *cfg_out = cfg;
211
212 gitfo_free_buf(&cfg->reader.buffer);
213
214 return error;
215
216 cleanup:
217 cvar_list_free(&cfg->var_list);
218 if (cfg->file_path)
219 free(cfg->file_path);
220 gitfo_free_buf(&cfg->reader.buffer);
221 free(cfg);
222
223 return error;
224 }
225
226 void git_config_free(git_config *cfg)
227 {
228 if (cfg == NULL)
229 return;
230
231 free(cfg->file_path);
232 cvar_list_free(&cfg->var_list);
233
234 free(cfg);
235 }
236
237 /*
238 * Loop over all the variables
239 */
240
241 int git_config_foreach(git_config *cfg, int (*fn)(const char *, void *), void *data)
242 {
243 int ret = GIT_SUCCESS;
244 git_cvar *var;
245 char *normalized;
246
247 CVAR_LIST_FOREACH(&cfg->var_list, var) {
248 ret = cvar_name_normalize(var->name, &normalized);
249 if (ret < GIT_SUCCESS)
250 return ret;
251
252 ret = fn(normalized, data);
253 free(normalized);
254 if (ret)
255 break;
256 }
257
258 return ret;
259 }
260
261 /**************
262 * Setters
263 **************/
264
265 /*
266 * Internal function to actually set the string value of a variable
267 */
268 static int config_set(git_config *cfg, const char *name, const char *value)
269 {
270 git_cvar *var = NULL;
271 git_cvar *existing = NULL;
272 int error = GIT_SUCCESS;
273 const char *last_dot;
274
275 /*
276 * If it already exists, we just need to update its value.
277 */
278 existing = cvar_list_find(&cfg->var_list, name);
279 if (existing != NULL) {
280 char *tmp = value ? git__strdup(value) : NULL;
281 if (tmp == NULL && value != NULL)
282 return GIT_ENOMEM;
283
284 free(existing->value);
285 existing->value = tmp;
286
287 return GIT_SUCCESS;
288 }
289
290 /*
291 * Otherwise, create it and stick it at the end of the queue.
292 */
293
294 var = git__malloc(sizeof(git_cvar));
295 if (var == NULL)
296 return GIT_ENOMEM;
297
298 memset(var, 0x0, sizeof(git_cvar));
299
300 last_dot = strrchr(name, '.');
301 if (last_dot == NULL) {
302 error = GIT_ERROR;
303 goto out;
304 }
305
306 var->section = git__strndup(name, last_dot - name);
307 if (var->section == NULL) {
308 error = GIT_ENOMEM;
309 goto out;
310 }
311
312 var->name = git__strdup(last_dot + 1);
313 if (var->name == NULL) {
314 error = GIT_ENOMEM;
315 goto out;
316 }
317
318 var->value = value ? git__strdup(value) : NULL;
319 if (var->value == NULL && value != NULL) {
320 error = GIT_ENOMEM;
321 goto out;
322 }
323
324 CVAR_LIST_APPEND(&cfg->var_list, var);
325
326 out:
327 if (error < GIT_SUCCESS)
328 cvar_free(var);
329
330 return error;
331 }
332
333 int git_config_set_long(git_config *cfg, const char *name, long int value)
334 {
335 char str_value[5]; /* Most numbers should fit in here */
336 int buf_len = sizeof(str_value), ret;
337 char *help_buf = NULL;
338
339 if ((ret = snprintf(str_value, buf_len, "%ld", value)) >= buf_len - 1){
340 /* The number is too large, we need to allocate more memory */
341 buf_len = ret + 1;
342 help_buf = git__malloc(buf_len);
343 snprintf(help_buf, buf_len, "%ld", value);
344 ret = config_set(cfg, name, help_buf);
345 free(help_buf);
346 } else {
347 ret = config_set(cfg, name, str_value);
348 }
349
350 return ret;
351 }
352
353 int git_config_set_int(git_config *cfg, const char *name, int value)
354 {
355 return git_config_set_long(cfg, name, value);
356 }
357
358 int git_config_set_bool(git_config *cfg, const char *name, int value)
359 {
360 const char *str_value;
361
362 if (value == 0)
363 str_value = "false";
364 else
365 str_value = "true";
366
367 return config_set(cfg, name, str_value);
368 }
369
370 int git_config_set_string(git_config *cfg, const char *name, const char *value)
371 {
372 return config_set(cfg, name, value);
373 }
374
375 /***********
376 * Getters
377 ***********/
378
379 /*
380 * Internal function that actually gets the value in string form
381 */
382 static int config_get(git_config *cfg, const char *name, const char **out)
383 {
384 git_cvar *var;
385 int error = GIT_SUCCESS;
386
387 var = cvar_list_find(&cfg->var_list, name);
388
389 if (var == NULL)
390 return GIT_ENOTFOUND;
391
392 *out = var->value;
393
394 return error;
395 }
396
397 int git_config_get_long(git_config *cfg, const char *name, long int *out)
398 {
399 const char *value, *num_end;
400 int ret;
401 long int num;
402
403 ret = config_get(cfg, name, &value);
404 if (ret < GIT_SUCCESS)
405 return ret;
406
407 ret = git__strtol32(&num, value, &num_end, 0);
408 if (ret < GIT_SUCCESS)
409 return ret;
410
411 switch (*num_end) {
412 case '\0':
413 break;
414 case 'k':
415 case 'K':
416 num *= 1024;
417 break;
418 case 'm':
419 case 'M':
420 num *= 1024 * 1024;
421 break;
422 case 'g':
423 case 'G':
424 num *= 1024 * 1024 * 1024;
425 break;
426 default:
427 return GIT_EINVALIDTYPE;
428 }
429
430 *out = num;
431
432 return GIT_SUCCESS;
433 }
434
435 int git_config_get_int(git_config *cfg, const char *name, int *out)
436 {
437 long int tmp;
438 int ret;
439
440 ret = git_config_get_long(cfg, name, &tmp);
441
442 *out = (int) tmp;
443
444 return ret;
445 }
446
447 int git_config_get_bool(git_config *cfg, const char *name, int *out)
448 {
449 const char *value;
450 int error = GIT_SUCCESS;
451
452 error = config_get(cfg, name, &value);
453 if (error < GIT_SUCCESS)
454 return error;
455
456 /* A missing value means true */
457 if (value == NULL) {
458 *out = 1;
459 return GIT_SUCCESS;
460 }
461
462 if (!strcasecmp(value, "true") ||
463 !strcasecmp(value, "yes") ||
464 !strcasecmp(value, "on")) {
465 *out = 1;
466 return GIT_SUCCESS;
467 }
468 if (!strcasecmp(value, "false") ||
469 !strcasecmp(value, "no") ||
470 !strcasecmp(value, "off")) {
471 *out = 0;
472 return GIT_SUCCESS;
473 }
474
475 /* Try to parse it as an integer */
476 error = git_config_get_int(cfg, name, out);
477 if (error == GIT_SUCCESS)
478 *out = !!(*out);
479
480 return error;
481 }
482
483 int git_config_get_string(git_config *cfg, const char *name, const char **out)
484 {
485 return config_get(cfg, name, out);
486 }
487
488 static int cfg_getchar_raw(git_config *cfg)
489 {
490 int c;
491
492 c = *cfg->reader.read_ptr++;
493
494 /*
495 Win 32 line breaks: if we find a \r\n sequence,
496 return only the \n as a newline
497 */
498 if (c == '\r' && *cfg->reader.read_ptr == '\n') {
499 cfg->reader.read_ptr++;
500 c = '\n';
501 }
502
503 if (c == '\n')
504 cfg->reader.line_number++;
505
506 if (c == 0) {
507 cfg->reader.eof = 1;
508 c = '\n';
509 }
510
511 return c;
512 }
513
514 #define SKIP_WHITESPACE (1 << 1)
515 #define SKIP_COMMENTS (1 << 2)
516
517 static int cfg_getchar(git_config *cfg_file, int flags)
518 {
519 const int skip_whitespace = (flags & SKIP_WHITESPACE);
520 const int skip_comments = (flags & SKIP_COMMENTS);
521 int c;
522
523 assert(cfg_file->reader.read_ptr);
524
525 do c = cfg_getchar_raw(cfg_file);
526 while (skip_whitespace && isspace(c));
527
528 if (skip_comments && (c == '#' || c == ';')) {
529 do c = cfg_getchar_raw(cfg_file);
530 while (c != '\n');
531 }
532
533 return c;
534 }
535
536 /*
537 * Read the next char, but don't move the reading pointer.
538 */
539 static int cfg_peek(git_config *cfg, int flags)
540 {
541 void *old_read_ptr;
542 int old_lineno, old_eof;
543 int ret;
544
545 assert(cfg->reader.read_ptr);
546
547 old_read_ptr = cfg->reader.read_ptr;
548 old_lineno = cfg->reader.line_number;
549 old_eof = cfg->reader.eof;
550
551 ret = cfg_getchar(cfg, flags);
552
553 cfg->reader.read_ptr = old_read_ptr;
554 cfg->reader.line_number = old_lineno;
555 cfg->reader.eof = old_eof;
556
557 return ret;
558 }
559
560 static const char *LINEBREAK_UNIX = "\\\n";
561 static const char *LINEBREAK_WIN32 = "\\\r\n";
562
563 static int is_linebreak(const char *pos)
564 {
565 return memcmp(pos - 1, LINEBREAK_UNIX, sizeof(LINEBREAK_UNIX)) == 0 ||
566 memcmp(pos - 2, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0;
567 }
568
569 /*
570 * Read and consume a line, returning it in newly-allocated memory.
571 */
572 static char *cfg_readline(git_config *cfg)
573 {
574 char *line = NULL;
575 char *line_src, *line_end;
576 int line_len;
577
578 line_src = cfg->reader.read_ptr;
579 line_end = strchr(line_src, '\n');
580
581 /* no newline at EOF */
582 if (line_end == NULL)
583 line_end = strchr(line_src, 0);
584 else
585 while (is_linebreak(line_end))
586 line_end = strchr(line_end + 1, '\n');
587
588
589 while (line_src < line_end && isspace(*line_src))
590 line_src++;
591
592 line = (char *)git__malloc((size_t)(line_end - line_src) + 1);
593 if (line == NULL)
594 return NULL;
595
596 line_len = 0;
597 while (line_src < line_end) {
598
599 if (memcmp(line_src, LINEBREAK_UNIX, sizeof(LINEBREAK_UNIX)) == 0) {
600 line_src += sizeof(LINEBREAK_UNIX);
601 continue;
602 }
603
604 if (memcmp(line_src, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0) {
605 line_src += sizeof(LINEBREAK_WIN32);
606 continue;
607 }
608
609 line[line_len++] = *line_src++;
610 }
611
612 line[line_len] = '\0';
613
614 while (--line_len >= 0 && isspace(line[line_len]))
615 line[line_len] = '\0';
616
617 if (*line_end == '\n')
618 line_end++;
619
620 if (*line_end == '\0')
621 cfg->reader.eof = 1;
622
623 cfg->reader.line_number++;
624 cfg->reader.read_ptr = line_end;
625
626 return line;
627 }
628
629 /*
630 * Consume a line, without storing it anywhere
631 */
632 void cfg_consume_line(git_config *cfg)
633 {
634 char *line_start, *line_end;
635 int len;
636
637 line_start = cfg->reader.read_ptr;
638 line_end = strchr(line_start, '\n');
639 /* No newline at EOF */
640 if(line_end == NULL){
641 line_end = strchr(line_start, '\0');
642 }
643
644 len = line_end - line_start;
645
646 if (*line_end == '\n')
647 line_end++;
648
649 if (*line_end == '\0')
650 cfg->reader.eof = 1;
651
652 cfg->reader.line_number++;
653 cfg->reader.read_ptr = line_end;
654 }
655
656 static inline int config_keychar(int c)
657 {
658 return isalnum(c) || c == '-';
659 }
660
661 static int parse_section_header_ext(const char *line, const char *base_name, char **section_name)
662 {
663 int buf_len, total_len, pos, rpos;
664 int c, ret;
665 char *subsection, *first_quote, *last_quote;
666 int error = GIT_SUCCESS;
667 int quote_marks;
668 /*
669 * base_name is what came before the space. We should be at the
670 * first quotation mark, except for now, line isn't being kept in
671 * sync so we only really use it to calculate the length.
672 */
673
674 first_quote = strchr(line, '"');
675 last_quote = strrchr(line, '"');
676
677 if (last_quote - first_quote == 0)
678 return GIT_EOBJCORRUPTED;
679
680 buf_len = last_quote - first_quote + 2;
681
682 subsection = git__malloc(buf_len + 2);
683 if (subsection == NULL)
684 return GIT_ENOMEM;
685
686 pos = 0;
687 rpos = 0;
688 quote_marks = 0;
689
690 line = first_quote;
691 c = line[rpos++];
692
693 /*
694 * At the end of each iteration, whatever is stored in c will be
695 * added to the string. In case of error, jump to out
696 */
697 do {
698 switch (c) {
699 case '"':
700 if (quote_marks++ >= 2)
701 return GIT_EOBJCORRUPTED;
702 break;
703 case '\\':
704 c = line[rpos++];
705 switch (c) {
706 case '"':
707 case '\\':
708 break;
709 default:
710 error = GIT_EOBJCORRUPTED;
711 goto out;
712 }
713 default:
714 break;
715 }
716
717 subsection[pos++] = c;
718 } while ((c = line[rpos++]) != ']');
719
720 subsection[pos] = '\0';
721
722 total_len = strlen(base_name) + strlen(subsection) + 2;
723 *section_name = git__malloc(total_len);
724 if (*section_name == NULL) {
725 error = GIT_ENOMEM;
726 goto out;
727 }
728
729 ret = snprintf(*section_name, total_len, "%s %s", base_name, subsection);
730 if (ret >= total_len) {
731 /* If this fails, we've checked the length wrong */
732 error = GIT_ERROR;
733 goto out;
734 } else if (ret < 0) {
735 error = GIT_EOSERR;
736 goto out;
737 }
738
739 git__strntolower(*section_name, strchr(*section_name, ' ') - *section_name);
740
741 out:
742 free(subsection);
743
744 return error;
745 }
746
747 static int parse_section_header(git_config *cfg, char **section_out)
748 {
749 char *name, *name_end;
750 int name_length, c, pos;
751 int error = GIT_SUCCESS;
752 char *line;
753
754 line = cfg_readline(cfg);
755 if (line == NULL)
756 return GIT_ENOMEM;
757
758 /* find the end of the variable's name */
759 name_end = strchr(line, ']');
760 if (name_end == NULL)
761 return GIT_EOBJCORRUPTED;
762
763 name = (char *)git__malloc((size_t)(name_end - line) + 1);
764 if (name == NULL)
765 return GIT_EOBJCORRUPTED;
766
767 name_length = 0;
768 pos = 0;
769
770 /* Make sure we were given a section header */
771 c = line[pos++];
772 if (c != '[') {
773 error = GIT_EOBJCORRUPTED;
774 goto error;
775 }
776
777 c = line[pos++];
778
779 do {
780 if (cfg->reader.eof){
781 error = GIT_EOBJCORRUPTED;
782 goto error;
783 }
784
785 if (isspace(c)){
786 name[name_length] = '\0';
787 error = parse_section_header_ext(line, name, section_out);
788 free(line);
789 free(name);
790 return error;
791 }
792
793 if (!config_keychar(c) && c != '.') {
794 error = GIT_EOBJCORRUPTED;
795 goto error;
796 }
797
798 name[name_length++] = tolower(c);
799
800 } while ((c = line[pos++]) != ']');
801
802 name[name_length] = 0;
803 free(line);
804 git__strtolower(name);
805 *section_out = name;
806 return GIT_SUCCESS;
807
808 error:
809 free(line);
810 free(name);
811 return error;
812 }
813
814 static int skip_bom(git_config *cfg)
815 {
816 static const unsigned char *utf8_bom = "\xef\xbb\xbf";
817
818 if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0)
819 cfg->reader.read_ptr += sizeof(utf8_bom);
820
821 /* TODO: the reference implementation does pretty stupid
822 shit with the BoM
823 */
824
825 return GIT_SUCCESS;
826 }
827
828 /*
829 (* basic types *)
830 digit = "0".."9"
831 integer = digit { digit }
832 alphabet = "a".."z" + "A" .. "Z"
833
834 section_char = alphabet | "." | "-"
835 extension_char = (* any character except newline *)
836 any_char = (* any character *)
837 variable_char = "alphabet" | "-"
838
839
840 (* actual grammar *)
841 config = { section }
842
843 section = header { definition }
844
845 header = "[" section [subsection | subsection_ext] "]"
846
847 subsection = "." section
848 subsection_ext = "\"" extension "\""
849
850 section = section_char { section_char }
851 extension = extension_char { extension_char }
852
853 definition = variable_name ["=" variable_value] "\n"
854
855 variable_name = variable_char { variable_char }
856 variable_value = string | boolean | integer
857
858 string = quoted_string | plain_string
859 quoted_string = "\"" plain_string "\""
860 plain_string = { any_char }
861
862 boolean = boolean_true | boolean_false
863 boolean_true = "yes" | "1" | "true" | "on"
864 boolean_false = "no" | "0" | "false" | "off"
865 */
866
867 static void strip_comments(char *line)
868 {
869 int quote_count = 0;
870 char *ptr;
871
872 for (ptr = line; *ptr; ++ptr) {
873 if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\')
874 quote_count++;
875
876 if ((ptr[0] == ';' || ptr[0] == '#') && (quote_count % 2) == 0) {
877 ptr[0] = '\0';
878 break;
879 }
880 }
881
882 if (isspace(ptr[-1])) {
883 /* TODO skip whitespace */
884 }
885 }
886
887 static int config_parse(git_config *cfg_file)
888 {
889 int error = GIT_SUCCESS, c;
890 char *current_section = NULL;
891 char *var_name;
892 char *var_value;
893 git_cvar *var;
894
895 /* Initialise the reading position */
896 cfg_file->reader.read_ptr = cfg_file->reader.buffer.data;
897 cfg_file->reader.eof = 0;
898
899 skip_bom(cfg_file);
900
901 while (error == GIT_SUCCESS && !cfg_file->reader.eof) {
902
903 c = cfg_peek(cfg_file, SKIP_WHITESPACE);
904
905 switch (c) {
906 case '\0': /* We've arrived at the end of the file */
907 break;
908
909 case '[': /* section header, new section begins */
910 free(current_section);
911 error = parse_section_header(cfg_file, &current_section);
912 break;
913
914 case ';':
915 case '#':
916 cfg_consume_line(cfg_file);
917 break;
918
919 default: /* assume variable declaration */
920 error = parse_variable(cfg_file, &var_name, &var_value);
921
922 if (error < GIT_SUCCESS)
923 break;
924
925 var = malloc(sizeof(git_cvar));
926 if (var == NULL) {
927 error = GIT_ENOMEM;
928 break;
929 }
930
931 memset(var, 0x0, sizeof(git_cvar));
932
933 var->section = git__strdup(current_section);
934 if (var->section == NULL) {
935 error = GIT_ENOMEM;
936 free(var);
937 break;
938 }
939
940 var->name = var_name;
941 var->value = var_value;
942 git__strtolower(var->name);
943
944 CVAR_LIST_APPEND(&cfg_file->var_list, var);
945
946 break;
947 }
948 }
949
950 if (current_section)
951 free(current_section);
952
953 return error;
954 }
955
956 static int is_multiline_var(const char *str)
957 {
958 char *end = strrchr(str, '\0') - 1;
959
960 while (isspace(*end))
961 --end;
962
963 return *end == '\\';
964 }
965
966 static int parse_multiline_variable(git_config *cfg, const char *first, char **out)
967 {
968 char *line = NULL, *end;
969 int error = GIT_SUCCESS, len, ret;
970 char *buf;
971
972 /* Check that the next line exists */
973 line = cfg_readline(cfg);
974 if (line == NULL)
975 return GIT_ENOMEM;
976
977 /* We've reached the end of the file, there is input missing */
978 if (line[0] == '\0') {
979 error = GIT_EOBJCORRUPTED;
980 goto out;
981 }
982
983 strip_comments(line);
984
985 /* If it was just a comment, pretend it didn't exist */
986 if (line[0] == '\0') {
987 error = parse_multiline_variable(cfg, first, out);
988 goto out;
989 }
990
991 /* Find the continuation character '\' and strip the whitespace */
992 end = strrchr(first, '\\');
993 while (isspace(end[-1]))
994 --end;
995
996 *end = '\0'; /* Terminate the string here */
997
998 len = strlen(first) + strlen(line) + 2;
999 buf = git__malloc(len);
1000 if (buf == NULL) {
1001 error = GIT_ENOMEM;
1002 goto out;
1003 }
1004
1005 ret = snprintf(buf, len, "%s %s", first, line);
1006 if (ret < 0) {
1007 error = GIT_EOSERR;
1008 free(buf);
1009 goto out;
1010 }
1011
1012 /*
1013 * If we need to continue reading the next line, pretend
1014 * everything we've read up to now was in one line and call
1015 * ourselves.
1016 */
1017 if (is_multiline_var(buf)) {
1018 char *final_val;
1019 error = parse_multiline_variable(cfg, buf, &final_val);
1020 free(buf);
1021 buf = final_val;
1022 }
1023
1024 *out = buf;
1025
1026 out:
1027 free(line);
1028 return error;
1029 }
1030
1031 static int parse_variable(git_config *cfg, char **var_name, char **var_value)
1032 {
1033 char *tmp;
1034 int error = GIT_SUCCESS;
1035 const char *var_end = NULL;
1036 const char *value_start = NULL;
1037 char *line;
1038
1039 line = cfg_readline(cfg);
1040 if (line == NULL)
1041 return GIT_ENOMEM;
1042
1043 strip_comments(line);
1044
1045 var_end = strchr(line, '=');
1046
1047 if (var_end == NULL)
1048 var_end = strchr(line, '\0');
1049 else
1050 value_start = var_end + 1;
1051
1052 if (isspace(var_end[-1])) {
1053 do var_end--;
1054 while (isspace(var_end[0]));
1055 }
1056
1057 tmp = strndup(line, var_end - line + 1);
1058 if (tmp == NULL) {
1059 error = GIT_ENOMEM;
1060 goto out;
1061 }
1062
1063 *var_name = tmp;
1064
1065 /*
1066 * Now, let's try to parse the value
1067 */
1068 if (value_start != NULL) {
1069
1070 while (isspace(value_start[0]))
1071 value_start++;
1072
1073 if (value_start[0] == '\0')
1074 goto out;
1075
1076 if (is_multiline_var(value_start)) {
1077 error = parse_multiline_variable(cfg, value_start, var_value);
1078 if (error < GIT_SUCCESS)
1079 free(*var_name);
1080 goto out;
1081 }
1082
1083 tmp = strdup(value_start);
1084 if (tmp == NULL) {
1085 free(*var_name);
1086 error = GIT_ENOMEM;
1087 goto out;
1088 }
1089
1090 *var_value = tmp;
1091 } else {
1092 /* If thre is no value, boolean true is assumed */
1093 *var_value = NULL;
1094 }
1095
1096 out:
1097 free(line);
1098 return error;
1099 }