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