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