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