Coverage Report

Created: 2026-05-30 07:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gpac/src/utils/configfile.c
Line
Count
Source
1
/*
2
 *      GPAC - Multimedia Framework C SDK
3
 *
4
 *      Authors: Jean Le Feuvre
5
 *      Copyright (c) Telecom ParisTech 2000-2023
6
 *          All rights reserved
7
 *
8
 *  This file is part of GPAC / common tools sub-project
9
 *
10
 *  GPAC is free software; you can redistribute it and/or modify
11
 *  it under the terms of the GNU Lesser General Public License as published by
12
 *  the Free Software Foundation; either version 2, or (at your option)
13
 *  any later version.
14
 *
15
 *  GPAC is distributed in the hope that it will be useful,
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 *  GNU Lesser General Public License for more details.
19
 *
20
 *  You should have received a copy of the GNU Lesser General Public
21
 *  License along with this library; see the file COPYING.  If not, write to
22
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23
 *
24
 */
25
26
#include <gpac/config_file.h>
27
#include <gpac/list.h>
28
29
0
#define MAX_INI_LINE      2046
30
31
typedef struct
32
{
33
  Bool do_restrict;
34
  char *name;
35
  char *value;
36
} IniKey;
37
38
typedef struct
39
{
40
  char *section_name;
41
  GF_List *keys;
42
} IniSection;
43
44
struct __tag_config
45
{
46
  char *fileName;
47
  GF_List *sections;
48
  Bool hasChanged, skip_changes;
49
};
50
51
52
static void DelSection(IniSection *ptr)
53
0
{
54
0
  if (!ptr) return;
55
0
  if (ptr->keys) {
56
0
    u32 i, count=gf_list_count(ptr->keys);
57
0
    for (i=0;i<count;i++) {
58
0
      IniKey *k = (IniKey *) gf_list_get(ptr->keys, i);
59
0
      if (k->value) gf_free(k->value);
60
0
      if (k->name) gf_free(k->name);
61
0
      gf_free(k);
62
0
    }
63
0
    gf_list_del(ptr->keys);
64
0
  }
65
0
  if (ptr->section_name) gf_free(ptr->section_name);
66
0
  gf_free(ptr);
67
0
}
68
69
/*!
70
 * \brief Clear the structure
71
\param iniFile The structure to clear
72
 */
73
static void gf_cfg_clear(GF_Config * iniFile)
74
0
{
75
0
  if (!iniFile) return;
76
0
  if (iniFile->sections) {
77
0
    u32 i, count=gf_list_count(iniFile->sections);
78
0
    for (i=0; i<count; i++) {
79
0
      IniSection *p = (IniSection *) gf_list_get(iniFile->sections, i);
80
0
      DelSection(p);
81
0
    }
82
0
    gf_list_del(iniFile->sections);
83
0
  }
84
0
  if (iniFile->fileName)
85
0
    gf_free(iniFile->fileName);
86
0
  memset((void *)iniFile, 0, sizeof(GF_Config));
87
0
}
88
89
/*!
90
 * \brief Parses the config file if any and clears the existing structure
91
 */
92
GF_Err gf_cfg_parse_config_file(GF_Config * tmp, const char * filePath, const char * file_name)
93
0
{
94
0
  IniSection *p;
95
0
  IniKey *k=NULL;
96
0
  FILE *file;
97
0
  char *line;
98
0
  u32 line_alloc = MAX_INI_LINE;
99
0
  char fileName[GF_MAX_PATH];
100
0
  Bool line_done = GF_FALSE;
101
0
  u32 nb_empty_lines=0;
102
0
  Bool is_opts_probe = GF_FALSE;
103
0
  Bool in_multiline = GF_FALSE;
104
0
  gf_cfg_clear(tmp);
105
106
0
  if (strstr(file_name, "/all_opts.txt")) {
107
0
    if (filePath && !strcmp(filePath, "probe")) {
108
0
      filePath = NULL;
109
0
      is_opts_probe = GF_TRUE;
110
0
    }
111
0
  }
112
113
0
  size_t out_size = strlen(file_name);
114
0
  if (filePath)
115
0
    out_size += strlen(filePath) + 1;
116
117
0
  if (out_size >= sizeof(fileName)) {
118
0
    return GF_BAD_PARAM;
119
0
  }
120
121
122
0
  if (filePath && strlen(filePath) && ((filePath[strlen(filePath)-1] == '/') || (filePath[strlen(filePath)-1] == '\\')) ) {
123
0
    strcpy(fileName, filePath);
124
0
    strcat(fileName, file_name);
125
0
  } else if (filePath && strlen(filePath)) {
126
0
    sprintf(fileName, "%s%c%s", filePath, GF_PATH_SEPARATOR, file_name);
127
0
  } else {
128
0
    strcpy(fileName, file_name);
129
0
  }
130
131
0
  tmp->fileName = gf_strdup(fileName);
132
0
  tmp->sections = gf_list_new();
133
0
  file = gf_fopen(fileName, "rt");
134
0
  if (!file)
135
0
    return GF_IO_ERR;
136
  /* load the file */
137
0
  p = NULL;
138
0
  line = (char*)gf_malloc(sizeof(char)*line_alloc);
139
0
  memset(line, 0, sizeof(char)*line_alloc);
140
141
0
#define FLUSH_EMPTY_LINES \
142
0
      if (k&& nb_empty_lines) {\
143
0
        u32 klen = (u32) strlen(k->value)+1+nb_empty_lines;\
144
0
        k->value = gf_realloc(k->value, sizeof(char)*klen);\
145
0
        while (nb_empty_lines) {\
146
0
          nb_empty_lines--;\
147
0
          strcat(k->value, "\n");\
148
0
        }\
149
0
        k=NULL;\
150
0
      }\
151
0
      nb_empty_lines=0;\
152
0
153
0
  while (!gf_feof(file)) {
154
0
    char *ret;
155
0
    u32 read, nb_pass;
156
0
    char *fsep;
157
0
    ret = gf_fgets(line, line_alloc, file);
158
0
    read = (u32) strlen(line);
159
160
0
    nb_pass = 1;
161
0
    while (read + nb_pass == line_alloc) {
162
0
      line_alloc += MAX_INI_LINE;
163
0
      line = (char*)gf_realloc(line, sizeof(char)*line_alloc);
164
0
      ret = gf_fgets(line+read, MAX_INI_LINE, file);
165
0
      read = (u32) strlen(line);
166
0
      nb_pass++;
167
0
    }
168
0
    if (!ret) continue;
169
170
    /* get rid of the end of line stuff */
171
0
    fsep = strchr(line, '=');
172
0
    if (!fsep || (fsep[1]!='@')) {
173
0
      while (!in_multiline) {
174
0
        u32 len = (u32) strlen(line);
175
0
        if (!len) break;
176
0
        if ((line[len-1] != '\n') && (line[len-1] != '\r')) break;
177
0
        line[len-1] = 0;
178
0
      }
179
0
    }
180
0
    if (!strlen(line)) {
181
0
      if (k) nb_empty_lines++;
182
0
      continue;
183
0
    }
184
0
    if (line[0] == '#') {
185
0
      FLUSH_EMPTY_LINES
186
0
      continue;
187
0
    }
188
189
    /* new section */
190
0
    if (line[0] == '[') {
191
      //compat with old arch
192
0
      if (nb_empty_lines) nb_empty_lines--;
193
194
0
      FLUSH_EMPTY_LINES
195
196
0
      p = (IniSection *) gf_malloc(sizeof(IniSection));
197
0
      p->keys = gf_list_new();
198
0
      p->section_name = gf_strdup(line + 1);
199
0
      p->section_name[strlen(line) - 2] = 0;
200
0
      while (p->section_name[strlen(p->section_name) - 1] == ']' || p->section_name[strlen(p->section_name) - 1] == ' ') p->section_name[strlen(p->section_name) - 1] = 0;
201
0
      gf_list_add(tmp->sections, p);
202
0
    }
203
0
    else if (strlen(line) && !in_multiline && (strchr(line, '=') != NULL) ) {
204
0
      if (!p) {
205
0
        gf_fclose(file);
206
0
        gf_free(line);
207
0
        gf_cfg_clear(tmp);
208
0
        return GF_IO_ERR;
209
0
      }
210
0
      FLUSH_EMPTY_LINES
211
212
0
      k = (IniKey *) gf_malloc(sizeof(IniKey));
213
0
      memset((void *)k, 0, sizeof(IniKey));
214
0
      ret = strchr(line, '=');
215
0
      if (ret) {
216
0
        ret[0] = 0;
217
0
        k->name = gf_strdup(line);
218
0
        while (k->name[0] && (k->name[strlen(k->name) - 1] == ' '))
219
0
          k->name[strlen(k->name) - 1] = 0;
220
0
        ret[0] = '=';
221
0
        ret += 1;
222
0
        while (ret[0] == ' ') ret++;
223
0
        if (ret[0]=='@') {
224
0
          ret++;
225
0
          in_multiline = GF_TRUE;
226
0
        }
227
0
        if ( ret[0] != 0) {
228
0
          k->value = gf_strdup(ret);
229
0
          while (k->value[strlen(k->value) - 1] == ' ') k->value[strlen(k->value) - 1] = 0;
230
0
        } else {
231
0
          k->value = gf_strdup("");
232
0
        }
233
0
        line_done = GF_FALSE;
234
0
      }
235
0
      gf_list_add(p->keys, k);
236
0
      if (line_done) k=NULL;
237
0
      line_done=GF_FALSE;
238
239
0
      if (is_opts_probe) {
240
0
        if (!strcmp(p->section_name, "version") && !strcmp(k->name, "version")) {
241
0
          break;
242
0
        }
243
0
      }
244
245
0
    } else if (k) {
246
0
      u32 l1 = (u32) strlen(k->value);
247
0
      u32 l2 = (u32) strlen(line);
248
0
      k->value = gf_realloc(k->value, sizeof(char) * (l1 + l2 + 1 + nb_empty_lines) );
249
0
      l2 += l1 + nb_empty_lines;
250
0
      while (nb_empty_lines) {
251
0
        strcat(k->value, "\n");
252
0
        nb_empty_lines--;
253
0
      }
254
0
      strcat(k->value, line);
255
0
      l2 -= 2; //@ and \n
256
0
      if (k->value[l2] == '@') {
257
0
        k->value[l2] = 0;
258
0
        k=NULL;
259
0
        in_multiline = GF_FALSE;
260
0
      }
261
0
    } else {
262
0
      k = NULL;
263
0
    }
264
0
  }
265
0
  gf_free(line);
266
0
  gf_fclose(file);
267
0
  return GF_OK;
268
0
}
269
270
GF_EXPORT
271
0
GF_Config *gf_cfg_force_new(const char *filePath, const char* file_name) {
272
0
  GF_Config *tmp = (GF_Config *)gf_malloc(sizeof(GF_Config));
273
0
  memset((void *)tmp, 0, sizeof(GF_Config));
274
0
  gf_cfg_parse_config_file(tmp, filePath, file_name);
275
0
  return tmp;
276
0
}
277
278
279
GF_EXPORT
280
GF_Config *gf_cfg_new(const char *filePath, const char* file_name)
281
0
{
282
0
  GF_Config *tmp = (GF_Config *)gf_malloc(sizeof(GF_Config));
283
0
  memset((void *)tmp, 0, sizeof(GF_Config));
284
0
  if (!filePath && !file_name) {
285
0
    tmp->sections = gf_list_new();
286
0
    return tmp;
287
0
  }
288
289
0
  if (gf_cfg_parse_config_file(tmp, filePath, file_name)) {
290
0
    gf_cfg_clear(tmp);
291
0
    gf_free(tmp);
292
0
    tmp = NULL;
293
0
  }
294
0
  return tmp;
295
0
}
296
297
GF_EXPORT
298
const char * gf_cfg_get_filename(GF_Config *iniFile)
299
0
{
300
0
  if (!iniFile)
301
0
    return NULL;
302
0
  return iniFile->fileName;
303
0
}
304
305
GF_EXPORT
306
GF_Err gf_cfg_save(GF_Config *iniFile)
307
0
{
308
0
  u32 i, j;
309
0
  IniSection *sec;
310
0
  IniKey *key;
311
0
  FILE *file;
312
313
0
  if (!iniFile->hasChanged) return GF_OK;
314
0
  if (iniFile->skip_changes) return GF_OK;
315
0
  if (!iniFile->fileName) return GF_OK;
316
317
0
  file = gf_fopen(iniFile->fileName, "wt");
318
0
  if (!file) return GF_IO_ERR;
319
320
0
  i=0;
321
0
  while ( (sec = (IniSection *) gf_list_enum(iniFile->sections, &i)) ) {
322
    /*Temporary sections are not saved*/
323
0
    if (!strnicmp(sec->section_name, "temp", 4)) continue;
324
325
0
    gf_fprintf(file, "[%s]\n", sec->section_name);
326
0
    j=0;
327
0
    while ( (key = (IniKey *) gf_list_enum(sec->keys, &j)) ) {
328
0
      if (strchr(key->value, '\n')) {
329
0
        gf_fprintf(file, "%s=@%s@\n", key->name, key->value);
330
0
      } else {
331
0
        gf_fprintf(file, "%s=%s\n", key->name, key->value);
332
0
      }
333
0
    }
334
0
  }
335
0
  gf_fclose(file);
336
0
  return GF_OK;
337
0
}
338
339
GF_EXPORT
340
GF_Err gf_cfg_discard_changes(GF_Config *iniFile)
341
0
{
342
0
  if (!iniFile) return GF_BAD_PARAM;
343
0
  iniFile->skip_changes = GF_TRUE;
344
0
  return GF_OK;
345
0
}
346
347
GF_EXPORT
348
void gf_cfg_del(GF_Config *iniFile)
349
0
{
350
0
  if (!iniFile) return;
351
0
  gf_cfg_save(iniFile);
352
0
  gf_cfg_clear(iniFile);
353
0
  gf_free(iniFile);
354
0
}
355
356
#if 0 //unused
357
void gf_cfg_remove(GF_Config *iniFile)
358
{
359
  if (!iniFile) return;
360
  gf_file_delete(iniFile->fileName);
361
  gf_cfg_clear(iniFile);
362
  gf_free(iniFile);
363
}
364
#endif
365
366
const char *gf_cfg_get_key_internal(GF_Config *iniFile, const char *secName, const char *keyName, Bool restricted_only)
367
0
{
368
0
  u32 i;
369
0
  IniSection *sec;
370
0
  IniKey *key;
371
372
0
  if (!iniFile || !iniFile->sections)
373
0
    return NULL;
374
375
0
  i=0;
376
0
  while ( (sec = (IniSection *) gf_list_enum(iniFile->sections, &i)) ) {
377
0
    if (!strcmp(secName, sec->section_name)) goto get_key;
378
0
  }
379
0
  return NULL;
380
381
0
get_key:
382
0
  i=0;
383
0
  while ( (key = (IniKey *) gf_list_enum(sec->keys, &i)) ) {
384
0
    if (!strcmp(key->name, keyName)) {
385
0
      if (!restricted_only || key->do_restrict)
386
0
        return key->value;
387
0
      return NULL;
388
0
    }
389
0
  }
390
0
  return NULL;
391
0
}
392
GF_EXPORT
393
const char *gf_cfg_get_key(GF_Config *iniFile, const char *secName, const char *keyName)
394
0
{
395
0
  return gf_cfg_get_key_internal(iniFile, secName, keyName, GF_FALSE);
396
0
}
397
398
#if 0 //unused
399
const char *gf_cfg_get_ikey(GF_Config *iniFile, const char *secName, const char *keyName)
400
{
401
  u32 i;
402
  IniSection *sec;
403
  IniKey *key;
404
405
  i=0;
406
  while ( (sec = (IniSection *) gf_list_enum(iniFile->sections, &i)) ) {
407
    if (!stricmp(secName, sec->section_name)) goto get_key;
408
  }
409
  return NULL;
410
411
get_key:
412
  i=0;
413
  while ( (key = (IniKey *) gf_list_enum(sec->keys, &i)) ) {
414
    if (!stricmp(key->name, keyName)) return key->value;
415
  }
416
  return NULL;
417
}
418
#endif
419
420
GF_Err gf_cfg_set_key_internal(GF_Config *iniFile, const char *secName, const char *keyName, const char *keyValue, Bool is_restrict)
421
0
{
422
0
  u32 i;
423
0
  Bool has_changed = GF_TRUE;
424
0
  IniSection *sec;
425
0
  IniKey *key;
426
427
0
  if (!iniFile || !secName || !keyName) return GF_BAD_PARAM;
428
429
0
  if (!strnicmp(secName, "temp", 4)) has_changed = GF_FALSE;
430
431
0
  i=0;
432
0
  while ((sec = (IniSection *) gf_list_enum(iniFile->sections, &i)) ) {
433
0
    if (!strcmp(secName, sec->section_name)) goto get_key;
434
0
  }
435
  /* need a new key */
436
0
  sec = (IniSection *) gf_malloc(sizeof(IniSection));
437
0
  sec->section_name = gf_strdup(secName);
438
0
  sec->keys = gf_list_new();
439
0
  if (has_changed)
440
0
    iniFile->hasChanged = GF_TRUE;
441
0
  gf_list_add(iniFile->sections, sec);
442
443
0
get_key:
444
0
  i=0;
445
0
  while ( (key = (IniKey *) gf_list_enum(sec->keys, &i) ) ) {
446
0
    if (!strcmp(key->name, keyName)) goto set_value;
447
0
  }
448
0
  if (!keyValue) return GF_OK;
449
  /* need a new key */
450
0
  key = (IniKey *) gf_malloc(sizeof(IniKey));
451
0
  key->name = gf_strdup(keyName);
452
0
  key->value = gf_strdup(keyValue);
453
0
  if (has_changed)
454
0
    iniFile->hasChanged = GF_TRUE;
455
0
  gf_list_add(sec->keys, key);
456
0
  key->do_restrict = is_restrict;
457
0
  return GF_OK;
458
459
0
set_value:
460
0
  if (!keyValue) {
461
0
    gf_list_del_item(sec->keys, key);
462
0
    if (key->name) gf_free(key->name);
463
0
    if (key->value) gf_free(key->value);
464
0
    gf_free(key);
465
0
    if (has_changed)
466
0
      iniFile->hasChanged = GF_TRUE;
467
0
    return GF_OK;
468
0
  }
469
0
  key->do_restrict = is_restrict;
470
  /* same value, don't update */
471
0
  if (!strcmp(key->value, keyValue)) return GF_OK;
472
473
0
  if (key->value) gf_free(key->value);
474
0
  key->value = gf_strdup(keyValue);
475
0
  if (has_changed)
476
0
    iniFile->hasChanged = GF_TRUE;
477
0
  return GF_OK;
478
0
}
479
480
GF_EXPORT
481
GF_Err gf_cfg_set_key(GF_Config *iniFile, const char *secName, const char *keyName, const char *keyValue)
482
0
{
483
0
  return gf_cfg_set_key_internal(iniFile, secName, keyName, keyValue, GF_FALSE);
484
485
0
}
486
487
GF_EXPORT
488
u32 gf_cfg_get_section_count(GF_Config *iniFile)
489
0
{
490
0
  return gf_list_count(iniFile->sections);
491
0
}
492
493
GF_EXPORT
494
const char *gf_cfg_get_section_name(GF_Config *iniFile, u32 secIndex)
495
0
{
496
0
  IniSection *is = (IniSection *) gf_list_get(iniFile->sections, secIndex);
497
0
  if (!is) return NULL;
498
0
  return is->section_name;
499
0
}
500
501
GF_EXPORT
502
u32 gf_cfg_get_key_count(GF_Config *iniFile, const char *secName)
503
0
{
504
0
  u32 i = 0;
505
0
  IniSection *sec;
506
0
  while ( (sec = (IniSection *) gf_list_enum(iniFile->sections, &i)) ) {
507
0
    if (!strcmp(secName, sec->section_name)) return gf_list_count(sec->keys);
508
0
  }
509
0
  return 0;
510
0
}
511
512
GF_EXPORT
513
const char *gf_cfg_get_key_name(GF_Config *iniFile, const char *secName, u32 keyIndex)
514
0
{
515
0
  u32 i = 0;
516
0
  IniSection *sec;
517
0
  while ( (sec = (IniSection *) gf_list_enum(iniFile->sections, &i) ) ) {
518
0
    if (!strcmp(secName, sec->section_name)) {
519
0
      IniKey *key = (IniKey *) gf_list_get(sec->keys, keyIndex);
520
0
      return key ? key->name : NULL;
521
0
    }
522
0
  }
523
0
  return NULL;
524
0
}
525
526
GF_EXPORT
527
void gf_cfg_del_section(GF_Config *iniFile, const char *secName)
528
0
{
529
0
  u32 i;
530
0
  IniSection *p;
531
0
  if (!iniFile) return;
532
533
0
  i = 0;
534
0
  while ((p = (IniSection*)gf_list_enum(iniFile->sections, &i))) {
535
0
    if (!strcmp(secName, p->section_name)) {
536
0
      DelSection(p);
537
0
      gf_list_rem(iniFile->sections, i-1);
538
0
      iniFile->hasChanged = GF_TRUE;
539
0
      return;
540
0
    }
541
0
  }
542
0
}
543
544
#if 0 //unused
545
GF_Err gf_cfg_insert_key(GF_Config *iniFile, const char *secName, const char *keyName, const char *keyValue, u32 index)
546
{
547
  u32 i;
548
  IniSection *sec;
549
  IniKey *key;
550
551
  if (!iniFile || !secName || !keyName|| !keyValue) return GF_BAD_PARAM;
552
553
  i=0;
554
  while ( (sec = (IniSection *) gf_list_enum(iniFile->sections, &i) ) ) {
555
    if (!strcmp(secName, sec->section_name)) break;
556
  }
557
  if (!sec) return GF_BAD_PARAM;
558
559
  i=0;
560
  while ( (key = (IniKey *) gf_list_enum(sec->keys, &i) ) ) {
561
    if (!strcmp(key->name, keyName)) return GF_BAD_PARAM;
562
  }
563
564
  key = (IniKey *) gf_malloc(sizeof(IniKey));
565
  key->name = gf_strdup(keyName);
566
  key->value = gf_strdup(keyValue);
567
  gf_list_insert(sec->keys, key, index);
568
  iniFile->hasChanged = GF_TRUE;
569
  return GF_OK;
570
}
571
#endif
572
573
#if 0 //unused
574
const char *gf_cfg_get_sub_key(GF_Config *iniFile, const char *secName, const char *keyName, u32 sub_index)
575
{
576
  u32 j;
577
  char *subKeyValue, *returnKey;
578
  char *keyValue;
579
580
581
  keyValue = gf_strdup(gf_cfg_get_key(iniFile, secName, keyName));
582
  if (!keyValue) {
583
    return NULL;
584
  }
585
586
  j = 0;
587
  subKeyValue = strtok((char*)keyValue,";");
588
  while (subKeyValue!=NULL) {
589
    if (j==sub_index) {
590
      returnKey = gf_strdup(subKeyValue);
591
      gf_free(keyValue);
592
      return returnKey;
593
    }
594
    j++;
595
    subKeyValue = strtok (NULL, ";");
596
  }
597
  gf_free(keyValue);
598
  return NULL;
599
}
600
#endif
601
602
GF_Err gf_cfg_set_filename(GF_Config *iniFile, const char * fileName)
603
0
{
604
0
  if (!fileName) return GF_OK;
605
0
  if (iniFile->fileName) gf_free(iniFile->fileName);
606
0
  iniFile->fileName = gf_strdup(fileName);
607
0
  return iniFile->fileName ? GF_OK : GF_OUT_OF_MEM;
608
0
}