Coverage Report

Created: 2026-02-26 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/opensips/lib/csv.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2017-2019 OpenSIPS Solutions
3
 *
4
 * This file is part of opensips, a free SIP server.
5
 *
6
 * opensips is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 2 of the License, or
9
 * (at your option) any later version
10
 *
11
 * opensips is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
 */
20
21
#include <string.h>
22
#include <stdio.h>
23
24
#include "../mem/mem.h"
25
#include "../trim.h"
26
27
#include "csv.h"
28
29
static osips_malloc_t malloc_f;
30
static osips_free_t free_f;
31
32
static str_list *push_csv_field(const str *field,
33
                                str_list **record, enum csv_flags parse_flags)
34
13.2k
{
35
13.2k
  str_list *rec;
36
13.2k
  enum csv_flags *flags_holder;
37
13.2k
  int len;
38
39
13.2k
  len = sizeof *rec;
40
13.2k
  if (!*record)
41
13.2k
    len += sizeof *flags_holder;
42
43
13.2k
  rec = malloc_f(len);
44
13.2k
  if (!rec) {
45
0
    LM_ERR("oom\n");
46
0
    return NULL;
47
0
  }
48
49
13.2k
  memset(rec, 0, len);
50
51
13.2k
  if (parse_flags & CSV_DUP_FIELDS) {
52
4.23k
    rec->s.s = malloc_f(field->len + 1);
53
4.23k
    if (!rec->s.s) {
54
0
      free_f(rec);
55
0
      LM_ERR("oom\n");
56
0
      return NULL;
57
0
    }
58
4.23k
    memcpy(rec->s.s, field->s, field->len);
59
4.23k
    rec->s.len = field->len;
60
4.23k
    rec->s.s[field->len] = '\0';
61
9.00k
  } else {
62
9.00k
    rec->s = *field;
63
9.00k
  }
64
65
13.2k
  if (!*record) {
66
13.2k
    flags_holder = (enum csv_flags *)(rec + 1);
67
13.2k
    *flags_holder = parse_flags;
68
13.2k
    *record = rec;
69
13.2k
  } else {
70
0
    (*record)->next = rec;
71
0
  }
72
73
13.2k
  return rec;
74
13.2k
}
75
76
csv_record *__parse_csv_record(const str *_in, enum csv_flags parse_flags,
77
                               unsigned char sep)
78
348
{
79
348
  str_list *record = NULL, **last = &record;
80
348
  str in = *_in, field;
81
348
  char *ch, *p, *c, finished, *lim, *field_start;
82
83
348
  if (parse_flags & CSV_SHM) {
84
0
    malloc_f = osips_shm_malloc;
85
0
    free_f = osips_shm_free;
86
348
  } else {
87
348
    malloc_f = osips_pkg_malloc;
88
348
    free_f = osips_pkg_free;
89
348
  }
90
91
348
  if (parse_flags & CSV_RFC_4180)
92
219
    goto rfc_4180_parsing;
93
94
129
  trim(&in);
95
96
9.13k
  for (finished = 0; !finished; ) {
97
9.00k
    ch = memchr(in.s, sep, in.len);
98
9.00k
    if (!ch) {
99
129
      ch = in.s + in.len;
100
129
      finished = 1;
101
129
    }
102
103
9.00k
    field.s = in.s;
104
9.00k
    field.len = ch - in.s;
105
9.00k
    in.s += field.len + 1;
106
9.00k
    in.len -= field.len + 1;
107
9.00k
    trim(&field);
108
109
9.00k
    if (!push_csv_field(&field, last, parse_flags))
110
0
      goto oom;
111
112
9.00k
    last = &(*last)->next;
113
9.00k
  }
114
115
129
  return record;
116
117
219
rfc_4180_parsing:
118
219
  parse_flags |= CSV_DUP_FIELDS;
119
120
219
  if (in.len >= 2 && in.s[in.len - 2] == '\r' && in.s[in.len - 1] == '\n')
121
1
    in.len -= 2;
122
123
219
  field_start = NULL;
124
5.79k
  for (ch = in.s, lim = in.s + in.len; ch < lim; ch++) {
125
5.75k
    if (*ch < 0x20 || *ch > 0x7E)
126
54
      goto bad_csv_str;
127
128
5.69k
    switch (*ch) {
129
2.26k
    case ',':
130
2.26k
      if (field_start)
131
2.21k
        field.s = field_start;
132
56
      else
133
56
        field.s = in.s;
134
135
2.26k
      field.len = ch - field.s;
136
2.26k
      field_start = ch + 1;
137
138
2.26k
      if (!push_csv_field(&field, last, parse_flags))
139
0
        goto oom;
140
141
2.26k
      last = &(*last)->next;
142
2.26k
      break;
143
144
2.59k
    case '"':
145
2.59k
      if ((field_start && ch != field_start) ||
146
2.17k
        (!field_start && ch != in.s))
147
617
        continue;
148
149
13.4k
      for (p = ch + 1; p < lim; p++) {
150
13.3k
        if (*p == '"') {
151
2.60k
          if (p == lim - 1 || *(p + 1) != '"')
152
1.93k
            goto matched_quote;
153
154
675
          p++;
155
675
          continue;
156
2.60k
        }
157
13.3k
      }
158
159
46
      goto bad_csv_str;
160
161
1.93k
matched_quote:
162
1.93k
      field.s = malloc_f(p - ch);
163
1.93k
      if (!field.s)
164
0
        goto oom;
165
166
4.66k
      for (c = field.s; ++ch < p; c++) {
167
2.72k
        if (*ch == '"')
168
481
          ch++;
169
2.72k
        *c = *ch;
170
2.72k
      }
171
172
1.93k
      if (ch < lim - 1) {
173
1.88k
        if (*(ch + 1) != ',') {
174
16
          free_f(field.s);
175
16
          goto bad_csv_str;
176
16
        }
177
1.86k
        ch++;
178
1.86k
        field_start = ch + 1;
179
1.86k
      }
180
181
1.91k
      *c = '\0';
182
1.91k
      field.len = c - field.s;
183
184
1.91k
      if (!push_csv_field(&field, last, parse_flags)) {
185
0
        free_f(field.s);
186
0
        goto oom;
187
0
      }
188
189
1.91k
      free_f(field.s);
190
1.91k
      last = &(*last)->next;
191
192
1.91k
      if (ch == lim - 1)
193
56
        return record;
194
195
1.86k
      break;
196
5.69k
    }
197
5.69k
  }
198
199
47
  if (field_start) {
200
27
    field.s = field_start;
201
27
    field.len = lim - field.s;
202
27
  } else {
203
20
    field = in;
204
20
  }
205
206
47
  if (!push_csv_field(&field, last, parse_flags))
207
0
    goto oom;
208
209
47
  return record;
210
211
116
bad_csv_str:
212
116
  LM_DBG("invalid CSV string: '%.*s'\n", in.len, in.s);
213
116
  free_csv_record(record);
214
116
  return NULL;
215
216
0
oom:
217
0
  LM_ERR("oom while parsing '%.*s'\n", in.len, in.s);
218
0
  free_csv_record(record);
219
0
  return NULL;
220
47
}
221
222
void free_csv_record(csv_record *record)
223
464
{
224
464
  enum csv_flags flags_holder;
225
464
  str_list *prev;
226
227
464
  if (!record)
228
192
    return;
229
230
272
  flags_holder = *(enum csv_flags *)(record + 1);
231
272
  if (flags_holder & CSV_SHM)
232
0
    free_f = osips_shm_free;
233
272
  else
234
272
    free_f = osips_pkg_free;
235
236
13.5k
  while (record) {
237
13.2k
    prev = record;
238
13.2k
    record = record->next;
239
240
13.2k
    if (flags_holder & CSV_DUP_FIELDS)
241
4.23k
      free_f(prev->s.s);
242
243
13.2k
    free_f(prev);
244
13.2k
  }
245
272
}
246
247
static int check_quote_csv_record(str *val, int *escape)
248
0
{
249
0
  char *p;
250
0
  int quote = 0;
251
0
  *escape = 0;
252
253
0
  for (p = val->s; p < val->s + val->len; p++) {
254
0
    switch (*p) {
255
0
      case '"':
256
0
        (*escape)++;
257
        /* fallthrough */
258
0
      case ',':
259
0
      case '\n':
260
0
        quote = 1;
261
0
        break;
262
0
    }
263
0
  }
264
0
  return quote;
265
0
}
266
267
str *__print_csv_record(csv_record *record, enum csv_flags print_flags,
268
            unsigned char sep)
269
0
{
270
0
  static str ret;
271
0
  str_list *it;
272
0
  int len = -1, esc;
273
0
  char *p, *c;
274
275
0
  if (print_flags & CSV_SHM)
276
0
    malloc_f = osips_shm_malloc;
277
0
  else
278
0
    malloc_f = osips_pkg_malloc;
279
280
0
  for (it = record; it; it = it->next) {
281
0
    len += 1 /* sep */ + it->s.len;
282
    /* check to see if ne need to encode */
283
0
    if (check_quote_csv_record(&it->s, &esc))
284
0
      len += 2 + esc;
285
0
  }
286
287
0
  ret.s = malloc_f(len);
288
0
  if (!ret.s)
289
0
    return NULL;
290
0
  p = ret.s;
291
0
  for (it = record; it; it = it->next) {
292
0
    if (it != record)
293
0
      *p++ = sep;
294
295
0
    if (check_quote_csv_record(&it->s, &esc)) {
296
0
      if (!esc) {
297
        /* simply add the quotes */
298
0
        *p++ = '"';
299
0
        memcpy(p, it->s.s, it->s.len);
300
0
        p+= it->s.len;
301
0
        *p++ = '"';
302
0
      } else {
303
0
        for (c = it->s.s; c < it->s.s + it->s.len; c++) {
304
0
          switch (*c) {
305
0
            case '"':
306
0
              *p++ = '"';
307
0
              break;
308
0
          }
309
0
          *p++ = *c;
310
0
        }
311
0
      }
312
0
    } else {
313
      /* simply copy the content */
314
0
      memcpy(p, it->s.s, it->s.len);
315
0
      p += it->s.len;
316
0
    }
317
0
  }
318
0
  ret.len = len;
319
0
  return &ret;
320
0
}