Coverage Report

Created: 2026-08-31 08:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/modules/access/rtp/sdp.c
Line
Count
Source
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 = 0, 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
    const 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
    char *port_cur;
192
0
    unsigned long port_start = strtoul(port, &port_cur, 10);
193
0
    unsigned long port_count = 1;
194
195
0
    if (*port_cur == '/')
196
0
        port_count = strtoul(port_cur + 1, &port_cur, 10);
197
0
    if (port_cur != port_end)
198
0
        goto bad;
199
200
0
    const char *proto_end = memchr(proto, ' ', end - proto);
201
202
0
    if (proto_end == NULL)
203
0
        goto bad;
204
205
0
    const char *format = proto_end + 1;
206
207
0
    if (format >= end)
208
0
        goto bad;
209
210
0
    struct vlc_sdp_media *m = malloc(sizeof (*m));
211
0
    if (unlikely(m == NULL))
212
0
        return NULL;
213
214
0
    m->next = NULL;
215
0
    m->session = sdp;
216
0
    m->conns = NULL;
217
0
    m->attrs = NULL;
218
0
    m->type = strndup(media, media_end - media);
219
0
    m->port = port_start;
220
0
    m->port_count = port_count;
221
0
    m->proto = strndup(proto, proto_end - proto);
222
0
    m->format = strndup(format, end - format);
223
224
0
    if (unlikely(m->type == NULL || m->proto == NULL || m->format == NULL))
225
0
        vlc_sdp_media_free(&m);
226
0
    if (!vlc_sdp_is_token(m->type)) {
227
0
        vlc_sdp_media_free(&m);
228
0
        errno = EINVAL;
229
0
    }
230
231
0
    return m;
232
0
}
233
234
struct vlc_sdp_input
235
{
236
    const char *cursor;
237
    const char *end;
238
};
239
240
static int vlc_sdp_getline(struct vlc_sdp_input *restrict in,
241
                           const char **restrict pp, size_t *restrict lenp)
242
0
{
243
0
    assert(in->end >= in->cursor);
244
0
    *lenp = 0;
245
246
0
    if (in->end == in->cursor)
247
0
        return 0; /* end */
248
249
0
    const char *lf = memchr(in->cursor, '\n', in->end - in->cursor);
250
251
0
    if (lf == NULL)
252
0
        goto error; /* cannot locate end of line */
253
254
0
    const char *end = memchr(in->cursor, '\r', lf - in->cursor);
255
0
    if (end != NULL) {
256
        /* CR should be present. If so, it must be right before LF. */
257
0
        if (end != lf - 1)
258
0
            goto error; /* CR within a line is not permitted. */
259
0
    } else
260
0
        end = lf;
261
262
0
    if ((end - in->cursor) < 2 || in->cursor[1] != '=')
263
0
        goto error;
264
265
0
    int c = (unsigned char)in->cursor[0];
266
267
0
    *pp = in->cursor + 2;
268
0
    *lenp = end - *pp;
269
0
    in->cursor = lf + 1;
270
271
0
    return c;
272
273
0
error:
274
0
    errno = EINVAL;
275
0
    return -1;
276
0
}
277
278
const struct vlc_sdp_attr *vlc_sdp_attr_first_by_name(
279
    struct vlc_sdp_attr *const *ap, const char *name)
280
0
{
281
0
    for (const struct vlc_sdp_attr *a = *ap; a != NULL; a = a->next)
282
0
        if (!strcmp(a->name, name))
283
0
            return a;
284
285
0
    return NULL;
286
0
}
287
288
void vlc_sdp_free(struct vlc_sdp *sdp)
289
0
{
290
0
    while (sdp->media != NULL)
291
0
        vlc_sdp_media_free(&sdp->media);
292
293
0
    while (sdp->attrs != NULL)
294
0
        vlc_sdp_attr_free(&sdp->attrs);
295
296
0
    if (sdp->conn != NULL)
297
0
        vlc_sdp_conn_free(&sdp->conn);
298
299
0
    free(sdp->info);
300
0
    free(sdp->name);
301
0
    free(sdp);
302
0
}
303
304
struct vlc_sdp *vlc_sdp_parse(const char *str, size_t length)
305
0
{
306
0
    if (memchr(str, 0, length) != NULL) {
307
        /* Nul byte inside the SDP is not permitted. */
308
0
        errno = EINVAL;
309
0
        return NULL;
310
0
    }
311
312
0
    struct vlc_sdp_input in = { str, str + length };
313
0
    const char *line;
314
0
    size_t linelen;
315
0
    int c;
316
317
    /* Version line, must be "0" */
318
0
    if (vlc_sdp_getline(&in, &line, &linelen) != 'v'
319
0
     || linelen != 1 || memcmp(line, "0", 1)) {
320
0
        errno = EINVAL;
321
0
        return NULL;
322
0
    }
323
324
    /* Origin line (ignored for now) */
325
0
    if (vlc_sdp_getline(&in, &line, &linelen) != 'o') {
326
0
        errno = EINVAL;
327
0
        return NULL;
328
0
    }
329
330
0
    struct vlc_sdp *sdp = malloc(sizeof (*sdp));
331
0
    if (unlikely(sdp == NULL))
332
0
        return NULL;
333
334
0
    sdp->name = NULL;
335
0
    sdp->info = NULL;
336
0
    sdp->conn = NULL;
337
0
    sdp->attrs = NULL;
338
0
    sdp->media = NULL;
339
340
    /* Session name line */
341
0
    if (vlc_sdp_getline(&in, &line, &linelen) != 's')
342
0
        goto bad;
343
344
0
    sdp->name = strndup(line, linelen);
345
0
    if (unlikely(sdp->name == NULL))
346
0
        goto error;
347
348
0
    c = vlc_sdp_getline(&in, &line, &linelen);
349
350
    /* Session information line (optional) */
351
0
    if (c == 'i') {
352
0
        sdp->info = strndup(line, linelen);
353
0
        if (unlikely(sdp->info == NULL))
354
0
            goto error;
355
356
0
        c = vlc_sdp_getline(&in, &line, &linelen);
357
0
    }
358
359
    /* URL line (optional) */
360
0
    if (c == 'u')
361
0
        c = vlc_sdp_getline(&in, &line, &linelen);
362
363
    /* Email lines */
364
0
    while (c == 'e')
365
0
        c = vlc_sdp_getline(&in, &line, &linelen);
366
367
    /* Phone number lines */
368
0
    while (c == 'p')
369
0
        c = vlc_sdp_getline(&in, &line, &linelen);
370
371
    /* Session connection line (optional) */
372
0
    if (c == 'c') {
373
0
        sdp->conn = vlc_sdp_conn_parse(line, linelen);
374
0
        if (sdp->conn == NULL)
375
0
            goto error;
376
377
0
        c = vlc_sdp_getline(&in, &line, &linelen);
378
0
    }
379
380
    /* Session bandwidth lines */
381
0
    while (c == 'b')
382
0
        c = vlc_sdp_getline(&in, &line, &linelen);
383
384
    /* Time descriptions / Session time lines */
385
0
    while (c == 't') {
386
0
        c = vlc_sdp_getline(&in, &line, &linelen);
387
388
        /* Repeat lines */
389
0
        while (c == 'r')
390
0
            c = vlc_sdp_getline(&in, &line, &linelen);
391
0
    }
392
393
    /* Time adjustment lines */
394
0
    while (c == 'z')
395
0
        c = vlc_sdp_getline(&in, &line, &linelen);
396
397
    /* Session encryption key line (unused in real life) */
398
0
    if (c == 'k')
399
0
        c = vlc_sdp_getline(&in, &line, &linelen);
400
401
    /* Session attribute lines */
402
0
    for (struct vlc_sdp_attr **ap = &sdp->attrs; c == 'a';) {
403
0
        struct vlc_sdp_attr *a = vlc_sdp_attr_parse(line, linelen);
404
0
        if (a == NULL)
405
0
            goto error;
406
407
0
        *ap = a;
408
0
        ap = &a->next;
409
0
        c = vlc_sdp_getline(&in, &line, &linelen);
410
0
    }
411
412
    /* Media descriptions / Media lines */
413
0
    for (struct vlc_sdp_media **mp = &sdp->media; c == 'm';) {
414
0
        struct vlc_sdp_media *m = vlc_sdp_media_parse(sdp, line, linelen);
415
0
        if (m == NULL)
416
0
            goto error;
417
418
0
        *mp = m;
419
0
        mp = &m->next;
420
0
        c = vlc_sdp_getline(&in, &line, &linelen);
421
422
        /* Media title line */
423
0
        if (c == 'i')
424
0
            c = vlc_sdp_getline(&in, &line, &linelen);
425
426
        /* Media connection lines */
427
0
        for (struct vlc_sdp_conn **cp = &m->conns; c == 'c';) {
428
0
             struct vlc_sdp_conn *conn = vlc_sdp_conn_parse(line, linelen);
429
0
             if (conn == NULL)
430
0
                 goto error;
431
432
0
             *cp = conn;
433
0
             cp = &conn->next;
434
0
             c = vlc_sdp_getline(&in, &line, &linelen);
435
0
        }
436
437
        /* Media bandwidth lines */
438
0
        while (c == 'b')
439
0
            c = vlc_sdp_getline(&in, &line, &linelen);
440
441
        /* Media encryption key line (unused in real life) */
442
0
        if (c == 'k')
443
0
            c = vlc_sdp_getline(&in, &line, &linelen);
444
445
        /* Session attribute lines */
446
0
        for (struct vlc_sdp_attr **ap = &m->attrs; c == 'a';) {
447
0
            struct vlc_sdp_attr *a = vlc_sdp_attr_parse(line, linelen);
448
0
            if (a == NULL)
449
0
                goto error;
450
451
0
            *ap = a;
452
0
            ap = &a->next;
453
0
            c = vlc_sdp_getline(&in, &line, &linelen);
454
0
        }
455
0
    }
456
457
0
    if (c == 0)
458
0
        return sdp;
459
460
0
bad:
461
0
    errno = EINVAL;
462
0
error:
463
0
    vlc_sdp_free(sdp);
464
    return NULL;
465
0
}