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