config_file.c 36.8 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3
 *
Vicent Marti committed
4 5
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
6 7 8 9 10
 */

#include "common.h"
#include "config.h"
#include "fileops.h"
11
#include "filebuf.h"
12
#include "buffer.h"
13
#include "buf_text.h"
14
#include "git2/config.h"
15
#include "git2/sys/config.h"
16
#include "git2/types.h"
17
#include "strmap.h"
18
#include "array.h"
19

20
#include <ctype.h>
21 22
#include <sys/types.h>
#include <regex.h>
23

24
GIT__USE_STRMAP;
25

26 27
typedef struct cvar_t {
	struct cvar_t *next;
28
	git_config_entry *entry;
29
	int included; /* whether this is part of [include] */
30
} cvar_t;
31

32
typedef struct git_config_file_iter {
33
	git_config_iterator parent;
34
	git_strmap_iter iter;
35
	cvar_t* next_var;
36 37
} git_config_file_iter;

38 39
/* Max depth for [include] directives */
#define MAX_INCLUDE_DEPTH 10
40

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
#define CVAR_LIST_HEAD(list) ((list)->head)

#define CVAR_LIST_TAIL(list) ((list)->tail)

#define CVAR_LIST_NEXT(var) ((var)->next)

#define CVAR_LIST_EMPTY(list) ((list)->head == NULL)

#define CVAR_LIST_APPEND(list, var) do {\
	if (CVAR_LIST_EMPTY(list)) {\
		CVAR_LIST_HEAD(list) = CVAR_LIST_TAIL(list) = var;\
	} else {\
		CVAR_LIST_NEXT(CVAR_LIST_TAIL(list)) = var;\
		CVAR_LIST_TAIL(list) = var;\
	}\
} while(0)

#define CVAR_LIST_REMOVE_HEAD(list) do {\
	CVAR_LIST_HEAD(list) = CVAR_LIST_NEXT(CVAR_LIST_HEAD(list));\
} while(0)

#define CVAR_LIST_REMOVE_AFTER(var) do {\
	CVAR_LIST_NEXT(var) = CVAR_LIST_NEXT(CVAR_LIST_NEXT(var));\
} while(0)

#define CVAR_LIST_FOREACH(list, iter)\
	for ((iter) = CVAR_LIST_HEAD(list);\
		 (iter) != NULL;\
		 (iter) = CVAR_LIST_NEXT(iter))

/*
 * Inspired by the FreeBSD functions
 */
#define CVAR_LIST_FOREACH_SAFE(start, iter, tmp)\
	for ((iter) = CVAR_LIST_HEAD(vars);\
		 (iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\
		 (iter) = (tmp))

79
struct reader {
80 81
	time_t file_mtime;
	size_t file_size;
82 83 84 85 86 87 88
	char *file_path;
	git_buf buffer;
	char *read_ptr;
	int line_number;
	int eof;
};

89
typedef struct {
Ben Straub committed
90
	git_config_backend parent;
91

92
	git_strmap *values;
93

94
	git_array_t(struct reader) readers;
95

96 97
	char  *file_path;

98
	git_config_level_t level;
99
} diskfile_backend;
100

101 102
static int config_parse(diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth);
static int parse_variable(struct reader *reader, char **var_name, char **var_value);
103
static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value);
104
static char *escape_value(const char *ptr);
105

106
static void set_parse_error(struct reader *reader, int col, const char *error_str)
107 108
{
	giterr_set(GITERR_CONFIG, "Failed to parse config file: %s (in %s:%d, column %d)",
109
		error_str, reader->file_path, reader->line_number, col);
110 111
}

112
static void cvar_free(cvar_t *var)
113 114 115 116
{
	if (var == NULL)
		return;

117 118 119
	git__free((char*)var->entry->name);
	git__free((char *)var->entry->value);
	git__free(var->entry);
120
	git__free(var);
121 122
}

123 124 125 126 127
static int cvar_length(cvar_t *var)
{
	int length = 0;

	while (var) {
Vicent Marti committed
128
		length++;
129 130 131 132 133 134
		var = var->next;
	}

	return length;
}

135 136 137 138 139 140 141 142 143 144 145 146
int git_config_file_normalize_section(char *start, char *end)
{
	char *scan;

	if (start == end)
		return GIT_EINVALIDSPEC;

	/* Validate and downcase range */
	for (scan = start; *scan; ++scan) {
		if (end && scan >= end)
			break;
		if (isalnum(*scan))
Linquize committed
147
			*scan = (char)tolower(*scan);
148 149 150 151 152 153 154 155 156 157
		else if (*scan != '-' || scan == start)
			return GIT_EINVALIDSPEC;
	}

	if (scan == start)
		return GIT_EINVALIDSPEC;

	return 0;
}

158
static void free_vars(git_strmap *values)
159
{
160
	cvar_t *var = NULL;
161

162 163
	if (values == NULL)
		return;
164

165
	git_strmap_foreach_value(values, var,
166 167 168 169 170
		while (var != NULL) {
			cvar_t *next = CVAR_LIST_NEXT(var);
			cvar_free(var);
			var = next;
		});
171

172
	git_strmap_free(values);
173 174
}

175
static int config_open(git_config_backend *cfg, git_config_level_t level)
176
{
177
	int res;
178
	struct reader *reader;
179
	diskfile_backend *b = (diskfile_backend *)cfg;
180

181 182
	b->level = level;

183
	b->values = git_strmap_alloc();
184
	GITERR_CHECK_ALLOC(b->values);
185

186 187 188 189 190 191 192 193
	git_array_init(b->readers);
	reader = git_array_alloc(b->readers);
	memset(reader, 0, sizeof(struct reader));

	reader->file_path = git__strdup(b->file_path);
	GITERR_CHECK_ALLOC(reader->file_path);

	git_buf_init(&reader->buffer, 0);
194
	res = git_futils_readbuffer_updated(
195
		&reader->buffer, b->file_path, &reader->file_mtime, &reader->file_size, NULL);
196

197
	/* It's fine if the file doesn't exist */
198 199 200
	if (res == GIT_ENOTFOUND)
		return 0;

201
	if (res < 0 || (res = config_parse(b, reader, level, 0)) < 0) {
202 203 204
		free_vars(b->values);
		b->values = NULL;
	}
205

206
	reader = git_array_get(b->readers, 0);
207
	git_buf_free(&reader->buffer);
208 209 210
	return res;
}

Ben Straub committed
211
static int config_refresh(git_config_backend *cfg)
212
{
213
	int res = 0, updated = 0, any_updated = 0;
214 215
	diskfile_backend *b = (diskfile_backend *)cfg;
	git_strmap *old_values;
216 217
	struct reader *reader;
	uint32_t i;
218

219 220 221 222 223 224 225 226 227 228 229 230 231
	for (i = 0; i < git_array_size(b->readers); i++) {
		reader = git_array_get(b->readers, i);
		res = git_futils_readbuffer_updated(
			&reader->buffer, reader->file_path, &reader->file_mtime, &reader->file_size, &updated);

		if (res < 0)
			return (res == GIT_ENOTFOUND) ? 0 : res;

		if (updated)
			any_updated = 1;
	}

	if (!any_updated)
232 233 234 235 236 237 238
		return (res == GIT_ENOTFOUND) ? 0 : res;

	/* need to reload - store old values and prep for reload */
	old_values = b->values;
	b->values = git_strmap_alloc();
	GITERR_CHECK_ALLOC(b->values);

239
	if ((res = config_parse(b, reader, b->level, 0)) < 0) {
240 241 242 243 244 245
		free_vars(b->values);
		b->values = old_values;
	} else {
		free_vars(old_values);
	}

246
	git_buf_free(&reader->buffer);
247
	return res;
248 249
}

Ben Straub committed
250
static void backend_free(git_config_backend *_backend)
251
{
252
	diskfile_backend *backend = (diskfile_backend *)_backend;
253
	uint32_t i;
254 255 256 257

	if (backend == NULL)
		return;

258 259 260 261 262 263
	for (i = 0; i < git_array_size(backend->readers); i++) {
		struct reader *r = git_array_get(backend->readers, i);
		git__free(r->file_path);
	}
	git_array_clear(backend->readers);

264
	git__free(backend->file_path);
265
	free_vars(backend->values);
266
	git__free(backend);
267 268
}

269
static void config_iterator_free(
270
	git_config_iterator* iter)
271
{
272 273
	git__free(iter);
}
274

275
static int config_iterator_next(
276
	git_config_entry **entry,
277
	git_config_iterator *iter)
278
{
279 280 281
	git_config_file_iter *it = (git_config_file_iter *) iter;
	diskfile_backend *b = (diskfile_backend *) it->parent.backend;
	int err = 0;
282
	cvar_t * var;
283

284
	if (it->next_var == NULL) {
285
		err = git_strmap_next((void**) &var, &(it->iter), b->values);
286
	} else {
287
		var = it->next_var;
288
	}
289

290
	if (err < 0) {
291
		it->next_var = NULL;
292
		return err;
293
	}
294

295
	*entry = var->entry;
296
	it->next_var = CVAR_LIST_NEXT(var);
297

298 299
	return 0;
}
300

301 302 303 304 305 306
static int config_iterator_new(
	git_config_iterator **iter,
	struct git_config_backend* backend)
{
	diskfile_backend *b = (diskfile_backend *)backend;
	git_config_file_iter *it = git__calloc(1, sizeof(git_config_file_iter));
307

308 309
	GIT_UNUSED(b);

310 311 312 313 314 315 316 317 318
	GITERR_CHECK_ALLOC(it);

	it->parent.backend = backend;
	it->iter = git_strmap_begin(b->values);
	it->next_var = NULL;

	it->parent.next = config_iterator_next;
	it->parent.free = config_iterator_free;
	*iter = (git_config_iterator *) it;
319 320

	return 0;
321 322
}

Ben Straub committed
323
static int config_set(git_config_backend *cfg, const char *name, const char *value)
324
{
325
	cvar_t *var = NULL, *old_var = NULL;
326
	diskfile_backend *b = (diskfile_backend *)cfg;
327
	char *key, *esc_value = NULL;
328
	khiter_t pos;
329
	int rval, ret;
330

331
	if ((rval = git_config__normalize_name(name, &key)) < 0)
332
		return rval;
333 334

	/*
335 336
	 * Try to find it in the existing values and update it if it
	 * only has one value.
337
	 */
338 339 340
	pos = git_strmap_lookup_index(b->values, key);
	if (git_strmap_valid_index(b->values, pos)) {
		cvar_t *existing = git_strmap_value_at(b->values, pos);
341
		char *tmp = NULL;
342 343

		git__free(key);
Russell Belfer committed
344

345 346 347 348
		if (existing->next != NULL) {
			giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set");
			return -1;
		}
349

Russell Belfer committed
350
		/* don't update if old and new values already match */
351 352
		if ((!existing->entry->value && !value) ||
			(existing->entry->value && value && !strcmp(existing->entry->value, value)))
Russell Belfer committed
353 354
			return 0;

355 356 357
		if (value) {
			tmp = git__strdup(value);
			GITERR_CHECK_ALLOC(tmp);
358 359
			esc_value = escape_value(value);
			GITERR_CHECK_ALLOC(esc_value);
360
		}
361

362 363
		git__free((void *)existing->entry->value);
		existing->entry->value = tmp;
364

365
		ret = config_write(b, existing->entry->name, NULL, esc_value);
366 367 368

		git__free(esc_value);
		return ret;
369 370
	}

371
	var = git__malloc(sizeof(cvar_t));
372
	GITERR_CHECK_ALLOC(var);
373
	memset(var, 0x0, sizeof(cvar_t));
374 375 376
	var->entry = git__malloc(sizeof(git_config_entry));
	GITERR_CHECK_ALLOC(var->entry);
	memset(var->entry, 0x0, sizeof(git_config_entry));
377

378 379
	var->entry->name = key;
	var->entry->value = NULL;
380

381
	if (value) {
382 383
		var->entry->value = git__strdup(value);
		GITERR_CHECK_ALLOC(var->entry->value);
384 385
		esc_value = escape_value(value);
		GITERR_CHECK_ALLOC(esc_value);
386 387
	}

388
	if ((ret = config_write(b, key, NULL, esc_value)) < 0) {
389
		git__free(esc_value);
390
		cvar_free(var);
391
		return ret;
392 393
	}

394
	git__free(esc_value);
395
	git_strmap_insert2(b->values, key, var, old_var, rval);
396
	if (rval < 0)
397
		return -1;
398 399
	if (old_var != NULL)
		cvar_free(old_var);
400

401
	return 0;
402 403 404 405 406
}

/*
 * Internal function that actually gets the value in string form
 */
Ben Straub committed
407
static int config_get(const git_config_backend *cfg, const char *name, const git_config_entry **out)
408
{
409
	diskfile_backend *b = (diskfile_backend *)cfg;
410
	char *key;
411
	khiter_t pos;
412
	int error;
413
	cvar_t *var;
414

415
	if ((error = git_config__normalize_name(name, &key)) < 0)
416
		return error;
417

418
	pos = git_strmap_lookup_index(b->values, key);
419
	git__free(key);
420

421
	/* no error message; the config system will write one */
422
	if (!git_strmap_valid_index(b->values, pos))
423
		return GIT_ENOTFOUND;
424

425 426 427 428 429
	var = git_strmap_value_at(b->values, pos);
	while (var->next)
		var = var->next;

	*out = var->entry;
430

431
	return 0;
432 433
}

434
static int config_set_multivar(
Ben Straub committed
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 440 441
	diskfile_backend *b = (diskfile_backend *)cfg;
	char *key;
	regex_t preg;
442
	int result;
443
	khiter_t pos;
444

445
	assert(regexp);
446

447
	if ((result = git_config__normalize_name(name, &key)) < 0)
448
		return result;
449

450 451
	pos = git_strmap_lookup_index(b->values, key);
	if (!git_strmap_valid_index(b->values, pos)) {
452 453
		/* If we don't have it, behave like a normal set */
		result = config_set(cfg, name, value);
454
		git__free(key);
455
		return result;
456
	}
457

458
	var = git_strmap_value_at(b->values, pos);
459

460 461
	result = regcomp(&preg, regexp, REG_EXTENDED);
	if (result < 0) {
462
		git__free(key);
463
		giterr_set_regex(&preg, result);
464
		regfree(&preg);
465
		return -1;
466
	}
467

468
	for (;;) {
469
		if (regexec(&preg, var->entry->value, 0, NULL, 0) == 0) {
470
			char *tmp = git__strdup(value);
471
			GITERR_CHECK_ALLOC(tmp);
472

473 474
			git__free((void *)var->entry->value);
			var->entry->value = tmp;
475 476 477
			replaced = 1;
		}

478
		if (var->next == NULL)
479
			break;
480 481 482

		var = var->next;
	}
483 484 485 486

	/* If we've reached the end of the variables and we haven't found it yet, we need to append it */
	if (!replaced) {
		newvar = git__malloc(sizeof(cvar_t));
487
		GITERR_CHECK_ALLOC(newvar);
488
		memset(newvar, 0x0, sizeof(cvar_t));
489 490 491 492 493 494
		newvar->entry = git__malloc(sizeof(git_config_entry));
		GITERR_CHECK_ALLOC(newvar->entry);
		memset(newvar->entry, 0x0, sizeof(git_config_entry));

		newvar->entry->name = git__strdup(var->entry->name);
		GITERR_CHECK_ALLOC(newvar->entry->name);
495

496 497
		newvar->entry->value = git__strdup(value);
		GITERR_CHECK_ALLOC(newvar->entry->value);
498

499
		newvar->entry->level = var->entry->level;
500 501 502 503

		var->next = newvar;
	}

504
	result = config_write(b, key, &preg, value);
505

506
	git__free(key);
507
	regfree(&preg);
508 509

	return result;
510 511
}

Ben Straub committed
512
static int config_delete(git_config_backend *cfg, const char *name)
513
{
514
	cvar_t *var;
515
	diskfile_backend *b = (diskfile_backend *)cfg;
516
	char *key;
517
	int result;
518
	khiter_t pos;
519

520
	if ((result = git_config__normalize_name(name, &key)) < 0)
521
		return result;
522

523
	pos = git_strmap_lookup_index(b->values, key);
524
	git__free(key);
525

526 527
	if (!git_strmap_valid_index(b->values, pos)) {
		giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name);
528
		return GIT_ENOTFOUND;
529
	}
530

531
	var = git_strmap_value_at(b->values, pos);
532

533 534 535 536
	if (var->next != NULL) {
		giterr_set(GITERR_CONFIG, "Cannot delete multivar with a single delete");
		return -1;
	}
537

538
	git_strmap_delete_at(b->values, pos);
539

540
	result = config_write(b, var->entry->name, NULL, NULL);
541

542 543
	cvar_free(var);
	return result;
544 545
}

546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
static int config_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp)
{
	cvar_t *var, *prev = NULL, *new_head = NULL;
	cvar_t **to_delete;
	int to_delete_idx;
	diskfile_backend *b = (diskfile_backend *)cfg;
	char *key;
	regex_t preg;
	int result;
	khiter_t pos;

	if ((result = git_config__normalize_name(name, &key)) < 0)
		return result;

	pos = git_strmap_lookup_index(b->values, key);

	if (!git_strmap_valid_index(b->values, pos)) {
		giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name);
		git__free(key);
		return GIT_ENOTFOUND;
	}

	var = git_strmap_value_at(b->values, pos);

	result = regcomp(&preg, regexp, REG_EXTENDED);
	if (result < 0) {
		git__free(key);
		giterr_set_regex(&preg, result);
		regfree(&preg);
		return -1;
	}

	to_delete = git__calloc(cvar_length(var), sizeof(cvar_t *));
	GITERR_CHECK_ALLOC(to_delete);
	to_delete_idx = 0;

Vicent Marti committed
582 583
	while (var != NULL) {
		cvar_t *next = var->next;
584 585 586 587 588

		if (regexec(&preg, var->entry->value, 0, NULL, 0) == 0) {
			// If we are past the head, reattach previous node to next one,
			// otherwise set the new head for the strmap.
			if (prev != NULL) {
Vicent Marti committed
589
				prev->next = next;
590
			} else {
Vicent Marti committed
591
				new_head = next;
592 593 594 595 596 597 598
			}

			to_delete[to_delete_idx++] = var;
		} else {
			prev = var;
		}

Vicent Marti committed
599
		var = next;
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614
	}

	if (new_head != NULL) {
		git_strmap_set_value_at(b->values, pos, new_head);
	} else {
		git_strmap_delete_at(b->values, pos);
	}

	if (to_delete_idx > 0)
		result = config_write(b, key, &preg, NULL);

	while (to_delete_idx-- > 0)
		cvar_free(to_delete[to_delete_idx]);

	git__free(key);
615 616
	git__free(to_delete);
	regfree(&preg);
617 618 619
	return result;
}

Ben Straub committed
620
int git_config_file__ondisk(git_config_backend **out, const char *path)
621
{
622
	diskfile_backend *backend;
623

624
	backend = git__calloc(1, sizeof(diskfile_backend));
625
	GITERR_CHECK_ALLOC(backend);
626

627
	backend->parent.version = GIT_CONFIG_BACKEND_VERSION;
628 629

	backend->file_path = git__strdup(path);
630
	GITERR_CHECK_ALLOC(backend->file_path);
631 632 633 634

	backend->parent.open = config_open;
	backend->parent.get = config_get;
	backend->parent.set = config_set;
635
	backend->parent.set_multivar = config_set_multivar;
636
	backend->parent.del = config_delete;
637
	backend->parent.del_multivar = config_delete_multivar;
638
	backend->parent.iterator = config_iterator_new;
639
	backend->parent.refresh = config_refresh;
640 641
	backend->parent.free = backend_free;

Ben Straub committed
642
	*out = (git_config_backend *)backend;
643

644
	return 0;
645 646
}

647
static int reader_getchar_raw(struct reader *reader)
648 649 650
{
	int c;

651
	c = *reader->read_ptr++;
652 653 654 655 656

	/*
	Win 32 line breaks: if we find a \r\n sequence,
	return only the \n as a newline
	*/
657 658
	if (c == '\r' && *reader->read_ptr == '\n') {
		reader->read_ptr++;
659 660 661 662
		c = '\n';
	}

	if (c == '\n')
663
		reader->line_number++;
664 665

	if (c == 0) {
666
		reader->eof = 1;
667 668 669 670 671 672 673 674 675
		c = '\n';
	}

	return c;
}

#define SKIP_WHITESPACE (1 << 1)
#define SKIP_COMMENTS (1 << 2)

676
static int reader_getchar(struct reader *reader, int flags)
677 678 679 680 681
{
	const int skip_whitespace = (flags & SKIP_WHITESPACE);
	const int skip_comments = (flags & SKIP_COMMENTS);
	int c;

682
	assert(reader->read_ptr);
683

684 685 686 687
	do {
		c = reader_getchar_raw(reader);
	} while (skip_whitespace && git__isspace(c) &&
	       !reader->eof);
688 689

	if (skip_comments && (c == '#' || c == ';')) {
690 691 692
		do {
			c = reader_getchar_raw(reader);
		} while (c != '\n');
693 694 695 696 697 698 699 700
	}

	return c;
}

/*
 * Read the next char, but don't move the reading pointer.
 */
701
static int reader_peek(struct reader *reader, int flags)
702 703 704 705 706
{
	void *old_read_ptr;
	int old_lineno, old_eof;
	int ret;

707
	assert(reader->read_ptr);
708

709 710 711
	old_read_ptr = reader->read_ptr;
	old_lineno = reader->line_number;
	old_eof = reader->eof;
712

713
	ret = reader_getchar(reader, flags);
714

715 716 717
	reader->read_ptr = old_read_ptr;
	reader->line_number = old_lineno;
	reader->eof = old_eof;
718 719 720 721 722 723 724

	return ret;
}

/*
 * Read and consume a line, returning it in newly-allocated memory.
 */
725
static char *reader_readline(struct reader *reader, bool skip_whitespace)
726 727 728
{
	char *line = NULL;
	char *line_src, *line_end;
729
	size_t line_len;
730

731
	line_src = reader->read_ptr;
732

733 734
	if (skip_whitespace) {
		/* Skip empty empty lines */
735
		while (git__isspace(*line_src))
736 737
			++line_src;
	}
738

739
	line_end = strchr(line_src, '\n');
740

741
	/* no newline at EOF */
742 743 744
	if (line_end == NULL)
		line_end = strchr(line_src, 0);

745
	line_len = line_end - line_src;
746

747
	line = git__malloc(line_len + 1);
748 749 750
	if (line == NULL)
		return NULL;

751
	memcpy(line, line_src, line_len);
752

753
	do line[line_len] = '\0';
754
	while (line_len-- > 0 && git__isspace(line[line_len]));
755 756 757 758 759

	if (*line_end == '\n')
		line_end++;

	if (*line_end == '\0')
760
		reader->eof = 1;
761

762 763
	reader->line_number++;
	reader->read_ptr = line_end;
764 765 766 767 768 769 770

	return line;
}

/*
 * Consume a line, without storing it anywhere
 */
771
static void reader_consume_line(struct reader *reader)
772 773 774
{
	char *line_start, *line_end;

775
	line_start = reader->read_ptr;
776 777 778 779 780 781 782 783 784 785
	line_end = strchr(line_start, '\n');
	/* No newline at EOF */
	if(line_end == NULL){
		line_end = strchr(line_start, '\0');
	}

	if (*line_end == '\n')
		line_end++;

	if (*line_end == '\0')
786
		reader->eof = 1;
787

788 789
	reader->line_number++;
	reader->read_ptr = line_end;
790 791
}

792
GIT_INLINE(int) config_keychar(int c)
793 794 795 796
{
	return isalnum(c) || c == '-';
}

797
static int parse_section_header_ext(struct reader *reader, const char *line, const char *base_name, char **section_name)
798
{
799 800 801
	int c, rpos;
	char *first_quote, *last_quote;
	git_buf buf = GIT_BUF_INIT;
802 803 804 805 806 807 808 809 810
	/*
	 * base_name is what came before the space. We should be at the
	 * first quotation mark, except for now, line isn't being kept in
	 * sync so we only really use it to calculate the length.
	 */

	first_quote = strchr(line, '"');
	last_quote = strrchr(line, '"');

811
	if (last_quote - first_quote == 0) {
812
		set_parse_error(reader, 0, "Missing closing quotation mark in section header");
813 814
		return -1;
	}
815

816 817
	git_buf_grow(&buf, strlen(base_name) + last_quote - first_quote + 2);
	git_buf_printf(&buf, "%s.", base_name);
818 819 820 821

	rpos = 0;

	line = first_quote;
822
	c = line[++rpos];
823 824 825 826 827 828

	/*
	 * At the end of each iteration, whatever is stored in c will be
	 * added to the string. In case of error, jump to out
	 */
	do {
829

830
		switch (c) {
831
		case 0:
832
			set_parse_error(reader, 0, "Unexpected end-of-line in section header");
833 834 835
			git_buf_free(&buf);
			return -1;

836
		case '"':
837
			goto end_parse;
838

839
		case '\\':
840
			c = line[++rpos];
841

842 843
			if (c == 0) {
				set_parse_error(reader, rpos, "Unexpected end-of-line in section header");
844 845
				git_buf_free(&buf);
				return -1;
846
			}
847

848 849 850 851
		default:
			break;
		}

Linquize committed
852
		git_buf_putc(&buf, (char)c);
853 854 855 856 857 858 859 860 861
		c = line[++rpos];
	} while (line + rpos < last_quote);

end_parse:
	if (line[rpos] != '"' || line[rpos + 1] != ']') {
		set_parse_error(reader, rpos, "Unexpected text after closing quotes");
		git_buf_free(&buf);
		return -1;
	}
862

863 864
	*section_name = git_buf_detach(&buf);
	return 0;
865 866
}

867
static int parse_section_header(struct reader *reader, char **section_out)
868 869 870
{
	char *name, *name_end;
	int name_length, c, pos;
871
	int result;
872 873
	char *line;

874
	line = reader_readline(reader, true);
875
	if (line == NULL)
876
		return -1;
877 878

	/* find the end of the variable's name */
879
	name_end = strrchr(line, ']');
schu committed
880
	if (name_end == NULL) {
881
		git__free(line);
882
		set_parse_error(reader, 0, "Missing ']' in section header");
883
		return -1;
schu committed
884
	}
885 886

	name = (char *)git__malloc((size_t)(name_end - line) + 1);
887
	GITERR_CHECK_ALLOC(name);
888 889 890 891 892 893

	name_length = 0;
	pos = 0;

	/* Make sure we were given a section header */
	c = line[pos++];
894
	assert(c == '[');
895 896 897 898

	c = line[pos++];

	do {
899
		if (git__isspace(c)){
900
			name[name_length] = '\0';
901
			result = parse_section_header_ext(reader, line, name, section_out);
902 903
			git__free(line);
			git__free(name);
904
			return result;
905 906 907
		}

		if (!config_keychar(c) && c != '.') {
908
			set_parse_error(reader, pos, "Unexpected character in header");
909
			goto fail_parse;
910 911
		}

912
		name[name_length++] = (char) tolower(c);
913 914 915

	} while ((c = line[pos++]) != ']');

schu committed
916
	if (line[pos - 1] != ']') {
917
		set_parse_error(reader, pos, "Unexpected end of file");
918
		goto fail_parse;
schu committed
919
	}
920

921
	git__free(line);
922 923

	name[name_length] = 0;
924 925
	*section_out = name;

926 927 928
	return 0;

fail_parse:
929 930
	git__free(line);
	git__free(name);
931
	return -1;
932 933
}

934
static int skip_bom(struct reader *reader)
935
{
936 937
	git_bom_t bom;
	int bom_offset = git_buf_text_detect_bom(&bom,
938
		&reader->buffer, reader->read_ptr - reader->buffer.ptr);
939

940
	if (bom == GIT_BOM_UTF8)
941
		reader->read_ptr += bom_offset;
942

943
	/* TODO: reference implementation is pretty stupid with BoM */
944

945
	return 0;
946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986
}

/*
	(* basic types *)
	digit = "0".."9"
	integer = digit { digit }
	alphabet = "a".."z" + "A" .. "Z"

	section_char = alphabet | "." | "-"
	extension_char = (* any character except newline *)
	any_char = (* any character *)
	variable_char = "alphabet" | "-"


	(* actual grammar *)
	config = { section }

	section = header { definition }

	header = "[" section [subsection | subsection_ext] "]"

	subsection = "." section
	subsection_ext = "\"" extension "\""

	section = section_char { section_char }
	extension = extension_char { extension_char }

	definition = variable_name ["=" variable_value] "\n"

	variable_name = variable_char { variable_char }
	variable_value = string | boolean | integer

	string = quoted_string | plain_string
	quoted_string = "\"" plain_string "\""
	plain_string = { any_char }

	boolean = boolean_true | boolean_false
	boolean_true = "yes" | "1" | "true" | "on"
	boolean_false = "no" | "0" | "false" | "off"
*/

987
static int strip_comments(char *line, int in_quotes)
988
{
989
	int quote_count = in_quotes;
990 991 992 993 994 995 996 997 998 999 1000 1001
	char *ptr;

	for (ptr = line; *ptr; ++ptr) {
		if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\')
			quote_count++;

		if ((ptr[0] == ';' || ptr[0] == '#') && (quote_count % 2) == 0) {
			ptr[0] = '\0';
			break;
		}
	}

1002
	/* skip any space at the end */
1003
	if (ptr > line && git__isspace(ptr[-1])) {
1004
		ptr--;
1005
	}
1006 1007 1008
	ptr[0] = '\0';

	return quote_count;
1009 1010
}

1011 1012 1013 1014 1015 1016 1017 1018 1019
static int included_path(git_buf *out, const char *dir, const char *path)
{
	/* From the user's home */
	if (path[0] == '~' && path[1] == '/')
		return git_futils_find_global_file(out, &path[1]);

	return git_path_join_unrooted(out, path, dir, NULL);
}

1020
static int config_parse(diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth)
1021
{
1022
	int c;
1023 1024 1025
	char *current_section = NULL;
	char *var_name;
	char *var_value;
1026
	cvar_t *var, *existing;
1027
	git_buf buf = GIT_BUF_INIT;
1028
	int result = 0;
1029
	khiter_t pos;
1030
	uint32_t reader_idx;
1031

1032 1033 1034 1035
	if (depth >= MAX_INCLUDE_DEPTH) {
		giterr_set(GITERR_CONFIG, "Maximum config include depth reached");
		return -1;
	}
1036

1037
	reader_idx = git_array_size(cfg_file->readers) - 1;
1038
	/* Initialize the reading position */
1039 1040
	reader->read_ptr = reader->buffer.ptr;
	reader->eof = 0;
1041

1042
	/* If the file is empty, there's nothing for us to do */
1043
	if (*reader->read_ptr == '\0')
1044
		return 0;
1045

1046
	skip_bom(reader);
1047

1048
	while (result == 0 && !reader->eof) {
1049

1050
		c = reader_peek(reader, SKIP_WHITESPACE);
1051 1052

		switch (c) {
1053
		case '\n': /* EOF when peeking, set EOF in the reader to exit the loop */
1054
			reader->eof = 1;
1055 1056 1057
			break;

		case '[': /* section header, new section begins */
1058
			git__free(current_section);
1059
			current_section = NULL;
1060
			result = parse_section_header(reader, &current_section);
1061 1062 1063 1064
			break;

		case ';':
		case '#':
1065
			reader_consume_line(reader);
1066 1067 1068
			break;

		default: /* assume variable declaration */
1069
			result = parse_variable(reader, &var_name, &var_value);
1070
			if (result < 0)
1071 1072
				break;

1073
			var = git__malloc(sizeof(cvar_t));
1074
			GITERR_CHECK_ALLOC(var);
1075
			memset(var, 0x0, sizeof(cvar_t));
1076 1077 1078
			var->entry = git__malloc(sizeof(git_config_entry));
			GITERR_CHECK_ALLOC(var->entry);
			memset(var->entry, 0x0, sizeof(git_config_entry));
1079

1080 1081 1082 1083
			git__strtolower(var_name);
			git_buf_printf(&buf, "%s.%s", current_section, var_name);
			git__free(var_name);

1084 1085
			if (git_buf_oom(&buf))
				return -1;
1086

1087 1088 1089
			var->entry->name = git_buf_detach(&buf);
			var->entry->value = var_value;
			var->entry->level = level;
1090
			var->included = !!depth;
1091

1092
			/* Add or append the new config option */
1093
			pos = git_strmap_lookup_index(cfg_file->values, var->entry->name);
1094
			if (!git_strmap_valid_index(cfg_file->values, pos)) {
1095
				git_strmap_insert(cfg_file->values, var->entry->name, var, result);
1096 1097 1098
				if (result < 0)
					break;
				result = 0;
1099
			} else {
1100
				existing = git_strmap_value_at(cfg_file->values, pos);
1101 1102 1103 1104 1105
				while (existing->next != NULL) {
					existing = existing->next;
				}
				existing->next = var;
			}
1106

1107
			if (!git__strcmp(var->entry->name, "include.path")) {
1108
				struct reader *r;
1109 1110
				git_buf path = GIT_BUF_INIT;
				char *dir;
1111
				uint32_t index;
1112

1113
				r = git_array_alloc(cfg_file->readers);
1114 1115
				/* The reader may have been reallocated */
				reader = git_array_get(cfg_file->readers, reader_idx);
1116
				memset(r, 0, sizeof(struct reader));
1117 1118 1119
				if ((result = git_path_dirname_r(&path, reader->file_path)) < 0)
					break;

1120 1121
				/* We need to know out index in the array, as the next config_parse call may realloc */
				index = git_array_size(cfg_file->readers) - 1;
1122 1123 1124 1125 1126 1127 1128
				dir = git_buf_detach(&path);
				result = included_path(&path, dir, var->entry->value);
				git__free(dir);

				if (result < 0)
					break;

1129 1130 1131 1132
				r->file_path = git_buf_detach(&path);
				git_buf_init(&r->buffer, 0);
				if ((result = git_futils_readbuffer_updated(&r->buffer, r->file_path, &r->file_mtime,
									    &r->file_size, NULL)) < 0)
1133 1134
					break;

1135
				result = config_parse(cfg_file, r, level, depth+1);
1136
				r = git_array_get(cfg_file->readers, index);
1137
				git_buf_free(&r->buffer);
1138 1139 1140 1141 1142

				if (result < 0)
					break;
			}

1143 1144 1145 1146
			break;
		}
	}

1147
	git__free(current_section);
1148
	return result;
1149 1150
}

1151
static int write_section(git_filebuf *file, const char *key)
1152
{
1153
	int result;
1154
	const char *dot;
1155
	git_buf buf = GIT_BUF_INIT;
1156

1157
	/* All of this just for [section "subsection"] */
1158
	dot = strchr(key, '.');
1159
	git_buf_putc(&buf, '[');
1160
	if (dot == NULL) {
1161
		git_buf_puts(&buf, key);
1162
	} else {
1163
		char *escaped;
1164
		git_buf_put(&buf, key, dot - key);
1165 1166 1167 1168
		escaped = escape_value(dot + 1);
		GITERR_CHECK_ALLOC(escaped);
		git_buf_printf(&buf, " \"%s\"", escaped);
		git__free(escaped);
1169 1170
	}
	git_buf_puts(&buf, "]\n");
1171

1172
	if (git_buf_oom(&buf))
1173
		return -1;
1174

1175
	result = git_filebuf_write(file, git_buf_cstr(&buf), buf.size);
1176
	git_buf_free(&buf);
1177

1178
	return result;
1179 1180
}

Vicent Marti committed
1181
static const char *quotes_for_value(const char *value)
1182
{
Vicent Marti committed
1183 1184 1185 1186 1187 1188
	const char *ptr;

	if (value[0] == ' ' || value[0] == '\0')
		return "\"";

	for (ptr = value; *ptr; ++ptr) {
1189
		if (*ptr == ';' || *ptr == '#')
Vicent Marti committed
1190
			return "\"";
1191 1192
	}

Vicent Marti committed
1193 1194 1195 1196
	if (ptr[-1] == ' ')
		return "\"";

	return "";
1197 1198
}

1199 1200 1201
/*
 * This is pretty much the parsing, except we write out anything we don't have
 */
1202
static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char* value)
1203
{
1204 1205
	int result, c;
	int section_matches = 0, last_section_matched = 0, preg_replaced = 0, write_trailer = 0;
1206
	const char *pre_end = NULL, *post_start = NULL, *data_start, *write_start;
1207
	char *current_section = NULL, *section, *name, *ldot;
1208
	git_filebuf file = GIT_FILEBUF_INIT;
1209
	struct reader *reader = git_array_get(cfg->readers, 0);
1210 1211

	/* We need to read in our own config file */
1212
	result = git_futils_readbuffer(&reader->buffer, cfg->file_path);
1213 1214

	/* Initialise the reading position */
1215
	if (result == GIT_ENOTFOUND) {
1216 1217
		reader->read_ptr = NULL;
		reader->eof = 1;
1218
		data_start = NULL;
1219
		git_buf_clear(&reader->buffer);
1220
	} else if (result == 0) {
1221 1222 1223
		reader->read_ptr = reader->buffer.ptr;
		reader->eof = 0;
		data_start = reader->read_ptr;
1224 1225
	} else {
		return -1; /* OS error when reading the file */
1226
	}
1227

1228 1229
	write_start = data_start;

1230
	/* Lock the file */
nulltoken committed
1231 1232 1233 1234 1235
	if ((result = git_filebuf_open(
		&file, cfg->file_path, 0, GIT_CONFIG_FILE_MODE)) < 0) {
			git_buf_free(&reader->buffer);
			return result;
	}
1236

1237
	skip_bom(reader);
1238 1239 1240
	ldot = strrchr(key, '.');
	name = ldot + 1;
	section = git__strndup(key, ldot - key);
1241

1242 1243
	while (!reader->eof) {
		c = reader_peek(reader, SKIP_WHITESPACE);
1244

1245
		if (c == '\0') { /* We've arrived at the end of the file */
1246 1247
			break;

1248
		} else if (c == '[') { /* section header, new section begins */
1249 1250 1251 1252 1253 1254 1255
			/*
			 * We set both positions to the current one in case we
			 * need to add a variable to the end of a section. In that
			 * case, we want both variables to point just before the
			 * new section. If we actually want to replace it, the
			 * default case will take care of updating them.
			 */
1256
			pre_end = post_start = reader->read_ptr;
1257 1258

			git__free(current_section);
1259
			current_section = NULL;
1260
			if (parse_section_header(reader, &current_section) < 0)
1261
				goto rewrite_fail;
1262 1263 1264

			/* Keep track of when it stops matching */
			last_section_matched = section_matches;
1265
			section_matches = !strcmp(current_section, section);
1266
		}
1267

1268
		else if (c == ';' || c == '#') {
1269
			reader_consume_line(reader);
1270
		}
1271

1272
		else {
1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284
			/*
			 * If the section doesn't match, but the last section did,
			 * it means we need to add a variable (so skip the line
			 * otherwise). If both the section and name match, we need
			 * to overwrite the variable (so skip the line
			 * otherwise). pre_end needs to be updated each time so we
			 * don't loose that information, but we only need to
			 * update post_start if we're going to use it in this
			 * iteration.
			 */
			if (!section_matches) {
				if (!last_section_matched) {
1285
					reader_consume_line(reader);
1286
					continue;
1287 1288
				}
			} else {
1289 1290
				int has_matched = 0;
				char *var_name, *var_value;
1291

1292 1293
				pre_end = reader->read_ptr;
				if (parse_variable(reader, &var_name, &var_value) < 0)
1294
					goto rewrite_fail;
1295

1296 1297 1298
				/* First try to match the name of the variable */
				if (strcasecmp(name, var_name) == 0)
					has_matched = 1;
1299

1300 1301 1302 1303
				/* If the name matches, and we have a regex to match the
				 * value, try to match it */
				if (has_matched && preg != NULL)
					has_matched = (regexec(preg, var_value, 0, NULL, 0) == 0);
1304

1305 1306
				git__free(var_name);
				git__free(var_value);
1307

1308 1309 1310
				/* if there is no match, keep going */
				if (!has_matched)
					continue;
1311

1312
				post_start = reader->read_ptr;
1313 1314
			}

1315 1316
			/* We've found the variable we wanted to change, so
			 * write anything up to it */
1317
			git_filebuf_write(&file, write_start, pre_end - write_start);
1318
			preg_replaced = 1;
1319

1320 1321 1322
			/* Then replace the variable. If the value is NULL, it
			 * means we want to delete it, so don't write anything. */
			if (value != NULL) {
Vicent Marti committed
1323 1324
				const char *q = quotes_for_value(value);
				git_filebuf_printf(&file, "\t%s = %s%s%s\n", name, q, value, q);
1325 1326
			}

1327 1328 1329 1330 1331 1332 1333
			/*
			 * If we have a multivar, we should keep looking for entries,
			 * but only if we're in the right section. Otherwise we'll end up
			 * looping on the edge of a matching and a non-matching section.
			 */
			if (section_matches && preg != NULL) {
				write_start = post_start;
1334 1335 1336
				continue;
			}

1337 1338
			write_trailer = 1;
			break; /* break from the loop */
1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350
		}
	}

	/*
	 * Being here can mean that
	 *
	 * 1) our section is the last one in the file and we're
	 * adding a variable
	 *
	 * 2) we didn't find a section for us so we need to create it
	 * ourselves.
	 *
1351 1352 1353 1354 1355 1356
	 * 3) we're setting a multivar with a regex, which means we
	 * continue to search for matching values
	 *
	 * In the last case, if we've already replaced a value, we
	 * want to write the rest of the file. Otherwise we need to write
	 * out the whole file and then the new variable.
1357
	 */
1358 1359
	if (write_trailer) {
		/* Write out rest of the file */
1360
		git_filebuf_write(&file, post_start, reader->buffer.size - (post_start - data_start));
1361 1362
	} else {
		if (preg_replaced) {
1363
			git_filebuf_printf(&file, "\n%s", write_start);
1364
		} else {
Vicent Marti committed
1365 1366
			const char *q;

1367
			git_filebuf_write(&file, reader->buffer.ptr, reader->buffer.size);
1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380

			/* And now if we just need to add a variable */
			if (!section_matches && write_section(&file, section) < 0)
				goto rewrite_fail;

			/* Sanity check: if we are here, and value is NULL, that means that somebody
			 * touched the config file after our intial read. We should probably assert()
			 * this, but instead we'll handle it gracefully with an error. */
			if (value == NULL) {
				giterr_set(GITERR_CONFIG,
					"Race condition when writing a config file (a cvar has been removed)");
				goto rewrite_fail;
			}
1381

1382
			/* If we are here, there is at least a section line */
1383
			if (reader->buffer.size > 0 && *(reader->buffer.ptr + reader->buffer.size - 1) != '\n')
1384 1385
				git_filebuf_write(&file, "\n", 1);

Vicent Marti committed
1386 1387
			q = quotes_for_value(value);
			git_filebuf_printf(&file, "\t%s = %s%s%s\n", name, q, value, q);
1388
		}
1389 1390
	}

1391 1392
	git__free(section);
	git__free(current_section);
1393

1394
	/* refresh stats - if this errors, then commit will error too */
1395
	(void)git_filebuf_stats(&reader->file_mtime, &reader->file_size, &file);
1396

1397
	result = git_filebuf_commit(&file);
1398
	git_buf_free(&reader->buffer);
1399

1400
	return result;
1401

1402
rewrite_fail:
1403
	git__free(section);
1404
	git__free(current_section);
1405

1406
	git_filebuf_cleanup(&file);
1407
	git_buf_free(&reader->buffer);
1408
	return -1;
1409 1410
}

1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423
static const char *escapes = "ntb\"\\";
static const char *escaped = "\n\t\b\"\\";

/* Escape the values to write them to the file */
static char *escape_value(const char *ptr)
{
	git_buf buf = GIT_BUF_INIT;
	size_t len;
	const char *esc;

	assert(ptr);

	len = strlen(ptr);
1424 1425 1426
	if (!len)
		return git__calloc(1, sizeof(char));

1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446
	git_buf_grow(&buf, len);

	while (*ptr != '\0') {
		if ((esc = strchr(escaped, *ptr)) != NULL) {
			git_buf_putc(&buf, '\\');
			git_buf_putc(&buf, escapes[esc - escaped]);
		} else {
			git_buf_putc(&buf, *ptr);
		}
		ptr++;
	}

	if (git_buf_oom(&buf)) {
		git_buf_free(&buf);
		return NULL;
	}

	return git_buf_detach(&buf);
}

1447 1448
/* '\"' -> '"' etc */
static char *fixup_line(const char *ptr, int quote_count)
1449
{
1450 1451 1452 1453 1454
	char *str = git__malloc(strlen(ptr) + 1);
	char *out = str, *esc;

	if (str == NULL)
		return NULL;
1455

1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484
	while (*ptr != '\0') {
		if (*ptr == '"') {
			quote_count++;
		} else if (*ptr != '\\') {
			*out++ = *ptr;
		} else {
			/* backslash, check the next char */
			ptr++;
			/* if we're at the end, it's a multiline, so keep the backslash */
			if (*ptr == '\0') {
				*out++ = '\\';
				goto out;
			}
			if ((esc = strchr(escapes, *ptr)) != NULL) {
				*out++ = escaped[esc - escapes];
			} else {
				git__free(str);
				giterr_set(GITERR_CONFIG, "Invalid escape at %s", ptr);
				return NULL;
			}
		}
		ptr++;
	}

out:
	*out = '\0';

	return str;
}
1485 1486 1487

static int is_multiline_var(const char *str)
{
1488
	int count = 0;
1489
	const char *end = str + strlen(str);
1490 1491 1492 1493 1494 1495 1496
	while (end > str && end[-1] == '\\') {
		count++;
		end--;
	}

	/* An odd number means last backslash wasn't escaped, so it's multiline */
	return (end > str) && (count & 1);
1497 1498
}

1499
static int parse_multiline_variable(struct reader *reader, git_buf *value, int in_quotes)
1500
{
1501 1502
	char *line = NULL, *proc_line = NULL;
	int quote_count;
1503 1504

	/* Check that the next line exists */
1505
	line = reader_readline(reader, false);
1506
	if (line == NULL)
1507
		return -1;
1508 1509 1510

	/* We've reached the end of the file, there is input missing */
	if (line[0] == '\0') {
1511
		set_parse_error(reader, 0, "Unexpected end of file while parsing multine var");
1512 1513
		git__free(line);
		return -1;
1514 1515
	}

1516
	quote_count = strip_comments(line, !!in_quotes);
1517 1518 1519

	/* If it was just a comment, pretend it didn't exist */
	if (line[0] == '\0') {
1520
		git__free(line);
1521
		return parse_multiline_variable(reader, value, quote_count);
1522
		/* TODO: unbounded recursion. This **could** be exploitable */
1523 1524
	}

1525 1526 1527 1528
	/* Drop the continuation character '\': to closely follow the UNIX
	 * standard, this character **has** to be last one in the buf, with
	 * no whitespace after it */
	assert(is_multiline_var(value->ptr));
1529
	git_buf_shorten(value, 1);
1530

1531 1532 1533 1534
	proc_line = fixup_line(line, in_quotes);
	if (proc_line == NULL) {
		git__free(line);
		return -1;
1535
	}
1536
	/* add this line to the multiline var */
1537
	git_buf_puts(value, proc_line);
1538
	git__free(line);
1539
	git__free(proc_line);
1540 1541

	/*
1542 1543
	 * If we need to continue reading the next line, let's just
	 * keep putting stuff in the buffer
1544
	 */
1545
	if (is_multiline_var(value->ptr))
1546
		return parse_multiline_variable(reader, value, quote_count);
1547

1548
	return 0;
1549 1550
}

1551
static int parse_variable(struct reader *reader, char **var_name, char **var_value)
1552 1553 1554 1555
{
	const char *var_end = NULL;
	const char *value_start = NULL;
	char *line;
1556
	int quote_count;
1557

1558
	line = reader_readline(reader, true);
1559
	if (line == NULL)
1560
		return -1;
1561

1562
	quote_count = strip_comments(line, 0);
1563 1564 1565 1566 1567 1568 1569 1570

	var_end = strchr(line, '=');

	if (var_end == NULL)
		var_end = strchr(line, '\0');
	else
		value_start = var_end + 1;

Russell Belfer committed
1571
	do var_end--;
1572
	while (var_end>line && git__isspace(*var_end));
1573

1574 1575
	*var_name = git__strndup(line, var_end - line + 1);
	GITERR_CHECK_ALLOC(*var_name);
1576

1577 1578
	/* If there is no value, boolean true is assumed */
	*var_value = NULL;
1579 1580 1581 1582 1583

	/*
	 * Now, let's try to parse the value
	 */
	if (value_start != NULL) {
1584
		while (git__isspace(value_start[0]))
1585 1586 1587
			value_start++;

		if (is_multiline_var(value_start)) {
1588
			git_buf multi_value = GIT_BUF_INIT;
1589 1590 1591
			char *proc_line = fixup_line(value_start, 0);
			GITERR_CHECK_ALLOC(proc_line);
			git_buf_puts(&multi_value, proc_line);
1592
			git__free(proc_line);
1593
			if (parse_multiline_variable(reader, &multi_value, quote_count) < 0 || git_buf_oom(&multi_value)) {
1594
				git__free(*var_name);
1595 1596 1597
				git__free(line);
				git_buf_free(&multi_value);
				return -1;
1598
			}
1599

1600
			*var_value = git_buf_detach(&multi_value);
1601

1602 1603
		}
		else if (value_start[0] != '\0') {
1604
			*var_value = fixup_line(value_start, 0);
1605
			GITERR_CHECK_ALLOC(*var_value);
1606 1607 1608
		} else { /* equals sign but missing rhs */
			*var_value = git__strdup("");
			GITERR_CHECK_ALLOC(*var_value);
1609 1610 1611
		}
	}

1612
	git__free(line);
1613
	return 0;
1614
}