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