Coverage Report

Created: 2026-02-14 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gnutls/lib/inih/ini.c
Line
Count
Source
1
/* inih -- simple .INI file parser
2
3
inih is released under the New BSD license (see LICENSE.txt). Go to the project
4
home page for more info:
5
6
https://github.com/benhoyt/inih
7
8
*/
9
10
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
11
#define _CRT_SECURE_NO_WARNINGS
12
#endif
13
14
#include <stdio.h>
15
#include <ctype.h>
16
#include <string.h>
17
18
#include "ini.h"
19
20
#if !INI_USE_STACK
21
#include <stdlib.h>
22
#endif
23
24
#define MAX_SECTION 50
25
#define MAX_NAME 50
26
27
/* Strip whitespace chars off end of given string, in place. Return s. */
28
static char *rstrip(char *s)
29
56
{
30
56
  char *p = s + strlen(s);
31
96
  while (p > s && isspace((unsigned char)(*--p)))
32
40
    *p = '\0';
33
56
  return s;
34
56
}
35
36
/* Return pointer to first non-whitespace char in given string. */
37
static char *lskip(const char *s)
38
40
{
39
56
  while (*s && isspace((unsigned char)(*s)))
40
16
    s++;
41
40
  return (char *)s;
42
40
}
43
44
/* Return pointer to first char (of chars) or inline comment in given string,
45
   or pointer to null at end of string if neither found. Inline comment must
46
   be prefixed by a whitespace character to register as a comment. */
47
static char *find_chars_or_comment(const char *s, const char *chars)
48
36
{
49
36
#if INI_ALLOW_INLINE_COMMENTS
50
36
  int was_space = 0;
51
464
  while (*s && (!chars || !strchr(chars, *s)) &&
52
428
         !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
53
428
    was_space = isspace((unsigned char)(*s));
54
428
    s++;
55
428
  }
56
#else
57
  while (*s && (!chars || !strchr(chars, *s))) {
58
    s++;
59
  }
60
#endif
61
36
  return (char *)s;
62
36
}
63
64
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
65
static char *strncpy0(char *dest, const char *src, size_t size)
66
20
{
67
20
  strncpy(dest, src, size - 1);
68
20
  dest[size - 1] = '\0';
69
20
  return dest;
70
20
}
71
72
/* See documentation in header file. */
73
int ini_parse_file(FILE *file, ini_handler handler, void *user)
74
4
{
75
  /* Uses a fair bit of stack (use heap instead if you need to) */
76
4
#if INI_USE_STACK
77
4
  char line[INI_MAX_LINE];
78
4
  int max_line = INI_MAX_LINE;
79
#else
80
  char *line;
81
  int max_line = INI_INITIAL_ALLOC;
82
#endif
83
#if INI_ALLOW_REALLOC && !INI_USE_STACK
84
  char *new_line;
85
  int offset;
86
#endif
87
4
  char section[MAX_SECTION] = "";
88
4
  char prev_name[MAX_NAME] = "";
89
90
4
  char *end;
91
4
  char *name;
92
4
  char *value;
93
4
  int lineno = 0;
94
4
  int error = 0;
95
96
#if !INI_USE_STACK
97
  line = (char *)malloc(INI_INITIAL_ALLOC);
98
  if (!line) {
99
    return -2;
100
  }
101
#endif
102
103
#if INI_HANDLER_LINENO
104
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
105
#else
106
32
#define HANDLER(u, s, n, v) handler(u, s, n, v)
107
4
#endif
108
109
  /* Scan through stream line by line */
110
28
  while (fgets(line, max_line, file) != NULL) {
111
24
    char *start;
112
#if INI_ALLOW_REALLOC && !INI_USE_STACK
113
    offset = strlen(line);
114
    while (offset == max_line - 1 && line[offset - 1] != '\n') {
115
      max_line *= 2;
116
      if (max_line > INI_MAX_LINE)
117
        max_line = INI_MAX_LINE;
118
      new_line = realloc(line, max_line);
119
      if (!new_line) {
120
        free(line);
121
        return -2;
122
      }
123
      line = new_line;
124
      if (fgets(line + offset, max_line - offset, file) ==
125
          NULL)
126
        break;
127
      if (max_line >= INI_MAX_LINE)
128
        break;
129
      offset += strlen(line + offset);
130
    }
131
#endif
132
133
24
    lineno++;
134
135
24
    start = line;
136
24
#if INI_ALLOW_BOM
137
24
    if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
138
0
        (unsigned char)start[1] == 0xBB &&
139
0
        (unsigned char)start[2] == 0xBF) {
140
0
      start += 3;
141
0
    }
142
24
#endif
143
24
    start = lskip(rstrip(start));
144
145
24
    if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
146
      /* Start-of-line comment */
147
4
    }
148
20
#if INI_ALLOW_MULTILINE
149
20
    else if (*prev_name && *start && start > line) {
150
      /* Non-blank line with leading whitespace, treat as continuation
151
         of previous name's value (as per Python configparser). */
152
0
      if (!HANDLER(user, section, prev_name, start) && !error)
153
0
        error = lineno;
154
0
    }
155
20
#endif
156
20
    else if (*start == '[') {
157
      /* A "[section]" line */
158
4
      end = find_chars_or_comment(start + 1, "]");
159
4
      if (*end == ']') {
160
4
        *end = '\0';
161
4
        strncpy0(section, start + 1, sizeof(section));
162
4
        *prev_name = '\0';
163
4
      } else if (!error) {
164
        /* No ']' found on section line */
165
0
        error = lineno;
166
0
      }
167
16
    } else if (*start) {
168
      /* Not a comment, must be a name[=:]value pair */
169
16
      end = find_chars_or_comment(start, "=:");
170
16
      if (*end == '=' || *end == ':') {
171
16
        *end = '\0';
172
16
        name = rstrip(start);
173
16
        value = end + 1;
174
16
#if INI_ALLOW_INLINE_COMMENTS
175
16
        end = find_chars_or_comment(value, NULL);
176
16
        if (*end)
177
0
          *end = '\0';
178
16
#endif
179
16
        value = lskip(value);
180
16
        rstrip(value);
181
182
        /* Valid name[=:]value pair found, call handler */
183
16
        strncpy0(prev_name, name, sizeof(prev_name));
184
16
        if (!HANDLER(user, section, name, value) &&
185
0
            !error)
186
0
          error = lineno;
187
16
      } else if (!error) {
188
        /* No '=' or ':' found on name[=:]value line */
189
0
        error = lineno;
190
0
      }
191
16
    }
192
24
#if INI_STOP_ON_FIRST_ERROR
193
24
    if (error)
194
0
      break;
195
24
#endif
196
24
  }
197
198
#if !INI_USE_STACK
199
  free(line);
200
#endif
201
202
4
  return error;
203
4
}