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