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
 */

#include "common.h"
#include "config.h"
10
#include "filebuf.h"
11
#include "sysdir.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 184
	if ((res = git_strmap_alloc(&b->values)) < 0)
		return res;
185

186 187
	git_array_init(b->readers);
	reader = git_array_alloc(b->readers);
188 189 190 191
	if (!reader) {
		git_strmap_free(b->values);
		return -1;
	}
192 193 194 195 196 197
	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);
198
	res = git_futils_readbuffer_updated(
199
		&reader->buffer, b->file_path, &reader->file_mtime, &reader->file_size, NULL);
200

201
	/* It's fine if the file doesn't exist */
202 203 204
	if (res == GIT_ENOTFOUND)
		return 0;

205
	if (res < 0 || (res = config_parse(b, reader, level, 0)) < 0) {
206 207 208
		free_vars(b->values);
		b->values = NULL;
	}
209

210
	reader = git_array_get(b->readers, 0);
211
	git_buf_free(&reader->buffer);
212

213 214 215
	return res;
}

Ben Straub committed
216
static int config_refresh(git_config_backend *cfg)
217
{
218
	int res = 0, updated = 0, any_updated = 0;
219 220
	diskfile_backend *b = (diskfile_backend *)cfg;
	git_strmap *old_values;
221
	struct reader *reader = NULL;
222
	uint32_t i;
223

224 225
	for (i = 0; i < git_array_size(b->readers); i++) {
		reader = git_array_get(b->readers, i);
226

227
		res = git_futils_readbuffer_updated(
228 229
			&reader->buffer, reader->file_path,
			&reader->file_mtime, &reader->file_size, &updated);
230 231 232 233 234 235 236 237 238

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

		if (updated)
			any_updated = 1;
	}

	if (!any_updated)
239 240 241 242
		return (res == GIT_ENOTFOUND) ? 0 : res;

	/* need to reload - store old values and prep for reload */
	old_values = b->values;
243 244 245
	if ((res = git_strmap_alloc(&b->values)) < 0) {
		b->values = old_values;
	} else if ((res = config_parse(b, reader, b->level, 0)) < 0) {
246 247 248 249 250 251
		free_vars(b->values);
		b->values = old_values;
	} else {
		free_vars(old_values);
	}

252
	git_buf_free(&reader->buffer);
253
	return res;
254 255
}

Ben Straub committed
256
static void backend_free(git_config_backend *_backend)
257
{
258
	diskfile_backend *backend = (diskfile_backend *)_backend;
259
	uint32_t i;
260 261 262 263

	if (backend == NULL)
		return;

264 265 266 267 268 269
	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);

270
	git__free(backend->file_path);
271
	free_vars(backend->values);
272
	git__free(backend);
273 274
}

275
static void config_iterator_free(
276
	git_config_iterator* iter)
277
{
278 279
	git__free(iter);
}
280

281
static int config_iterator_next(
282
	git_config_entry **entry,
283
	git_config_iterator *iter)
284
{
285 286 287
	git_config_file_iter *it = (git_config_file_iter *) iter;
	diskfile_backend *b = (diskfile_backend *) it->parent.backend;
	int err = 0;
288
	cvar_t * var;
289

290
	if (it->next_var == NULL) {
291
		err = git_strmap_next((void**) &var, &(it->iter), b->values);
292
	} else {
293
		var = it->next_var;
294
	}
295

296
	if (err < 0) {
297
		it->next_var = NULL;
298
		return err;
299
	}
300

301
	*entry = var->entry;
302
	it->next_var = CVAR_LIST_NEXT(var);
303

304 305
	return 0;
}
306

307 308 309 310 311 312
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));
313

314 315
	GIT_UNUSED(b);

316 317 318 319 320 321 322 323 324
	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;
325 326

	return 0;
327 328
}

Ben Straub committed
329
static int config_set(git_config_backend *cfg, const char *name, const char *value)
330
{
331
	cvar_t *var = NULL, *old_var = NULL;
332
	diskfile_backend *b = (diskfile_backend *)cfg;
333
	char *key, *esc_value = NULL;
334
	khiter_t pos;
335
	int rval, ret;
336

337
	if ((rval = git_config__normalize_name(name, &key)) < 0)
338
		return rval;
339 340

	/*
341 342
	 * Try to find it in the existing values and update it if it
	 * only has one value.
343
	 */
344 345 346
	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);
347
		char *tmp = NULL;
348 349

		git__free(key);
Russell Belfer committed
350

351 352 353 354
		if (existing->next != NULL) {
			giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set");
			return -1;
		}
355

Russell Belfer committed
356
		/* don't update if old and new values already match */
357 358
		if ((!existing->entry->value && !value) ||
			(existing->entry->value && value && !strcmp(existing->entry->value, value)))
Russell Belfer committed
359 360
			return 0;

361 362 363
		if (value) {
			tmp = git__strdup(value);
			GITERR_CHECK_ALLOC(tmp);
364 365
			esc_value = escape_value(value);
			GITERR_CHECK_ALLOC(esc_value);
366
		}
367

368 369
		git__free((void *)existing->entry->value);
		existing->entry->value = tmp;
370

371
		ret = config_write(b, existing->entry->name, NULL, esc_value);
372 373 374

		git__free(esc_value);
		return ret;
375 376
	}

377
	var = git__malloc(sizeof(cvar_t));
378
	GITERR_CHECK_ALLOC(var);
379
	memset(var, 0x0, sizeof(cvar_t));
380 381 382
	var->entry = git__malloc(sizeof(git_config_entry));
	GITERR_CHECK_ALLOC(var->entry);
	memset(var->entry, 0x0, sizeof(git_config_entry));
383

384 385
	var->entry->name = key;
	var->entry->value = NULL;
386

387
	if (value) {
388 389
		var->entry->value = git__strdup(value);
		GITERR_CHECK_ALLOC(var->entry->value);
390 391
		esc_value = escape_value(value);
		GITERR_CHECK_ALLOC(esc_value);
392 393
	}

394
	if ((ret = config_write(b, key, NULL, esc_value)) < 0) {
395
		git__free(esc_value);
396
		cvar_free(var);
397
		return ret;
398 399
	}

400
	git__free(esc_value);
401
	git_strmap_insert2(b->values, key, var, old_var, rval);
402
	if (rval < 0)
403
		return -1;
404 405
	if (old_var != NULL)
		cvar_free(old_var);
406

407
	return 0;
408 409 410 411 412
}

/*
 * Internal function that actually gets the value in string form
 */
413
static int config_get(const git_config_backend *cfg, const char *key, const git_config_entry **out)
414
{
415
	diskfile_backend *b = (diskfile_backend *)cfg;
416
	khiter_t pos = git_strmap_lookup_index(b->values, key);
417
	cvar_t *var;
418

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

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

	*out = var->entry;
428
	return 0;
429 430
}

431
static int config_set_multivar(
Ben Straub committed
432
	git_config_backend *cfg, const char *name, const char *regexp, const char *value)
433
{
434
	int replaced = 0;
435
	cvar_t *var, *newvar;
436 437 438
	diskfile_backend *b = (diskfile_backend *)cfg;
	char *key;
	regex_t preg;
439
	int result;
440
	khiter_t pos;
441

442
	assert(regexp);
443

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

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

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

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

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

470 471
			git__free((void *)var->entry->value);
			var->entry->value = tmp;
472 473 474
			replaced = 1;
		}

475
		if (var->next == NULL)
476
			break;
477 478 479

		var = var->next;
	}
480 481 482 483

	/* 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));
484
		GITERR_CHECK_ALLOC(newvar);
485
		memset(newvar, 0x0, sizeof(cvar_t));
486 487 488 489 490 491
		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);
492

493 494
		newvar->entry->value = git__strdup(value);
		GITERR_CHECK_ALLOC(newvar->entry->value);
495

496
		newvar->entry->level = var->entry->level;
497 498 499 500

		var->next = newvar;
	}

501
	result = config_write(b, key, &preg, value);
502

503
	git__free(key);
504
	regfree(&preg);
505 506

	return result;
507 508
}

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

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

520
	pos = git_strmap_lookup_index(b->values, key);
521
	git__free(key);
522

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

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

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

535
	git_strmap_delete_at(b->values, pos);
536

537
	result = config_write(b, var->entry->name, NULL, NULL);
538

539 540
	cvar_free(var);
	return result;
541 542
}

543 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
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
579 580
	while (var != NULL) {
		cvar_t *next = var->next;
581 582 583 584 585

		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
586
				prev->next = next;
587
			} else {
Vicent Marti committed
588
				new_head = next;
589 590 591 592 593 594 595
			}

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

Vicent Marti committed
596
		var = next;
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611
	}

	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);
612 613
	git__free(to_delete);
	regfree(&preg);
614 615 616
	return result;
}

Ben Straub committed
617
int git_config_file__ondisk(git_config_backend **out, const char *path)
618
{
619
	diskfile_backend *backend;
620

621
	backend = git__calloc(1, sizeof(diskfile_backend));
622
	GITERR_CHECK_ALLOC(backend);
623

624
	backend->parent.version = GIT_CONFIG_BACKEND_VERSION;
625 626

	backend->file_path = git__strdup(path);
627
	GITERR_CHECK_ALLOC(backend->file_path);
628 629 630 631

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

Ben Straub committed
639
	*out = (git_config_backend *)backend;
640

641
	return 0;
642 643
}

644
static int reader_getchar_raw(struct reader *reader)
645 646 647
{
	int c;

648
	c = *reader->read_ptr++;
649 650 651 652 653

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

	if (c == '\n')
660
		reader->line_number++;
661 662

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

	return c;
}

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

673
static int reader_getchar(struct reader *reader, int flags)
674 675 676 677 678
{
	const int skip_whitespace = (flags & SKIP_WHITESPACE);
	const int skip_comments = (flags & SKIP_COMMENTS);
	int c;

679
	assert(reader->read_ptr);
680

681 682 683 684
	do {
		c = reader_getchar_raw(reader);
	} while (skip_whitespace && git__isspace(c) &&
	       !reader->eof);
685 686

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

	return c;
}

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

704
	assert(reader->read_ptr);
705

706 707 708
	old_read_ptr = reader->read_ptr;
	old_lineno = reader->line_number;
	old_eof = reader->eof;
709

710
	ret = reader_getchar(reader, flags);
711

712 713 714
	reader->read_ptr = old_read_ptr;
	reader->line_number = old_lineno;
	reader->eof = old_eof;
715 716 717 718 719 720 721

	return ret;
}

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

728
	line_src = reader->read_ptr;
729

730 731
	if (skip_whitespace) {
		/* Skip empty empty lines */
732
		while (git__isspace(*line_src))
733 734
			++line_src;
	}
735

736
	line_end = strchr(line_src, '\n');
737

738
	/* no newline at EOF */
739 740 741
	if (line_end == NULL)
		line_end = strchr(line_src, 0);

742
	line_len = line_end - line_src;
743

744
	line = git__malloc(line_len + 1);
745 746 747
	if (line == NULL)
		return NULL;

748
	memcpy(line, line_src, line_len);
749

750
	do line[line_len] = '\0';
751
	while (line_len-- > 0 && git__isspace(line[line_len]));
752 753 754 755 756

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

	if (*line_end == '\0')
757
		reader->eof = 1;
758

759 760
	reader->line_number++;
	reader->read_ptr = line_end;
761 762 763 764 765 766 767

	return line;
}

/*
 * Consume a line, without storing it anywhere
 */
768
static void reader_consume_line(struct reader *reader)
769 770 771
{
	char *line_start, *line_end;

772
	line_start = reader->read_ptr;
773 774 775 776 777 778 779 780 781 782
	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')
783
		reader->eof = 1;
784

785 786
	reader->line_number++;
	reader->read_ptr = line_end;
787 788
}

789
GIT_INLINE(int) config_keychar(int c)
790 791 792 793
{
	return isalnum(c) || c == '-';
}

794
static int parse_section_header_ext(struct reader *reader, const char *line, const char *base_name, char **section_name)
795
{
796 797 798
	int c, rpos;
	char *first_quote, *last_quote;
	git_buf buf = GIT_BUF_INIT;
799 800 801 802 803 804 805 806 807
	/*
	 * 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, '"');

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

813 814
	git_buf_grow(&buf, strlen(base_name) + last_quote - first_quote + 2);
	git_buf_printf(&buf, "%s.", base_name);
815 816 817 818

	rpos = 0;

	line = first_quote;
819
	c = line[++rpos];
820 821 822 823 824 825

	/*
	 * 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 {
826

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

833
		case '"':
834
			goto end_parse;
835

836
		case '\\':
837
			c = line[++rpos];
838

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

845 846 847 848
		default:
			break;
		}

Linquize committed
849
		git_buf_putc(&buf, (char)c);
850 851 852 853 854 855 856 857 858
		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;
	}
859

860 861
	*section_name = git_buf_detach(&buf);
	return 0;
862 863
}

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

871
	line = reader_readline(reader, true);
872
	if (line == NULL)
873
		return -1;
874 875

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

	name = (char *)git__malloc((size_t)(name_end - line) + 1);
884
	GITERR_CHECK_ALLOC(name);
885 886 887 888 889 890

	name_length = 0;
	pos = 0;

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

	c = line[pos++];

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

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

909
		name[name_length++] = (char) tolower(c);
910 911 912

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

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

918
	git__free(line);
919 920

	name[name_length] = 0;
921 922
	*section_out = name;

923 924 925
	return 0;

fail_parse:
926 927
	git__free(line);
	git__free(name);
928
	return -1;
929 930
}

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

937
	if (bom == GIT_BOM_UTF8)
938
		reader->read_ptr += bom_offset;
939

940
	/* TODO: reference implementation is pretty stupid with BoM */
941

942
	return 0;
943 944 945 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
}

/*
	(* 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"
*/

984
static int strip_comments(char *line, int in_quotes)
985
{
986
	int quote_count = in_quotes;
987 988 989 990 991 992 993 994 995 996 997 998
	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;
		}
	}

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

	return quote_count;
1006 1007
}

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

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

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

1029 1030 1031 1032
	if (depth >= MAX_INCLUDE_DEPTH) {
		giterr_set(GITERR_CONFIG, "Maximum config include depth reached");
		return -1;
	}
1033

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

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

1043
	skip_bom(reader);
1044

1045
	while (result == 0 && !reader->eof) {
1046

1047
		c = reader_peek(reader, SKIP_WHITESPACE);
1048 1049

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

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

		case ';':
		case '#':
1062
			reader_consume_line(reader);
1063 1064 1065
			break;

		default: /* assume variable declaration */
1066
			result = parse_variable(reader, &var_name, &var_value);
1067
			if (result < 0)
1068 1069
				break;

1070
			var = git__malloc(sizeof(cvar_t));
1071
			GITERR_CHECK_ALLOC(var);
1072
			memset(var, 0x0, sizeof(cvar_t));
1073 1074 1075
			var->entry = git__malloc(sizeof(git_config_entry));
			GITERR_CHECK_ALLOC(var->entry);
			memset(var->entry, 0x0, sizeof(git_config_entry));
1076

1077 1078 1079 1080
			git__strtolower(var_name);
			git_buf_printf(&buf, "%s.%s", current_section, var_name);
			git__free(var_name);

1081 1082
			if (git_buf_oom(&buf)) {
				git__free(var_value);
1083
				return -1;
1084
			}
1085

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

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

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

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

1119 1120
				/* 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;
1121 1122 1123 1124 1125 1126 1127
				dir = git_buf_detach(&path);
				result = included_path(&path, dir, var->entry->value);
				git__free(dir);

				if (result < 0)
					break;

1128 1129 1130 1131
				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)
1132 1133
					break;

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

				if (result < 0)
					break;
			}

1142 1143 1144 1145
			break;
		}
	}

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

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

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

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

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

1177
	return result;
1178 1179
}

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

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

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

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

	return "";
1196 1197
}

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

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

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

1227 1228
	write_start = data_start;

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

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

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

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

1247
		} else if (c == '[') { /* section header, new section begins */
1248 1249 1250 1251 1252 1253 1254
			/*
			 * 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.
			 */
1255
			pre_end = post_start = reader->read_ptr;
1256 1257

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

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

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

1271
		else {
1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283
			/*
			 * 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) {
1284
					reader_consume_line(reader);
1285
					continue;
1286 1287
				}
			} else {
1288 1289
				int has_matched = 0;
				char *var_name, *var_value;
1290

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

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

1299 1300 1301 1302
				/* 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);
1303

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

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

1311
				post_start = reader->read_ptr;
1312 1313
			}

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

1319 1320 1321
			/* 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
1322 1323
				const char *q = quotes_for_value(value);
				git_filebuf_printf(&file, "\t%s = %s%s%s\n", name, q, value, q);
1324 1325
			}

1326 1327 1328 1329 1330 1331 1332
			/*
			 * 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;
1333 1334 1335
				continue;
			}

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

	/*
	 * 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.
	 *
1350 1351 1352 1353 1354 1355
	 * 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.
1356
	 */
1357 1358
	if (write_trailer) {
		/* Write out rest of the file */
1359
		git_filebuf_write(&file, post_start, reader->buffer.size - (post_start - data_start));
1360 1361
	} else {
		if (preg_replaced) {
1362
			git_filebuf_printf(&file, "\n%s", write_start);
1363
		} else {
Vicent Marti committed
1364 1365
			const char *q;

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

			/* 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;
			}
1380

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

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

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

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

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

1399
	return result;
1400

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

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

1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422
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);
1423 1424 1425
	if (!len)
		return git__calloc(1, sizeof(char));

1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445
	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);
}

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

	if (str == NULL)
		return NULL;
1454

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
	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;
}
1484 1485 1486

static int is_multiline_var(const char *str)
{
1487
	int count = 0;
1488
	const char *end = str + strlen(str);
1489 1490 1491 1492 1493 1494 1495
	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);
1496 1497
}

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

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

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

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

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

1524 1525 1526 1527
	/* 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));
1528
	git_buf_shorten(value, 1);
1529

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

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

1547
	return 0;
1548 1549
}

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

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

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

	var_end = strchr(line, '=');

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

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

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

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

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

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

1599
			*var_value = git_buf_detach(&multi_value);
1600

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

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