/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 | } |