Coverage Report

Created: 2025-07-18 08:04

/src/vlc/modules/access/rtp/sdp.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file sdp.c
3
 * @brief Real-Time Protocol (RTP) demux module for VLC media player
4
 */
5
/*****************************************************************************
6
 * Copyright © 2020 Rémi Denis-Courmont
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public License
10
 * as published by the Free Software Foundation; either version 2.1
11
 * of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
 * GNU Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public License
19
 * along with this library; if not, write to the Free Software Foundation,
20
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
21
 ****************************************************************************/
22
23
#ifdef HAVE_CONFIG_H
24
# include "config.h"
25
#endif
26
27
#include <assert.h>
28
#include <errno.h>
29
#include <stdlib.h>
30
#include <string.h>
31
#include "sdp.h"
32
#include <vlc_common.h>
33
34
static bool istokenchar(unsigned char c)
35
0
{   /* RFC4566 §9 */
36
0
    if (c < 0x21 || c > 0x7E)
37
0
        return false;
38
0
    if (memchr("\x22\x28\x29\x2C\x2F\x5B\x5C\x5D", c, 8) != NULL)
39
0
        return false;
40
0
    if (c > 0x39 && c < 0x41)
41
0
        return false;
42
0
    return true;
43
0
}
44
45
static size_t vlc_sdp_token_length(const char *str)
46
0
{
47
0
    const char *p = str;
48
49
0
    while (istokenchar(*p))
50
0
        p++;
51
0
    return p - str;
52
0
}
53
54
static bool vlc_sdp_is_token(const char *str)
55
0
{
56
0
    return str[vlc_sdp_token_length(str)] == '\0';
57
0
}
58
59
static void vlc_sdp_conn_free(struct vlc_sdp_conn **conn)
60
0
{
61
0
    struct vlc_sdp_conn *c = *conn;
62
63
0
    *conn = c->next;
64
0
    free(c);
65
0
}
66
67
static struct vlc_sdp_conn *vlc_sdp_conn_parse(const char *str, size_t len)
68
0
{
69
0
    const char *end = str + len;
70
0
    const char *net_type = str;
71
0
    const char *addr_type = memchr(str, ' ', len);
72
73
0
    if (addr_type == NULL) {
74
0
bad:
75
0
        errno = EINVAL;
76
0
        return NULL;
77
0
    }
78
79
0
    addr_type++; /* skip white space */
80
0
    const char *addr = memchr(addr_type, ' ', end - addr_type);
81
82
0
    if (addr == NULL)
83
0
        goto bad;
84
85
0
    addr++; /* skip white space */
86
87
0
    if (memchr(addr, ' ', end - addr) != NULL)
88
0
        goto bad;
89
90
0
    size_t addrlen = end - addr;
91
0
    struct vlc_sdp_conn *c = malloc(sizeof (*c) + addrlen + 1);
92
0
    if (unlikely(c == NULL))
93
0
        return NULL;
94
95
0
    c->next = NULL;
96
0
    c->family = 0;
97
0
    c->ttl = 255;
98
0
    c->addr_count = 1;
99
0
    memcpy(c->addr, addr, addrlen);
100
0
    c->addr[addrlen] = '\0';
101
102
0
    if (len >= 7 && memcmp(net_type, "IN ", 3) == 0) {
103
0
        int offset, val = -1;
104
105
0
        if (memcmp(addr_type, "IP4 ", 4) == 0) {
106
            /* IPv4 */
107
0
            c->family = 4;
108
0
            val = sscanf(c->addr, "%*[^/]%n/%hhu/%hu", &offset, &c->ttl,
109
0
                         &c->addr_count);
110
111
0
        } else if (memcmp(addr_type, "IP6 ", 4) == 0) {
112
            /* IPv6 */
113
0
            c->family = 6;
114
0
            val = sscanf(c->addr, "%*[^/]%n/%hu", &offset, &c->addr_count);
115
0
        }
116
117
0
        if (val >= 0)
118
0
            c->addr[offset] = '\0';
119
0
    }
120
121
0
    return c;
122
0
}
123
124
static struct vlc_sdp_attr *vlc_sdp_attr_parse(const char *str, size_t len)
125
0
{
126
0
    size_t namelen = vlc_sdp_token_length(str);
127
0
    if (namelen < len && str[namelen] != ':') {
128
0
        errno = EINVAL;
129
0
        return NULL;
130
0
    }
131
132
0
    struct vlc_sdp_attr *a = malloc(sizeof (*a) + len + 1);
133
0
    if (unlikely(a == NULL))
134
0
        return NULL;
135
136
0
    memcpy(a->name, str, len);
137
0
    a->name[namelen] = '\0';
138
0
    if (namelen < len) {
139
0
        a->name[len] = '\0';
140
0
        a->value = a->name + namelen + 1;
141
0
    } else
142
0
        a->value = NULL;
143
0
    a->next = NULL;
144
0
    return a;
145
0
}
146
147
static void vlc_sdp_attr_free(struct vlc_sdp_attr **attr)
148
0
{
149
0
    struct vlc_sdp_attr *a = *attr;
150
151
0
    *attr = a->next;
152
0
    free(a);
153
0
}
154
155
static void vlc_sdp_media_free(struct vlc_sdp_media **media)
156
0
{
157
0
    struct vlc_sdp_media *m = *media;
158
159
0
    while (m->conns != NULL)
160
0
        vlc_sdp_conn_free(&m->conns);
161
0
    while (m->attrs != NULL)
162
0
        vlc_sdp_attr_free(&m->attrs);
163
164
0
    *media = m->next;
165
0
    free(m->format);
166
0
    free(m->proto);
167
0
    free(m->type);
168
0
    free(m);
169
0
}
170
171
static struct vlc_sdp_media *vlc_sdp_media_parse(struct vlc_sdp *sdp,
172
                                                 const char *str, size_t len)
173
0
{
174
0
    const char *end = str + len;
175
0
    const char *media = str;
176
0
    const char *media_end = memchr(str, ' ', end - str);
177
178
0
    if (media_end == NULL) {
179
0
bad:
180
0
        errno = EINVAL;
181
0
        return NULL;
182
0
    }
183
184
0
    const char *port = media_end + 1;
185
0
    char *port_end = memchr(port, ' ', end - port);
186
187
0
    if (port_end == NULL)
188
0
        goto bad;
189
190
0
    const char *proto = port_end + 1;
191
0
    unsigned long port_start = strtoul(port, &port_end, 10);
192
0
    unsigned long port_count = 1;
193
194
0
    if (*port_end == '/')
195
0
        port_count = strtoul(port_end + 1, &port_end, 10);
196
0
    if (*port_end != ' ')
197
0
        goto bad;
198
199
0
    const char *proto_end = memchr(proto, ' ', end - proto);
200
201
0
    if (proto_end == NULL)
202
0
        goto bad;
203
204
0
    const char *format = proto_end + 1;
205
206
0
    if (format >= end)
207
0
        goto bad;
208
209
0
    struct vlc_sdp_media *m = malloc(sizeof (*m));
210
0
    if (unlikely(m == NULL))
211
0
        return NULL;
212
213
0
    m->next = NULL;
214
0
    m->session = sdp;
215
0
    m->conns = NULL;
216
0
    m->attrs = NULL;
217
0
    m->type = strndup(media, media_end - media);
218
0
    m->port = port_start;
219
0
    m->port_count = port_count;
220
0
    m->proto = strndup(proto, proto_end - proto);
221
0
    m->format = strndup(format, end - format);
222
223
0
    if (unlikely(m->type == NULL || m->proto == NULL || m->format == NULL))
224
0
        vlc_sdp_media_free(&m);
225
0
    if (!vlc_sdp_is_token(m->type)) {
226
0
        vlc_sdp_media_free(&m);
227
0
        errno = EINVAL;
228
0
    }
229
230
0
    return m;
231
0
}
232
233
struct vlc_sdp_input
234
{
235
    const char *cursor;
236
    const char *end;
237
};
238
239
static int vlc_sdp_getline(struct vlc_sdp_input *restrict in,
240
                           const char **restrict pp, size_t *restrict lenp)
241
0
{
242
0
    assert(in->end >= in->cursor);
243
0
    *lenp = 0;
244
245
0
    if (in->end == in->cursor)
246
0
        return 0; /* end */
247
248
0
    const char *lf = memchr(in->cursor, '\n', in->end - in->cursor);
249
250
0
    if (lf == NULL)
251
0
        goto error; /* cannot locate end of line */
252
253
0
    const char *end = memchr(in->cursor, '\r', lf - in->cursor);
254
0
    if (end != NULL) {
255
        /* CR should be present. If so, it must be right before LF. */
256
0
        if (end != lf - 1)
257
0
            goto error; /* CR within a line is not permitted. */
258
0
    } else
259
0
        end = lf;
260
261
0
    if ((end - in->cursor) < 2 || in->cursor[1] != '=')
262
0
        goto error;
263
264
0
    int c = (unsigned char)in->cursor[0];
265
266
0
    *pp = in->cursor + 2;
267
0
    *lenp = end - *pp;
268
0
    in->cursor = lf + 1;
269
270
0
    return c;
271
272
0
error:
273
0
    errno = EINVAL;
274
0
    return -1;
275
0
}
276
277
const struct vlc_sdp_attr *vlc_sdp_attr_first_by_name(
278
    struct vlc_sdp_attr *const *ap, const char *name)
279
0
{
280
0
    for (const struct vlc_sdp_attr *a = *ap; a != NULL; a = a->next)
281
0
        if (!strcmp(a->name, name))
282
0
            return a;
283
284
0
    return NULL;
285
0
}
286
287
void vlc_sdp_free(struct vlc_sdp *sdp)
288
0
{
289
0
    while (sdp->media != NULL)
290
0
        vlc_sdp_media_free(&sdp->media);
291
292
0
    while (sdp->attrs != NULL)
293
0
        vlc_sdp_attr_free(&sdp->attrs);
294
295
0
    if (sdp->conn != NULL)
296
0
        vlc_sdp_conn_free(&sdp->conn);
297
298
0
    free(sdp->info);
299
0
    free(sdp->name);
300
0
    free(sdp);
301
0
}
302
303
struct vlc_sdp *vlc_sdp_parse(const char *str, size_t length)
304
0
{
305
0
    if (memchr(str, 0, length) != NULL) {
306
        /* Nul byte inside the SDP is not permitted. */
307
0
        errno = EINVAL;
308
0
        return NULL;
309
0
    }
310
311
0
    struct vlc_sdp_input in = { str, str + length };
312
0
    const char *line;
313
0
    size_t linelen;
314
0
    int c;
315
316
    /* Version line, must be "0" */
317
0
    if (vlc_sdp_getline(&in, &line, &linelen) != 'v'
318
0
     || linelen != 1 || memcmp(line, "0", 1)) {
319
0
        errno = EINVAL;
320
0
        return NULL;
321
0
    }
322
323
    /* Origin line (ignored for now) */
324
0
    if (vlc_sdp_getline(&in, &line, &linelen) != 'o') {
325
0
        errno = EINVAL;
326
0
        return NULL;
327
0
    }
328
329
0
    struct vlc_sdp *sdp = malloc(sizeof (*sdp));
330
0
    if (unlikely(sdp == NULL))
331
0
        return NULL;
332
333
0
    sdp->name = NULL;
334
0
    sdp->info = NULL;
335
0
    sdp->conn = NULL;
336
0
    sdp->attrs = NULL;
337
0
    sdp->media = NULL;
338
339
    /* Session name line */
340
0
    if (vlc_sdp_getline(&in, &line, &linelen) != 's')
341
0
        goto bad;
342
343
0
    sdp->name = strndup(line, linelen);
344
0
    if (unlikely(sdp->name == NULL))
345
0
        goto error;
346
347
0
    c = vlc_sdp_getline(&in, &line, &linelen);
348
349
    /* Session information line (optional) */
350
0
    if (c == 'i') {
351
0
        sdp->info = strndup(line, linelen);
352
0
        if (unlikely(sdp->info == NULL))
353
0
            goto error;
354
355
0
        c = vlc_sdp_getline(&in, &line, &linelen);
356
0
    }
357
358
    /* URL line (optional) */
359
0
    if (c == 'u')
360
0
        c = vlc_sdp_getline(&in, &line, &linelen);
361
362
    /* Email lines */
363
0
    while (c == 'e')
364
0
        c = vlc_sdp_getline(&in, &line, &linelen);
365
366
    /* Phone number lines */
367
0
    while (c == 'p')
368
0
        c = vlc_sdp_getline(&in, &line, &linelen);
369
370
    /* Session connection line (optional) */
371
0
    if (c == 'c') {
372
0
        sdp->conn = vlc_sdp_conn_parse(line, linelen);
373
0
        if (sdp->conn == NULL)
374
0
            goto error;
375
376
0
        c = vlc_sdp_getline(&in, &line, &linelen);
377
0
    }
378
379
    /* Session bandwidth lines */
380
0
    while (c == 'b')
381
0
        c = vlc_sdp_getline(&in, &line, &linelen);
382
383
    /* Time descriptions / Session time lines */
384
0
    while (c == 't') {
385
0
        c = vlc_sdp_getline(&in, &line, &linelen);
386
387
        /* Repeat lines */
388
0
        while (c == 'r')
389
0
            c = vlc_sdp_getline(&in, &line, &linelen);
390
0
    }
391
392
    /* Time adjustment lines */
393
0
    while (c == 'z')
394
0
        c = vlc_sdp_getline(&in, &line, &linelen);
395
396
    /* Session encryption key line (unused in real life) */
397
0
    if (c == 'k')
398
0
        c = vlc_sdp_getline(&in, &line, &linelen);
399
400
    /* Session attribute lines */
401
0
    for (struct vlc_sdp_attr **ap = &sdp->attrs; c == 'a';) {
402
0
        struct vlc_sdp_attr *a = vlc_sdp_attr_parse(line, linelen);
403
0
        if (a == NULL)
404
0
            goto error;
405
406
0
        *ap = a;
407
0
        ap = &a->next;
408
0
        c = vlc_sdp_getline(&in, &line, &linelen);
409
0
    }
410
411
    /* Media descriptions / Media lines */
412
0
    for (struct vlc_sdp_media **mp = &sdp->media; c == 'm';) {
413
0
        struct vlc_sdp_media *m = vlc_sdp_media_parse(sdp, line, linelen);
414
0
        if (m == NULL)
415
0
            goto error;
416
417
0
        *mp = m;
418
0
        mp = &m->next;
419
0
        c = vlc_sdp_getline(&in, &line, &linelen);
420
421
        /* Media title line */
422
0
        if (c == 'i')
423
0
            c = vlc_sdp_getline(&in, &line, &linelen);
424
425
        /* Media connection lines */
426
0
        for (struct vlc_sdp_conn **cp = &m->conns; c == 'c';) {
427
0
             struct vlc_sdp_conn *conn = vlc_sdp_conn_parse(line, linelen);
428
0
             if (conn == NULL)
429
0
                 goto error;
430
431
0
             *cp = conn;
432
0
             cp = &conn->next;
433
0
             c = vlc_sdp_getline(&in, &line, &linelen);
434
0
        }
435
436
        /* Media bandwidth lines */
437
0
        while (c == 'b')
438
0
            c = vlc_sdp_getline(&in, &line, &linelen);
439
440
        /* Media encryption key line (unused in real life) */
441
0
        if (c == 'k')
442
0
            c = vlc_sdp_getline(&in, &line, &linelen);
443
444
        /* Session attribute lines */
445
0
        for (struct vlc_sdp_attr **ap = &m->attrs; c == 'a';) {
446
0
            struct vlc_sdp_attr *a = vlc_sdp_attr_parse(line, linelen);
447
0
            if (a == NULL)
448
0
                goto error;
449
450
0
            *ap = a;
451
0
            ap = &a->next;
452
0
            c = vlc_sdp_getline(&in, &line, &linelen);
453
0
        }
454
0
    }
455
456
0
    if (c == 0)
457
0
        return sdp;
458
459
0
bad:
460
0
    errno = EINVAL;
461
0
error:
462
0
    vlc_sdp_free(sdp);
463
0
    return NULL;
464
0
}