1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24
25 #include "curl_setup.h"
26
27 #if !defined(CURL_DISABLE_RTSP)
28
29 #include "urldata.h"
30 #include <curl/curl.h>
31 #include "transfer.h"
32 #include "sendf.h"
33 #include "multiif.h"
34 #include "http.h"
35 #include "url.h"
36 #include "progress.h"
37 #include "rtsp.h"
38 #include "strcase.h"
39 #include "select.h"
40 #include "connect.h"
41 #include "cfilters.h"
42 #include "strdup.h"
43 /* The last 3 #include files should be in this order */
44 #include "curl_printf.h"
45 #include "curl_memory.h"
46 #include "memdebug.h"
47
48 #define RTP_PKT_LENGTH(p) ((((unsigned int)((unsigned char)((p)[2]))) << 8) | \
49 ((unsigned int)((unsigned char)((p)[3]))))
50
51 /* protocol-specific functions set up to be called by the main engine */
52 static CURLcode rtsp_do(struct Curl_easy *data, bool *done);
53 static CURLcode rtsp_done(struct Curl_easy *data, CURLcode, bool premature);
54 static CURLcode rtsp_connect(struct Curl_easy *data, bool *done);
55 static CURLcode rtsp_disconnect(struct Curl_easy *data,
56 struct connectdata *conn, bool dead);
57 static int rtsp_getsock_do(struct Curl_easy *data,
58 struct connectdata *conn, curl_socket_t *socks);
59
60 /*
61 * Parse and write out an RTSP response.
62 * @param data the transfer
63 * @param conn the connection
64 * @param buf data read from connection
65 * @param blen amount of data in buf
66 * @param is_eos TRUE iff this is the last write
67 * @param readmore out, TRUE iff complete buf was consumed and more data
68 * is needed
69 */
70 static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data,
71 const char *buf,
72 size_t blen,
73 bool is_eos);
74
75 static CURLcode rtsp_setup_connection(struct Curl_easy *data,
76 struct connectdata *conn);
77 static unsigned int rtsp_conncheck(struct Curl_easy *data,
78 struct connectdata *check,
79 unsigned int checks_to_perform);
80
81 /* this returns the socket to wait for in the DO and DOING state for the multi
82 interface and then we are always _sending_ a request and thus we wait for
83 the single socket to become writable only */
rtsp_getsock_do(struct Curl_easy * data,struct connectdata * conn,curl_socket_t * socks)84 static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn,
85 curl_socket_t *socks)
86 {
87 /* write mode */
88 (void)data;
89 socks[0] = conn->sock[FIRSTSOCKET];
90 return GETSOCK_WRITESOCK(0);
91 }
92
93 static
94 CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len);
95 static
96 CURLcode rtsp_parse_transport(struct Curl_easy *data, const char *transport);
97
98
99 /*
100 * RTSP handler interface.
101 */
102 const struct Curl_handler Curl_handler_rtsp = {
103 "rtsp", /* scheme */
104 rtsp_setup_connection, /* setup_connection */
105 rtsp_do, /* do_it */
106 rtsp_done, /* done */
107 ZERO_NULL, /* do_more */
108 rtsp_connect, /* connect_it */
109 ZERO_NULL, /* connecting */
110 ZERO_NULL, /* doing */
111 ZERO_NULL, /* proto_getsock */
112 rtsp_getsock_do, /* doing_getsock */
113 ZERO_NULL, /* domore_getsock */
114 ZERO_NULL, /* perform_getsock */
115 rtsp_disconnect, /* disconnect */
116 rtsp_rtp_write_resp, /* write_resp */
117 ZERO_NULL, /* write_resp_hd */
118 rtsp_conncheck, /* connection_check */
119 ZERO_NULL, /* attach connection */
120 Curl_http_follow, /* follow */
121 PORT_RTSP, /* defport */
122 CURLPROTO_RTSP, /* protocol */
123 CURLPROTO_RTSP, /* family */
124 PROTOPT_NONE /* flags */
125 };
126
127 #define MAX_RTP_BUFFERSIZE 1000000 /* arbitrary */
128
rtsp_setup_connection(struct Curl_easy * data,struct connectdata * conn)129 static CURLcode rtsp_setup_connection(struct Curl_easy *data,
130 struct connectdata *conn)
131 {
132 struct RTSP *rtsp;
133 (void)conn;
134
135 data->req.p.rtsp = rtsp = calloc(1, sizeof(struct RTSP));
136 if(!rtsp)
137 return CURLE_OUT_OF_MEMORY;
138
139 Curl_dyn_init(&conn->proto.rtspc.buf, MAX_RTP_BUFFERSIZE);
140 return CURLE_OK;
141 }
142
143
144 /*
145 * Function to check on various aspects of a connection.
146 */
rtsp_conncheck(struct Curl_easy * data,struct connectdata * conn,unsigned int checks_to_perform)147 static unsigned int rtsp_conncheck(struct Curl_easy *data,
148 struct connectdata *conn,
149 unsigned int checks_to_perform)
150 {
151 unsigned int ret_val = CONNRESULT_NONE;
152 (void)data;
153
154 if(checks_to_perform & CONNCHECK_ISDEAD) {
155 bool input_pending;
156 if(!Curl_conn_is_alive(data, conn, &input_pending))
157 ret_val |= CONNRESULT_DEAD;
158 }
159
160 return ret_val;
161 }
162
163
rtsp_connect(struct Curl_easy * data,bool * done)164 static CURLcode rtsp_connect(struct Curl_easy *data, bool *done)
165 {
166 CURLcode httpStatus;
167
168 httpStatus = Curl_http_connect(data, done);
169
170 /* Initialize the CSeq if not already done */
171 if(data->state.rtsp_next_client_CSeq == 0)
172 data->state.rtsp_next_client_CSeq = 1;
173 if(data->state.rtsp_next_server_CSeq == 0)
174 data->state.rtsp_next_server_CSeq = 1;
175
176 data->conn->proto.rtspc.rtp_channel = -1;
177
178 return httpStatus;
179 }
180
rtsp_disconnect(struct Curl_easy * data,struct connectdata * conn,bool dead)181 static CURLcode rtsp_disconnect(struct Curl_easy *data,
182 struct connectdata *conn, bool dead)
183 {
184 (void) dead;
185 (void) data;
186 Curl_dyn_free(&conn->proto.rtspc.buf);
187 return CURLE_OK;
188 }
189
190
rtsp_done(struct Curl_easy * data,CURLcode status,bool premature)191 static CURLcode rtsp_done(struct Curl_easy *data,
192 CURLcode status, bool premature)
193 {
194 struct RTSP *rtsp = data->req.p.rtsp;
195 CURLcode httpStatus;
196
197 /* Bypass HTTP empty-reply checks on receive */
198 if(data->set.rtspreq == RTSPREQ_RECEIVE)
199 premature = TRUE;
200
201 httpStatus = Curl_http_done(data, status, premature);
202
203 if(rtsp && !status && !httpStatus) {
204 /* Check the sequence numbers */
205 long CSeq_sent = rtsp->CSeq_sent;
206 long CSeq_recv = rtsp->CSeq_recv;
207 if((data->set.rtspreq != RTSPREQ_RECEIVE) && (CSeq_sent != CSeq_recv)) {
208 failf(data,
209 "The CSeq of this request %ld did not match the response %ld",
210 CSeq_sent, CSeq_recv);
211 return CURLE_RTSP_CSEQ_ERROR;
212 }
213 if(data->set.rtspreq == RTSPREQ_RECEIVE &&
214 (data->conn->proto.rtspc.rtp_channel == -1)) {
215 infof(data, "Got an RTP Receive with a CSeq of %ld", CSeq_recv);
216 }
217 if(data->set.rtspreq == RTSPREQ_RECEIVE &&
218 data->req.eos_written) {
219 failf(data, "Server prematurely closed the RTSP connection.");
220 return CURLE_RECV_ERROR;
221 }
222 }
223
224 return httpStatus;
225 }
226
rtsp_do(struct Curl_easy * data,bool * done)227 static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
228 {
229 struct connectdata *conn = data->conn;
230 CURLcode result = CURLE_OK;
231 Curl_RtspReq rtspreq = data->set.rtspreq;
232 struct RTSP *rtsp = data->req.p.rtsp;
233 struct dynbuf req_buffer;
234 unsigned char httpversion = 11; /* RTSP is close to HTTP/1.1, sort of... */
235
236 const char *p_request = NULL;
237 const char *p_session_id = NULL;
238 const char *p_accept = NULL;
239 const char *p_accept_encoding = NULL;
240 const char *p_range = NULL;
241 const char *p_referrer = NULL;
242 const char *p_stream_uri = NULL;
243 const char *p_transport = NULL;
244 const char *p_uagent = NULL;
245 const char *p_proxyuserpwd = NULL;
246 const char *p_userpwd = NULL;
247
248 *done = TRUE;
249 /* Initialize a dynamic send buffer */
250 Curl_dyn_init(&req_buffer, DYN_RTSP_REQ_HEADER);
251
252 rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq;
253 rtsp->CSeq_recv = 0;
254
255 /* Setup the first_* fields to allow auth details get sent
256 to this origin */
257
258 if(!data->state.first_host) {
259 data->state.first_host = strdup(conn->host.name);
260 if(!data->state.first_host)
261 return CURLE_OUT_OF_MEMORY;
262
263 data->state.first_remote_port = conn->remote_port;
264 data->state.first_remote_protocol = conn->handler->protocol;
265 }
266
267 /* Setup the 'p_request' pointer to the proper p_request string
268 * Since all RTSP requests are included here, there is no need to
269 * support custom requests like HTTP.
270 **/
271 data->req.no_body = TRUE; /* most requests do not contain a body */
272 switch(rtspreq) {
273 default:
274 failf(data, "Got invalid RTSP request");
275 return CURLE_BAD_FUNCTION_ARGUMENT;
276 case RTSPREQ_OPTIONS:
277 p_request = "OPTIONS";
278 break;
279 case RTSPREQ_DESCRIBE:
280 p_request = "DESCRIBE";
281 data->req.no_body = FALSE;
282 break;
283 case RTSPREQ_ANNOUNCE:
284 p_request = "ANNOUNCE";
285 break;
286 case RTSPREQ_SETUP:
287 p_request = "SETUP";
288 break;
289 case RTSPREQ_PLAY:
290 p_request = "PLAY";
291 break;
292 case RTSPREQ_PAUSE:
293 p_request = "PAUSE";
294 break;
295 case RTSPREQ_TEARDOWN:
296 p_request = "TEARDOWN";
297 break;
298 case RTSPREQ_GET_PARAMETER:
299 /* GET_PARAMETER's no_body status is determined later */
300 p_request = "GET_PARAMETER";
301 data->req.no_body = FALSE;
302 break;
303 case RTSPREQ_SET_PARAMETER:
304 p_request = "SET_PARAMETER";
305 break;
306 case RTSPREQ_RECORD:
307 p_request = "RECORD";
308 break;
309 case RTSPREQ_RECEIVE:
310 p_request = "";
311 /* Treat interleaved RTP as body */
312 data->req.no_body = FALSE;
313 break;
314 case RTSPREQ_LAST:
315 failf(data, "Got invalid RTSP request: RTSPREQ_LAST");
316 return CURLE_BAD_FUNCTION_ARGUMENT;
317 }
318
319 if(rtspreq == RTSPREQ_RECEIVE) {
320 Curl_xfer_setup1(data, CURL_XFER_RECV, -1, TRUE);
321 goto out;
322 }
323
324 p_session_id = data->set.str[STRING_RTSP_SESSION_ID];
325 if(!p_session_id &&
326 (rtspreq & ~(Curl_RtspReq)(RTSPREQ_OPTIONS |
327 RTSPREQ_DESCRIBE |
328 RTSPREQ_SETUP))) {
329 failf(data, "Refusing to issue an RTSP request [%s] without a session ID.",
330 p_request);
331 result = CURLE_BAD_FUNCTION_ARGUMENT;
332 goto out;
333 }
334
335 /* Stream URI. Default to server '*' if not specified */
336 if(data->set.str[STRING_RTSP_STREAM_URI]) {
337 p_stream_uri = data->set.str[STRING_RTSP_STREAM_URI];
338 }
339 else {
340 p_stream_uri = "*";
341 }
342
343 /* Transport Header for SETUP requests */
344 p_transport = Curl_checkheaders(data, STRCONST("Transport"));
345 if(rtspreq == RTSPREQ_SETUP && !p_transport) {
346 /* New Transport: setting? */
347 if(data->set.str[STRING_RTSP_TRANSPORT]) {
348 Curl_safefree(data->state.aptr.rtsp_transport);
349
350 data->state.aptr.rtsp_transport =
351 aprintf("Transport: %s\r\n",
352 data->set.str[STRING_RTSP_TRANSPORT]);
353 if(!data->state.aptr.rtsp_transport)
354 return CURLE_OUT_OF_MEMORY;
355 }
356 else {
357 failf(data,
358 "Refusing to issue an RTSP SETUP without a Transport: header.");
359 result = CURLE_BAD_FUNCTION_ARGUMENT;
360 goto out;
361 }
362
363 p_transport = data->state.aptr.rtsp_transport;
364 }
365
366 /* Accept Headers for DESCRIBE requests */
367 if(rtspreq == RTSPREQ_DESCRIBE) {
368 /* Accept Header */
369 p_accept = Curl_checkheaders(data, STRCONST("Accept")) ?
370 NULL : "Accept: application/sdp\r\n";
371
372 /* Accept-Encoding header */
373 if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) &&
374 data->set.str[STRING_ENCODING]) {
375 Curl_safefree(data->state.aptr.accept_encoding);
376 data->state.aptr.accept_encoding =
377 aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]);
378
379 if(!data->state.aptr.accept_encoding) {
380 result = CURLE_OUT_OF_MEMORY;
381 goto out;
382 }
383 p_accept_encoding = data->state.aptr.accept_encoding;
384 }
385 }
386
387 /* The User-Agent string might have been allocated in url.c already, because
388 it might have been used in the proxy connect, but if we have got a header
389 with the user-agent string specified, we erase the previously made string
390 here. */
391 if(Curl_checkheaders(data, STRCONST("User-Agent")) &&
392 data->state.aptr.uagent) {
393 Curl_safefree(data->state.aptr.uagent);
394 }
395 else if(!Curl_checkheaders(data, STRCONST("User-Agent")) &&
396 data->set.str[STRING_USERAGENT]) {
397 p_uagent = data->state.aptr.uagent;
398 }
399
400 /* setup the authentication headers */
401 result = Curl_http_output_auth(data, conn, p_request, HTTPREQ_GET,
402 p_stream_uri, FALSE);
403 if(result)
404 goto out;
405
406 #ifndef CURL_DISABLE_PROXY
407 p_proxyuserpwd = data->state.aptr.proxyuserpwd;
408 #endif
409 p_userpwd = data->state.aptr.userpwd;
410
411 /* Referrer */
412 Curl_safefree(data->state.aptr.ref);
413 if(data->state.referer && !Curl_checkheaders(data, STRCONST("Referer")))
414 data->state.aptr.ref = aprintf("Referer: %s\r\n", data->state.referer);
415
416 p_referrer = data->state.aptr.ref;
417
418 /*
419 * Range Header
420 * Only applies to PLAY, PAUSE, RECORD
421 *
422 * Go ahead and use the Range stuff supplied for HTTP
423 */
424 if(data->state.use_range &&
425 (rtspreq & (RTSPREQ_PLAY | RTSPREQ_PAUSE | RTSPREQ_RECORD))) {
426
427 /* Check to see if there is a range set in the custom headers */
428 if(!Curl_checkheaders(data, STRCONST("Range")) && data->state.range) {
429 Curl_safefree(data->state.aptr.rangeline);
430 data->state.aptr.rangeline = aprintf("Range: %s\r\n", data->state.range);
431 p_range = data->state.aptr.rangeline;
432 }
433 }
434
435 /*
436 * Sanity check the custom headers
437 */
438 if(Curl_checkheaders(data, STRCONST("CSeq"))) {
439 failf(data, "CSeq cannot be set as a custom header.");
440 result = CURLE_RTSP_CSEQ_ERROR;
441 goto out;
442 }
443 if(Curl_checkheaders(data, STRCONST("Session"))) {
444 failf(data, "Session ID cannot be set as a custom header.");
445 result = CURLE_BAD_FUNCTION_ARGUMENT;
446 goto out;
447 }
448
449 result =
450 Curl_dyn_addf(&req_buffer,
451 "%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */
452 "CSeq: %ld\r\n", /* CSeq */
453 p_request, p_stream_uri, rtsp->CSeq_sent);
454 if(result)
455 goto out;
456
457 /*
458 * Rather than do a normal alloc line, keep the session_id unformatted
459 * to make comparison easier
460 */
461 if(p_session_id) {
462 result = Curl_dyn_addf(&req_buffer, "Session: %s\r\n", p_session_id);
463 if(result)
464 goto out;
465 }
466
467 /*
468 * Shared HTTP-like options
469 */
470 result = Curl_dyn_addf(&req_buffer,
471 "%s" /* transport */
472 "%s" /* accept */
473 "%s" /* accept-encoding */
474 "%s" /* range */
475 "%s" /* referrer */
476 "%s" /* user-agent */
477 "%s" /* proxyuserpwd */
478 "%s" /* userpwd */
479 ,
480 p_transport ? p_transport : "",
481 p_accept ? p_accept : "",
482 p_accept_encoding ? p_accept_encoding : "",
483 p_range ? p_range : "",
484 p_referrer ? p_referrer : "",
485 p_uagent ? p_uagent : "",
486 p_proxyuserpwd ? p_proxyuserpwd : "",
487 p_userpwd ? p_userpwd : "");
488
489 /*
490 * Free userpwd now --- cannot reuse this for Negotiate and possibly NTLM
491 * with basic and digest, it will be freed anyway by the next request
492 */
493 Curl_safefree(data->state.aptr.userpwd);
494
495 if(result)
496 goto out;
497
498 if((rtspreq == RTSPREQ_SETUP) || (rtspreq == RTSPREQ_DESCRIBE)) {
499 result = Curl_add_timecondition(data, &req_buffer);
500 if(result)
501 goto out;
502 }
503
504 result = Curl_add_custom_headers(data, FALSE, httpversion, &req_buffer);
505 if(result)
506 goto out;
507
508 if(rtspreq == RTSPREQ_ANNOUNCE ||
509 rtspreq == RTSPREQ_SET_PARAMETER ||
510 rtspreq == RTSPREQ_GET_PARAMETER) {
511 curl_off_t req_clen; /* request content length */
512
513 if(data->state.upload) {
514 req_clen = data->state.infilesize;
515 data->state.httpreq = HTTPREQ_PUT;
516 result = Curl_creader_set_fread(data, req_clen);
517 if(result)
518 goto out;
519 }
520 else {
521 if(data->set.postfields) {
522 size_t plen = strlen(data->set.postfields);
523 req_clen = (curl_off_t)plen;
524 result = Curl_creader_set_buf(data, data->set.postfields, plen);
525 }
526 else if(data->state.infilesize >= 0) {
527 req_clen = data->state.infilesize;
528 result = Curl_creader_set_fread(data, req_clen);
529 }
530 else {
531 req_clen = 0;
532 result = Curl_creader_set_null(data);
533 }
534 if(result)
535 goto out;
536 }
537
538 if(req_clen > 0) {
539 /* As stated in the http comments, it is probably not wise to
540 * actually set a custom Content-Length in the headers */
541 if(!Curl_checkheaders(data, STRCONST("Content-Length"))) {
542 result =
543 Curl_dyn_addf(&req_buffer, "Content-Length: %" FMT_OFF_T"\r\n",
544 req_clen);
545 if(result)
546 goto out;
547 }
548
549 if(rtspreq == RTSPREQ_SET_PARAMETER ||
550 rtspreq == RTSPREQ_GET_PARAMETER) {
551 if(!Curl_checkheaders(data, STRCONST("Content-Type"))) {
552 result = Curl_dyn_addn(&req_buffer,
553 STRCONST("Content-Type: "
554 "text/parameters\r\n"));
555 if(result)
556 goto out;
557 }
558 }
559
560 if(rtspreq == RTSPREQ_ANNOUNCE) {
561 if(!Curl_checkheaders(data, STRCONST("Content-Type"))) {
562 result = Curl_dyn_addn(&req_buffer,
563 STRCONST("Content-Type: "
564 "application/sdp\r\n"));
565 if(result)
566 goto out;
567 }
568 }
569 }
570 else if(rtspreq == RTSPREQ_GET_PARAMETER) {
571 /* Check for an empty GET_PARAMETER (heartbeat) request */
572 data->state.httpreq = HTTPREQ_HEAD;
573 data->req.no_body = TRUE;
574 }
575 }
576 else {
577 result = Curl_creader_set_null(data);
578 if(result)
579 goto out;
580 }
581
582 /* Finish the request buffer */
583 result = Curl_dyn_addn(&req_buffer, STRCONST("\r\n"));
584 if(result)
585 goto out;
586
587 Curl_xfer_setup1(data, CURL_XFER_SENDRECV, -1, TRUE);
588
589 /* issue the request */
590 result = Curl_req_send(data, &req_buffer, httpversion);
591 if(result) {
592 failf(data, "Failed sending RTSP request");
593 goto out;
594 }
595
596 /* Increment the CSeq on success */
597 data->state.rtsp_next_client_CSeq++;
598
599 if(data->req.writebytecount) {
600 /* if a request-body has been sent off, we make sure this progress is
601 noted properly */
602 Curl_pgrsSetUploadCounter(data, data->req.writebytecount);
603 if(Curl_pgrsUpdate(data))
604 result = CURLE_ABORTED_BY_CALLBACK;
605 }
606 out:
607 Curl_dyn_free(&req_buffer);
608 return result;
609 }
610
611 /**
612 * write any BODY bytes missing to the client, ignore the rest.
613 */
rtp_write_body_junk(struct Curl_easy * data,const char * buf,size_t blen)614 static CURLcode rtp_write_body_junk(struct Curl_easy *data,
615 const char *buf,
616 size_t blen)
617 {
618 struct rtsp_conn *rtspc = &(data->conn->proto.rtspc);
619 curl_off_t body_remain;
620 bool in_body;
621
622 in_body = (data->req.headerline && !rtspc->in_header) &&
623 (data->req.size >= 0) &&
624 (data->req.bytecount < data->req.size);
625 body_remain = in_body ? (data->req.size - data->req.bytecount) : 0;
626 DEBUGASSERT(body_remain >= 0);
627 if(body_remain) {
628 if((curl_off_t)blen > body_remain)
629 blen = (size_t)body_remain;
630 return Curl_client_write(data, CLIENTWRITE_BODY, (char *)buf, blen);
631 }
632 return CURLE_OK;
633 }
634
rtsp_filter_rtp(struct Curl_easy * data,const char * buf,size_t blen,size_t * pconsumed)635 static CURLcode rtsp_filter_rtp(struct Curl_easy *data,
636 const char *buf,
637 size_t blen,
638 size_t *pconsumed)
639 {
640 struct rtsp_conn *rtspc = &(data->conn->proto.rtspc);
641 CURLcode result = CURLE_OK;
642 size_t skip_len = 0;
643
644 *pconsumed = 0;
645 while(blen) {
646 bool in_body = (data->req.headerline && !rtspc->in_header) &&
647 (data->req.size >= 0) &&
648 (data->req.bytecount < data->req.size);
649 switch(rtspc->state) {
650
651 case RTP_PARSE_SKIP: {
652 DEBUGASSERT(Curl_dyn_len(&rtspc->buf) == 0);
653 while(blen && buf[0] != '$') {
654 if(!in_body && buf[0] == 'R' &&
655 data->set.rtspreq != RTSPREQ_RECEIVE) {
656 if(strncmp(buf, "RTSP/", (blen < 5) ? blen : 5) == 0) {
657 /* This could be the next response, no consume and return */
658 if(*pconsumed) {
659 DEBUGF(infof(data, "RTP rtsp_filter_rtp[SKIP] RTSP/ prefix, "
660 "skipping %zd bytes of junk", *pconsumed));
661 }
662 rtspc->state = RTP_PARSE_SKIP;
663 rtspc->in_header = TRUE;
664 goto out;
665 }
666 }
667 /* junk/BODY, consume without buffering */
668 *pconsumed += 1;
669 ++buf;
670 --blen;
671 ++skip_len;
672 }
673 if(blen && buf[0] == '$') {
674 /* possible start of an RTP message, buffer */
675 if(skip_len) {
676 /* end of junk/BODY bytes, flush */
677 result = rtp_write_body_junk(data,
678 (char *)(buf - skip_len), skip_len);
679 skip_len = 0;
680 if(result)
681 goto out;
682 }
683 if(Curl_dyn_addn(&rtspc->buf, buf, 1)) {
684 result = CURLE_OUT_OF_MEMORY;
685 goto out;
686 }
687 *pconsumed += 1;
688 ++buf;
689 --blen;
690 rtspc->state = RTP_PARSE_CHANNEL;
691 }
692 break;
693 }
694
695 case RTP_PARSE_CHANNEL: {
696 int idx = ((unsigned char)buf[0]) / 8;
697 int off = ((unsigned char)buf[0]) % 8;
698 DEBUGASSERT(Curl_dyn_len(&rtspc->buf) == 1);
699 if(!(data->state.rtp_channel_mask[idx] & (1 << off))) {
700 /* invalid channel number, junk or BODY data */
701 rtspc->state = RTP_PARSE_SKIP;
702 DEBUGASSERT(skip_len == 0);
703 /* we do not consume this byte, it is BODY data */
704 DEBUGF(infof(data, "RTSP: invalid RTP channel %d, skipping", idx));
705 if(*pconsumed == 0) {
706 /* We did not consume the initial '$' in our buffer, but had
707 * it from an earlier call. We cannot un-consume it and have
708 * to write it directly as BODY data */
709 result = rtp_write_body_junk(data, Curl_dyn_ptr(&rtspc->buf), 1);
710 if(result)
711 goto out;
712 }
713 else {
714 /* count the '$' as skip and continue */
715 skip_len = 1;
716 }
717 Curl_dyn_free(&rtspc->buf);
718 break;
719 }
720 /* a valid channel, so we expect this to be a real RTP message */
721 rtspc->rtp_channel = (unsigned char)buf[0];
722 if(Curl_dyn_addn(&rtspc->buf, buf, 1)) {
723 result = CURLE_OUT_OF_MEMORY;
724 goto out;
725 }
726 *pconsumed += 1;
727 ++buf;
728 --blen;
729 rtspc->state = RTP_PARSE_LEN;
730 break;
731 }
732
733 case RTP_PARSE_LEN: {
734 size_t rtp_len = Curl_dyn_len(&rtspc->buf);
735 const char *rtp_buf;
736 DEBUGASSERT(rtp_len >= 2 && rtp_len < 4);
737 if(Curl_dyn_addn(&rtspc->buf, buf, 1)) {
738 result = CURLE_OUT_OF_MEMORY;
739 goto out;
740 }
741 *pconsumed += 1;
742 ++buf;
743 --blen;
744 if(rtp_len == 2)
745 break;
746 rtp_buf = Curl_dyn_ptr(&rtspc->buf);
747 rtspc->rtp_len = RTP_PKT_LENGTH(rtp_buf) + 4;
748 rtspc->state = RTP_PARSE_DATA;
749 break;
750 }
751
752 case RTP_PARSE_DATA: {
753 size_t rtp_len = Curl_dyn_len(&rtspc->buf);
754 size_t needed;
755 DEBUGASSERT(rtp_len < rtspc->rtp_len);
756 needed = rtspc->rtp_len - rtp_len;
757 if(needed <= blen) {
758 if(Curl_dyn_addn(&rtspc->buf, buf, needed)) {
759 result = CURLE_OUT_OF_MEMORY;
760 goto out;
761 }
762 *pconsumed += needed;
763 buf += needed;
764 blen -= needed;
765 /* complete RTP message in buffer */
766 DEBUGF(infof(data, "RTP write channel %d rtp_len %zu",
767 rtspc->rtp_channel, rtspc->rtp_len));
768 result = rtp_client_write(data, Curl_dyn_ptr(&rtspc->buf),
769 rtspc->rtp_len);
770 Curl_dyn_free(&rtspc->buf);
771 rtspc->state = RTP_PARSE_SKIP;
772 if(result)
773 goto out;
774 }
775 else {
776 if(Curl_dyn_addn(&rtspc->buf, buf, blen)) {
777 result = CURLE_OUT_OF_MEMORY;
778 goto out;
779 }
780 *pconsumed += blen;
781 buf += blen;
782 blen = 0;
783 }
784 break;
785 }
786
787 default:
788 DEBUGASSERT(0);
789 return CURLE_RECV_ERROR;
790 }
791 }
792 out:
793 if(!result && skip_len)
794 result = rtp_write_body_junk(data, (char *)(buf - skip_len), skip_len);
795 return result;
796 }
797
rtsp_rtp_write_resp(struct Curl_easy * data,const char * buf,size_t blen,bool is_eos)798 static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data,
799 const char *buf,
800 size_t blen,
801 bool is_eos)
802 {
803 struct rtsp_conn *rtspc = &(data->conn->proto.rtspc);
804 CURLcode result = CURLE_OK;
805 size_t consumed = 0;
806
807 if(!data->req.header)
808 rtspc->in_header = FALSE;
809 if(!blen) {
810 goto out;
811 }
812
813 DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, eos=%d)",
814 blen, rtspc->in_header, is_eos));
815
816 /* If header parsing is not ongoing, extract RTP messages */
817 if(!rtspc->in_header) {
818 result = rtsp_filter_rtp(data, buf, blen, &consumed);
819 if(result)
820 goto out;
821 buf += consumed;
822 blen -= consumed;
823 /* either we consumed all or are at the start of header parsing */
824 if(blen && !data->req.header)
825 DEBUGF(infof(data, "RTSP: %zu bytes, possibly excess in response body",
826 blen));
827 }
828
829 /* we want to parse headers, do so */
830 if(data->req.header && blen) {
831 rtspc->in_header = TRUE;
832 result = Curl_http_write_resp_hds(data, buf, blen, &consumed);
833 if(result)
834 goto out;
835
836 buf += consumed;
837 blen -= consumed;
838
839 if(!data->req.header)
840 rtspc->in_header = FALSE;
841
842 if(!rtspc->in_header) {
843 /* If header parsing is done, extract interleaved RTP messages */
844 if(data->req.size <= -1) {
845 /* Respect section 4.4 of rfc2326: If the Content-Length header is
846 absent, a length 0 must be assumed. */
847 data->req.size = 0;
848 data->req.download_done = TRUE;
849 }
850 result = rtsp_filter_rtp(data, buf, blen, &consumed);
851 if(result)
852 goto out;
853 blen -= consumed;
854 }
855 }
856
857 if(rtspc->state != RTP_PARSE_SKIP)
858 data->req.done = FALSE;
859 /* we SHOULD have consumed all bytes, unless the response is borked.
860 * In which case we write out the left over bytes, letting the client
861 * writer deal with it (it will report EXCESS and fail the transfer). */
862 DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, done=%d "
863 " rtspc->state=%d, req.size=%" FMT_OFF_T ")",
864 blen, rtspc->in_header, data->req.done, rtspc->state,
865 data->req.size));
866 if(!result && (is_eos || blen)) {
867 result = Curl_client_write(data, CLIENTWRITE_BODY|
868 (is_eos ? CLIENTWRITE_EOS : 0),
869 (char *)buf, blen);
870 }
871
872 out:
873 if((data->set.rtspreq == RTSPREQ_RECEIVE) &&
874 (rtspc->state == RTP_PARSE_SKIP)) {
875 /* In special mode RECEIVE, we just process one chunk of network
876 * data, so we stop the transfer here, if we have no incomplete
877 * RTP message pending. */
878 data->req.download_done = TRUE;
879 }
880 return result;
881 }
882
883 static
rtp_client_write(struct Curl_easy * data,const char * ptr,size_t len)884 CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len)
885 {
886 size_t wrote;
887 curl_write_callback writeit;
888 void *user_ptr;
889
890 if(len == 0) {
891 failf(data, "Cannot write a 0 size RTP packet.");
892 return CURLE_WRITE_ERROR;
893 }
894
895 /* If the user has configured CURLOPT_INTERLEAVEFUNCTION then use that
896 function and any configured CURLOPT_INTERLEAVEDATA to write out the RTP
897 data. Otherwise, use the CURLOPT_WRITEFUNCTION with the CURLOPT_WRITEDATA
898 pointer to write out the RTP data. */
899 if(data->set.fwrite_rtp) {
900 writeit = data->set.fwrite_rtp;
901 user_ptr = data->set.rtp_out;
902 }
903 else {
904 writeit = data->set.fwrite_func;
905 user_ptr = data->set.out;
906 }
907
908 Curl_set_in_callback(data, TRUE);
909 wrote = writeit((char *)ptr, 1, len, user_ptr);
910 Curl_set_in_callback(data, FALSE);
911
912 if(CURL_WRITEFUNC_PAUSE == wrote) {
913 failf(data, "Cannot pause RTP");
914 return CURLE_WRITE_ERROR;
915 }
916
917 if(wrote != len) {
918 failf(data, "Failed writing RTP data");
919 return CURLE_WRITE_ERROR;
920 }
921
922 return CURLE_OK;
923 }
924
Curl_rtsp_parseheader(struct Curl_easy * data,const char * header)925 CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header)
926 {
927 if(checkprefix("CSeq:", header)) {
928 long CSeq = 0;
929 char *endp;
930 const char *p = &header[5];
931 while(ISBLANK(*p))
932 p++;
933 CSeq = strtol(p, &endp, 10);
934 if(p != endp) {
935 struct RTSP *rtsp = data->req.p.rtsp;
936 rtsp->CSeq_recv = CSeq; /* mark the request */
937 data->state.rtsp_CSeq_recv = CSeq; /* update the handle */
938 }
939 else {
940 failf(data, "Unable to read the CSeq header: [%s]", header);
941 return CURLE_RTSP_CSEQ_ERROR;
942 }
943 }
944 else if(checkprefix("Session:", header)) {
945 const char *start, *end;
946 size_t idlen;
947
948 /* Find the first non-space letter */
949 start = header + 8;
950 while(*start && ISBLANK(*start))
951 start++;
952
953 if(!*start) {
954 failf(data, "Got a blank Session ID");
955 return CURLE_RTSP_SESSION_ERROR;
956 }
957
958 /* Find the end of Session ID
959 *
960 * Allow any non whitespace content, up to the field separator or end of
961 * line. RFC 2326 is not 100% clear on the session ID and for example
962 * gstreamer does url-encoded session ID's not covered by the standard.
963 */
964 end = start;
965 while(*end && *end != ';' && !ISSPACE(*end))
966 end++;
967 idlen = end - start;
968
969 if(data->set.str[STRING_RTSP_SESSION_ID]) {
970
971 /* If the Session ID is set, then compare */
972 if(strlen(data->set.str[STRING_RTSP_SESSION_ID]) != idlen ||
973 strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], idlen)) {
974 failf(data, "Got RTSP Session ID Line [%s], but wanted ID [%s]",
975 start, data->set.str[STRING_RTSP_SESSION_ID]);
976 return CURLE_RTSP_SESSION_ERROR;
977 }
978 }
979 else {
980 /* If the Session ID is not set, and we find it in a response, then set
981 * it.
982 */
983
984 /* Copy the id substring into a new buffer */
985 data->set.str[STRING_RTSP_SESSION_ID] = Curl_memdup0(start, idlen);
986 if(!data->set.str[STRING_RTSP_SESSION_ID])
987 return CURLE_OUT_OF_MEMORY;
988 }
989 }
990 else if(checkprefix("Transport:", header)) {
991 CURLcode result;
992 result = rtsp_parse_transport(data, header + 10);
993 if(result)
994 return result;
995 }
996 return CURLE_OK;
997 }
998
999 static
rtsp_parse_transport(struct Curl_easy * data,const char * transport)1000 CURLcode rtsp_parse_transport(struct Curl_easy *data, const char *transport)
1001 {
1002 /* If we receive multiple Transport response-headers, the linterleaved
1003 channels of each response header is recorded and used together for
1004 subsequent data validity checks.*/
1005 /* e.g.: ' RTP/AVP/TCP;unicast;interleaved=5-6' */
1006 const char *start, *end;
1007 start = transport;
1008 while(start && *start) {
1009 while(*start && ISBLANK(*start) )
1010 start++;
1011 end = strchr(start, ';');
1012 if(checkprefix("interleaved=", start)) {
1013 long chan1, chan2, chan;
1014 char *endp;
1015 const char *p = start + 12;
1016 chan1 = strtol(p, &endp, 10);
1017 if(p != endp && chan1 >= 0 && chan1 <= 255) {
1018 unsigned char *rtp_channel_mask = data->state.rtp_channel_mask;
1019 chan2 = chan1;
1020 if(*endp == '-') {
1021 p = endp + 1;
1022 chan2 = strtol(p, &endp, 10);
1023 if(p == endp || chan2 < 0 || chan2 > 255) {
1024 infof(data, "Unable to read the interleaved parameter from "
1025 "Transport header: [%s]", transport);
1026 chan2 = chan1;
1027 }
1028 }
1029 for(chan = chan1; chan <= chan2; chan++) {
1030 long idx = chan / 8;
1031 long off = chan % 8;
1032 rtp_channel_mask[idx] |= (unsigned char)(1 << off);
1033 }
1034 }
1035 else {
1036 infof(data, "Unable to read the interleaved parameter from "
1037 "Transport header: [%s]", transport);
1038 }
1039 break;
1040 }
1041 /* skip to next parameter */
1042 start = (!end) ? end : (end + 1);
1043 }
1044 return CURLE_OK;
1045 }
1046
1047
1048 #endif /* CURL_DISABLE_RTSP */
1049