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