Coverage Report

Created: 2025-07-11 06:28

/src/opensips/db/db_id.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (C) 2001-2005 iptel.org
3
 * Copyright (C) 2007-2008 1&1 Internet AG
4
 *
5
 * This file is part of opensips, a free SIP server.
6
 *
7
 * opensips is free software; you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 2 of the License, or
10
 * (at your option) any later version
11
 *
12
 * opensips is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
20
 */
21
22
/**
23
 * \file db/db_id.c
24
 * \brief Functions for parsing a database URL and work with db identifier.
25
 */
26
27
#include "db_id.h"
28
#include "../dprint.h"
29
#include "../mem/mem.h"
30
#include "../ut.h"
31
#include <stdlib.h>
32
#include <string.h>
33
34
35
/**
36
 * Duplicate a string
37
 * \param dst destination
38
 * \param begin start of the string
39
 * \param end end of the string
40
 */
41
static int dupl_string(char** dst, const char* begin, const char* end)
42
0
{
43
0
  str old, new;
44
45
0
  if (*dst) pkg_free(*dst);
46
47
0
  *dst = pkg_malloc(end - begin + 1);
48
0
  if ((*dst) == NULL) {
49
0
    return -1;
50
0
  }
51
52
0
  old.s = (char*)begin;
53
0
  old.len = end - begin;
54
0
  new.s = *dst;
55
0
  un_escape(&old, &new );
56
57
0
  new.s[new.len] = '\0';
58
0
  return 0;
59
0
}
60
61
62
/**
63
 * Parse a database URL of form
64
 * scheme://[username[:password]@]hostname[:port]/database[?parameters]
65
 *
66
 * \param id filled id struct
67
 * \param url parsed URL
68
 * \return 0 if parsing was successful and -1 otherwise
69
 */
70
static int parse_db_url(struct db_id* id, const str* url)
71
0
{
72
0
#define SHORTEST_DB_URL "s://a/b"
73
0
#define SHORTEST_DB_URL_LEN (sizeof(SHORTEST_DB_URL) - 1)
74
75
0
  enum state {
76
0
    ST_SCHEME,     /* Scheme part */
77
0
    ST_SLASH1,     /* First slash */
78
0
    ST_SLASH2,     /* Second slash */
79
0
    ST_USER_HOST,  /* Username or hostname */
80
0
    ST_PASS_PORT,  /* Password or port part */
81
0
    ST_PASSWORD,   /* Explicitly the password */
82
0
    ST_HOST,       /* Hostname part */
83
0
    ST_HOST6,      /* Hostname part IPv6 */
84
0
    ST_PORT,       /* Port part */
85
0
    ST_UNIX_SOCKET, /* Unix socket */
86
0
    ST_DB,         /* Database part */
87
0
    ST_PARAMS       /* Parameters part */
88
0
  };
89
90
0
  enum state st;
91
0
  unsigned int len, i, ipv6_flag = 0;
92
0
  const char* begin;
93
0
  char* prev_token = NULL, *p;
94
0
  str unix_socket_host = str_init("localhost");
95
0
  int have_pass = 0;
96
97
0
  if (!id || !url || !url->s) {
98
0
    return -1;
99
0
  }
100
101
0
  len = url->len;
102
0
  if (len < SHORTEST_DB_URL_LEN) {
103
0
    return -1;
104
0
  }
105
106
  /* Initialize all attributes to 0 */
107
0
  memset(id, 0, sizeof(struct db_id));
108
0
  st = ST_SCHEME;
109
0
  begin = url->s;
110
111
  /* to allow support for '/' characters in password => look ahead! */
112
0
  p = q_memchr(url->s, ':', len);
113
0
  if (p && q_memchr(p, '@', len - (p - url->s)))
114
0
    have_pass = 1;
115
116
0
  for(i = 0; i < len; i++) {
117
0
    switch(st) {
118
0
    case ST_SCHEME:
119
0
      switch(url->s[i]) {
120
0
      case ':':
121
0
        st = ST_SLASH1;
122
0
        if (dupl_string(&id->scheme, begin, url->s + i) < 0) goto err;
123
0
        break;
124
0
      }
125
0
      break;
126
127
0
    case ST_SLASH1:
128
0
      switch(url->s[i]) {
129
0
      case '/':
130
0
        st = ST_SLASH2;
131
0
        break;
132
133
0
      default:
134
0
        goto err;
135
0
      }
136
0
      break;
137
138
0
    case ST_SLASH2:
139
0
      switch(url->s[i]) {
140
0
      case '/':
141
0
        st = ST_USER_HOST;
142
0
        begin = url->s + i + 1;
143
0
        break;
144
145
0
      default:
146
0
        goto err;
147
0
      }
148
0
      break;
149
150
0
    case ST_USER_HOST:
151
0
      switch(url->s[i]) {
152
0
      case '@':
153
0
        st = ST_HOST;
154
0
        if (dupl_string(&id->username, begin, url->s + i) < 0) goto err;
155
0
        begin = url->s + i + 1;
156
0
        break;
157
158
0
      case ':':
159
0
        if (have_pass)
160
0
          st = ST_PASSWORD;
161
0
        else
162
0
          st = ST_PORT;
163
0
        if (dupl_string(&prev_token, begin, url->s + i) < 0) goto err;
164
0
        begin = url->s + i + 1;
165
0
        break;
166
167
0
      case '[':
168
0
        st = ST_HOST6;
169
0
        begin = url->s + i + 1;
170
0
        break;
171
172
0
      case '/':
173
0
        st = ST_DB;
174
0
        if (dupl_string(&id->host, begin, url->s + i) < 0) goto err;
175
0
        begin = url->s + i + 1;
176
0
      }
177
0
      break;
178
179
0
    case ST_PASS_PORT:
180
0
      switch(url->s[i]) {
181
0
      case '@':
182
0
        st = ST_HOST;
183
0
        id->username = prev_token; prev_token = NULL;
184
0
        if (dupl_string(&id->password, begin, url->s + i) < 0) goto err;
185
0
        begin = url->s + i + 1;
186
0
        break;
187
188
0
      case '/':
189
0
        id->host = prev_token; prev_token = NULL;
190
0
        id->port = str2s(begin, url->s + i - begin, 0);
191
0
        st = ST_DB;
192
0
        begin = url->s + i + 1;
193
0
        break;
194
0
      }
195
0
      break;
196
197
0
    case ST_PASSWORD:
198
0
      switch (url->s[i]) {
199
0
      case '@':  // Only @ terminates password
200
0
        st = ST_HOST;
201
0
        id->username = prev_token; prev_token = NULL;
202
0
        if (dupl_string(&id->password, begin, url->s + i) < 0) goto err;
203
0
        begin = url->s + i + 1;
204
0
        break;
205
0
      }
206
0
      break;
207
208
0
    case ST_HOST:
209
0
      if (strncasecmp(begin, "unix(", 5) == 0) {
210
0
        st = ST_UNIX_SOCKET;
211
0
        i+=5;
212
0
        begin = url->s + i;
213
0
        break;
214
0
      }
215
0
      switch(url->s[i]) {
216
0
      case '[':
217
0
        st = ST_HOST6;
218
0
        begin = url->s + i + 1;
219
0
        break;
220
221
0
      case ':':
222
0
        st = ST_PORT;
223
0
        if (dupl_string(&id->host, begin, url->s + i - ipv6_flag) < 0) goto err;
224
0
        begin = url->s + i + 1;
225
0
        break;
226
227
0
      case '/':
228
0
        if (dupl_string(&id->host, begin, url->s + i - ipv6_flag) < 0) goto err;
229
0
        st = ST_DB;
230
0
        begin = url->s + i + 1;
231
0
      }
232
0
      break;
233
234
0
    case ST_HOST6:
235
0
      switch(url->s[i]) {
236
0
      case ']':
237
0
        ipv6_flag = 1;
238
0
        st = ST_HOST;
239
0
        break;
240
241
0
      }
242
0
      break;
243
244
0
    case ST_UNIX_SOCKET:
245
0
      switch(url->s[i]) {
246
0
      case ')':
247
0
        if (dupl_string(&id->unix_socket, begin, url->s + i) < 0) goto err;
248
0
        if (dupl_string(&id->host, unix_socket_host.s, unix_socket_host.s + unix_socket_host.len) < 0) goto err;
249
0
        begin = url->s + i + 1;
250
0
        if (*begin == '/') {
251
0
          i++;
252
0
          begin = url->s + i + 1;
253
0
        }
254
0
        st = ST_DB;
255
0
      }
256
0
      break;
257
258
0
    case ST_PORT:
259
0
      switch(url->s[i]) {
260
0
      case '/':
261
0
        if (!have_pass) {
262
0
          id->host = prev_token;
263
0
          prev_token = NULL;
264
0
        }
265
0
        id->port = str2s(begin, url->s + i - begin, 0);
266
0
        st = ST_DB;
267
0
        begin = url->s + i + 1;
268
0
      }
269
0
      break;
270
271
0
    case ST_DB:
272
0
      switch(url->s[i]) {
273
0
      case '?':
274
0
        st = ST_PARAMS;
275
0
        if (dupl_string(&id->database, begin, url->s + i) < 0) goto err;
276
0
        begin = url->s + i + 1;
277
0
      }
278
0
      break;
279
280
0
    case ST_PARAMS:
281
0
      break;
282
0
    }
283
0
  }
284
285
0
  if (st != ST_DB && st != ST_PARAMS) goto err;
286
287
0
  if (st == ST_DB) {
288
0
    if (dupl_string(&id->database, begin, url->s + len) < 0) goto err;
289
0
  } else {
290
0
    if (dupl_string(&id->parameters, begin, url->s + len) < 0) goto err;
291
0
  }
292
293
0
  return 0;
294
295
0
 err:
296
0
  if (id->scheme) pkg_free(id->scheme);
297
0
  if (id->username) pkg_free(id->username);
298
0
  if (id->password) pkg_free(id->password);
299
0
  if (id->host) pkg_free(id->host);
300
0
  if (id->unix_socket) pkg_free(id->unix_socket);
301
0
  if (id->database) pkg_free(id->database);
302
0
  if (prev_token) pkg_free(prev_token);
303
0
  return -1;
304
0
}
305
306
307
/**
308
 * Create a new connection identifier
309
 * \param url database URL
310
 * \return connection identifier, or zero on error
311
 */
312
struct db_id* new_db_id(const str* url)
313
0
{
314
0
  struct db_id* ptr;
315
316
0
  if (!url || !url->s) {
317
0
    LM_ERR("invalid parameter\n");
318
0
    return 0;
319
0
  }
320
321
0
  ptr = (struct db_id*)pkg_malloc(sizeof(struct db_id));
322
0
  if (!ptr) {
323
0
    LM_ERR("no private memory left\n");
324
0
    goto err;
325
0
  }
326
0
  memset(ptr, 0, sizeof(struct db_id));
327
328
0
  if (parse_db_url(ptr, url) < 0) {
329
0
    LM_ERR("error while parsing database URL: '%.*s' \n", url->len, url->s);
330
0
    goto err;
331
0
  }
332
333
  /* store the original url */
334
0
  ptr->url.s = url->s;
335
0
  ptr->url.len = url->len;
336
337
0
  return ptr;
338
339
0
 err:
340
0
  if (ptr) pkg_free(ptr);
341
0
  return 0;
342
0
}
343
344
345
/**
346
 * Compare two connection identifiers
347
 * \param id1 first identifier
348
 * \param id2 second identifier
349
 * \return one if both are equal, zero otherwise
350
 */
351
unsigned char cmp_db_id(const struct db_id* id1, const struct db_id* id2)
352
0
{
353
0
  if (!id1 || !id2) return 0;
354
355
0
  if (id1->port != id2->port) return 0;
356
357
0
  if (strcmp(id1->scheme, id2->scheme)) return 0;
358
359
0
  if (id1->username != 0 && id2->username != 0) {
360
0
    if (strcmp(id1->username, id2->username)) return 0;
361
0
  } else {
362
0
    if (id1->username!=0 || id2->username!=0) return 0;
363
0
  }
364
365
0
  if (id1->password!=0 && id2->password!=0) {
366
0
    if(strcmp(id1->password, id2->password)) return 0;
367
0
  } else {
368
0
    if (id1->password!=0 || id2->password!=0) return 0;
369
0
  }
370
371
0
  if (strcasecmp(id1->host, id2->host)) return 0;
372
373
0
  if (id1->unix_socket!=0 && id2->unix_socket!=0) {
374
0
    if (strcasecmp(id1->unix_socket, id2->unix_socket)) return 0;
375
0
  } else {
376
0
    if (id1->unix_socket!=0 || id2->unix_socket!=0) return 0;
377
0
  }
378
379
0
  if (strcmp(id1->database, id2->database)) return 0;
380
381
0
  if (id1->parameters != 0 && id2->parameters != 0) {
382
0
    if(strcmp(id1->parameters, id2->parameters)) return 0;
383
0
  } else {
384
0
    if (id1->parameters!=0 || id2->parameters!=0) return 0;
385
0
  }
386
387
0
  return 1;
388
0
}
389
390
391
/**
392
 * Free a connection identifier
393
 * \param id identifier
394
 */
395
void free_db_id(struct db_id* id)
396
0
{
397
0
  if (!id) return;
398
399
0
  if (id->scheme) pkg_free(id->scheme);
400
0
  if (id->username) pkg_free(id->username);
401
0
  if (id->password) pkg_free(id->password);
402
0
  if (id->host) pkg_free(id->host);
403
0
  if (id->unix_socket) pkg_free(id->unix_socket);
404
0
  if (id->database) pkg_free(id->database);
405
0
  if (id->parameters) pkg_free(id->parameters);
406
0
  pkg_free(id);
407
0
}