1/*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 1998 - 2016, 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.haxx.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 ***************************************************************************/ 22 23#include "curl_setup.h" 24 25#ifndef CURL_DISABLE_RTSP 26 27#include "urldata.h" 28#include <curl/curl.h> 29#include "transfer.h" 30#include "sendf.h" 31#include "multiif.h" 32#include "http.h" 33#include "url.h" 34#include "progress.h" 35#include "rtsp.h" 36#include "rawstr.h" 37#include "select.h" 38#include "connect.h" 39/* The last 3 #include files should be in this order */ 40#include "curl_printf.h" 41#include "curl_memory.h" 42#include "memdebug.h" 43 44/* 45 * TODO (general) 46 * -incoming server requests 47 * -server CSeq counter 48 * -digest authentication 49 * -connect thru proxy 50 * -pipelining? 51 */ 52 53 54#define RTP_PKT_CHANNEL(p) ((int)((unsigned char)((p)[1]))) 55 56#define RTP_PKT_LENGTH(p) ((((int)((unsigned char)((p)[2]))) << 8) | \ 57 ((int)((unsigned char)((p)[3])))) 58 59/* protocol-specific functions set up to be called by the main engine */ 60static CURLcode rtsp_do(struct connectdata *conn, bool *done); 61static CURLcode rtsp_done(struct connectdata *conn, CURLcode, bool premature); 62static CURLcode rtsp_connect(struct connectdata *conn, bool *done); 63static CURLcode rtsp_disconnect(struct connectdata *conn, bool dead); 64 65static int rtsp_getsock_do(struct connectdata *conn, 66 curl_socket_t *socks, 67 int numsocks); 68 69/* 70 * Parse and write out any available RTP data. 71 * 72 * nread: amount of data left after k->str. will be modified if RTP 73 * data is parsed and k->str is moved up 74 * readmore: whether or not the RTP parser needs more data right away 75 */ 76static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data, 77 struct connectdata *conn, 78 ssize_t *nread, 79 bool *readmore); 80 81static CURLcode rtsp_setup_connection(struct connectdata *conn); 82 83 84/* this returns the socket to wait for in the DO and DOING state for the multi 85 interface and then we're always _sending_ a request and thus we wait for 86 the single socket to become writable only */ 87static int rtsp_getsock_do(struct connectdata *conn, 88 curl_socket_t *socks, 89 int numsocks) 90{ 91 /* write mode */ 92 (void)numsocks; /* unused, we trust it to be at least 1 */ 93 socks[0] = conn->sock[FIRSTSOCKET]; 94 return GETSOCK_WRITESOCK(0); 95} 96 97static 98CURLcode rtp_client_write(struct connectdata *conn, char *ptr, size_t len); 99 100 101/* 102 * RTSP handler interface. 103 */ 104const struct Curl_handler Curl_handler_rtsp = { 105 "RTSP", /* scheme */ 106 rtsp_setup_connection, /* setup_connection */ 107 rtsp_do, /* do_it */ 108 rtsp_done, /* done */ 109 ZERO_NULL, /* do_more */ 110 rtsp_connect, /* connect_it */ 111 ZERO_NULL, /* connecting */ 112 ZERO_NULL, /* doing */ 113 ZERO_NULL, /* proto_getsock */ 114 rtsp_getsock_do, /* doing_getsock */ 115 ZERO_NULL, /* domore_getsock */ 116 ZERO_NULL, /* perform_getsock */ 117 rtsp_disconnect, /* disconnect */ 118 rtsp_rtp_readwrite, /* readwrite */ 119 PORT_RTSP, /* defport */ 120 CURLPROTO_RTSP, /* protocol */ 121 PROTOPT_NONE /* flags */ 122}; 123 124 125static CURLcode rtsp_setup_connection(struct connectdata *conn) 126{ 127 struct RTSP *rtsp; 128 129 conn->data->req.protop = rtsp = calloc(1, sizeof(struct RTSP)); 130 if(!rtsp) 131 return CURLE_OUT_OF_MEMORY; 132 133 return CURLE_OK; 134} 135 136 137/* 138 * The server may send us RTP data at any point, and RTSPREQ_RECEIVE does not 139 * want to block the application forever while receiving a stream. Therefore, 140 * we cannot assume that an RTSP socket is dead just because it is readable. 141 * 142 * Instead, if it is readable, run Curl_getconnectinfo() to peek at the socket 143 * and distinguish between closed and data. 144 */ 145bool Curl_rtsp_connisdead(struct connectdata *check) 146{ 147 int sval; 148 bool ret_val = TRUE; 149 150 sval = Curl_socket_ready(check->sock[FIRSTSOCKET], CURL_SOCKET_BAD, 0); 151 if(sval == 0) { 152 /* timeout */ 153 ret_val = FALSE; 154 } 155 else if(sval & CURL_CSELECT_ERR) { 156 /* socket is in an error state */ 157 ret_val = TRUE; 158 } 159 else if((sval & CURL_CSELECT_IN) && check->data) { 160 /* readable with no error. could be closed or could be alive but we can 161 only check if we have a proper Curl_easy for the connection */ 162 curl_socket_t connectinfo = Curl_getconnectinfo(check->data, &check); 163 if(connectinfo != CURL_SOCKET_BAD) 164 ret_val = FALSE; 165 } 166 167 return ret_val; 168} 169 170static CURLcode rtsp_connect(struct connectdata *conn, bool *done) 171{ 172 CURLcode httpStatus; 173 struct Curl_easy *data = conn->data; 174 175 httpStatus = Curl_http_connect(conn, done); 176 177 /* Initialize the CSeq if not already done */ 178 if(data->state.rtsp_next_client_CSeq == 0) 179 data->state.rtsp_next_client_CSeq = 1; 180 if(data->state.rtsp_next_server_CSeq == 0) 181 data->state.rtsp_next_server_CSeq = 1; 182 183 conn->proto.rtspc.rtp_channel = -1; 184 185 return httpStatus; 186} 187 188static CURLcode rtsp_disconnect(struct connectdata *conn, bool dead) 189{ 190 (void) dead; 191 Curl_safefree(conn->proto.rtspc.rtp_buf); 192 return CURLE_OK; 193} 194 195 196static CURLcode rtsp_done(struct connectdata *conn, 197 CURLcode status, bool premature) 198{ 199 struct Curl_easy *data = conn->data; 200 struct RTSP *rtsp = data->req.protop; 201 CURLcode httpStatus; 202 long CSeq_sent; 203 long CSeq_recv; 204 205 /* Bypass HTTP empty-reply checks on receive */ 206 if(data->set.rtspreq == RTSPREQ_RECEIVE) 207 premature = TRUE; 208 209 httpStatus = Curl_http_done(conn, status, premature); 210 211 if(rtsp) { 212 /* Check the sequence numbers */ 213 CSeq_sent = rtsp->CSeq_sent; 214 CSeq_recv = rtsp->CSeq_recv; 215 if((data->set.rtspreq != RTSPREQ_RECEIVE) && (CSeq_sent != CSeq_recv)) { 216 failf(data, 217 "The CSeq of this request %ld did not match the response %ld", 218 CSeq_sent, CSeq_recv); 219 return CURLE_RTSP_CSEQ_ERROR; 220 } 221 else if(data->set.rtspreq == RTSPREQ_RECEIVE && 222 (conn->proto.rtspc.rtp_channel == -1)) { 223 infof(data, "Got an RTP Receive with a CSeq of %ld\n", CSeq_recv); 224 /* TODO CPC: Server -> Client logic here */ 225 } 226 } 227 228 return httpStatus; 229} 230 231static CURLcode rtsp_do(struct connectdata *conn, bool *done) 232{ 233 struct Curl_easy *data = conn->data; 234 CURLcode result=CURLE_OK; 235 Curl_RtspReq rtspreq = data->set.rtspreq; 236 struct RTSP *rtsp = data->req.protop; 237 struct HTTP *http; 238 Curl_send_buffer *req_buffer; 239 curl_off_t postsize = 0; /* for ANNOUNCE and SET_PARAMETER */ 240 curl_off_t putsize = 0; /* for ANNOUNCE and SET_PARAMETER */ 241 242 const char *p_request = NULL; 243 const char *p_session_id = NULL; 244 const char *p_accept = NULL; 245 const char *p_accept_encoding = NULL; 246 const char *p_range = NULL; 247 const char *p_referrer = NULL; 248 const char *p_stream_uri = NULL; 249 const char *p_transport = NULL; 250 const char *p_uagent = NULL; 251 const char *p_proxyuserpwd = NULL; 252 const char *p_userpwd = NULL; 253 254 *done = TRUE; 255 256 http = &(rtsp->http_wrapper); 257 /* Assert that no one has changed the RTSP struct in an evil way */ 258 DEBUGASSERT((void *)http == (void *)rtsp); 259 260 rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq; 261 rtsp->CSeq_recv = 0; 262 263 /* Setup the 'p_request' pointer to the proper p_request string 264 * Since all RTSP requests are included here, there is no need to 265 * support custom requests like HTTP. 266 **/ 267 data->set.opt_no_body = TRUE; /* most requests don't contain a body */ 268 switch(rtspreq) { 269 default: 270 failf(data, "Got invalid RTSP request"); 271 return CURLE_BAD_FUNCTION_ARGUMENT; 272 case RTSPREQ_OPTIONS: 273 p_request = "OPTIONS"; 274 break; 275 case RTSPREQ_DESCRIBE: 276 p_request = "DESCRIBE"; 277 data->set.opt_no_body = FALSE; 278 break; 279 case RTSPREQ_ANNOUNCE: 280 p_request = "ANNOUNCE"; 281 break; 282 case RTSPREQ_SETUP: 283 p_request = "SETUP"; 284 break; 285 case RTSPREQ_PLAY: 286 p_request = "PLAY"; 287 break; 288 case RTSPREQ_PAUSE: 289 p_request = "PAUSE"; 290 break; 291 case RTSPREQ_TEARDOWN: 292 p_request = "TEARDOWN"; 293 break; 294 case RTSPREQ_GET_PARAMETER: 295 /* GET_PARAMETER's no_body status is determined later */ 296 p_request = "GET_PARAMETER"; 297 data->set.opt_no_body = FALSE; 298 break; 299 case RTSPREQ_SET_PARAMETER: 300 p_request = "SET_PARAMETER"; 301 break; 302 case RTSPREQ_RECORD: 303 p_request = "RECORD"; 304 break; 305 case RTSPREQ_RECEIVE: 306 p_request = ""; 307 /* Treat interleaved RTP as body*/ 308 data->set.opt_no_body = FALSE; 309 break; 310 case RTSPREQ_LAST: 311 failf(data, "Got invalid RTSP request: RTSPREQ_LAST"); 312 return CURLE_BAD_FUNCTION_ARGUMENT; 313 } 314 315 if(rtspreq == RTSPREQ_RECEIVE) { 316 Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, 317 &http->readbytecount, -1, NULL); 318 319 return result; 320 } 321 322 p_session_id = data->set.str[STRING_RTSP_SESSION_ID]; 323 if(!p_session_id && 324 (rtspreq & ~(RTSPREQ_OPTIONS | RTSPREQ_DESCRIBE | RTSPREQ_SETUP))) { 325 failf(data, "Refusing to issue an RTSP request [%s] without a session ID.", 326 p_request); 327 return CURLE_BAD_FUNCTION_ARGUMENT; 328 } 329 330 /* TODO: proxy? */ 331 332 /* Stream URI. Default to server '*' if not specified */ 333 if(data->set.str[STRING_RTSP_STREAM_URI]) { 334 p_stream_uri = data->set.str[STRING_RTSP_STREAM_URI]; 335 } 336 else { 337 p_stream_uri = "*"; 338 } 339 340 /* Transport Header for SETUP requests */ 341 p_transport = Curl_checkheaders(conn, "Transport:"); 342 if(rtspreq == RTSPREQ_SETUP && !p_transport) { 343 /* New Transport: setting? */ 344 if(data->set.str[STRING_RTSP_TRANSPORT]) { 345 Curl_safefree(conn->allocptr.rtsp_transport); 346 347 conn->allocptr.rtsp_transport = 348 aprintf("Transport: %s\r\n", 349 data->set.str[STRING_RTSP_TRANSPORT]); 350 if(!conn->allocptr.rtsp_transport) 351 return CURLE_OUT_OF_MEMORY; 352 } 353 else { 354 failf(data, 355 "Refusing to issue an RTSP SETUP without a Transport: header."); 356 return CURLE_BAD_FUNCTION_ARGUMENT; 357 } 358 359 p_transport = conn->allocptr.rtsp_transport; 360 } 361 362 /* Accept Headers for DESCRIBE requests */ 363 if(rtspreq == RTSPREQ_DESCRIBE) { 364 /* Accept Header */ 365 p_accept = Curl_checkheaders(conn, "Accept:")? 366 NULL:"Accept: application/sdp\r\n"; 367 368 /* Accept-Encoding header */ 369 if(!Curl_checkheaders(conn, "Accept-Encoding:") && 370 data->set.str[STRING_ENCODING]) { 371 Curl_safefree(conn->allocptr.accept_encoding); 372 conn->allocptr.accept_encoding = 373 aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); 374 375 if(!conn->allocptr.accept_encoding) 376 return CURLE_OUT_OF_MEMORY; 377 378 p_accept_encoding = conn->allocptr.accept_encoding; 379 } 380 } 381 382 /* The User-Agent string might have been allocated in url.c already, because 383 it might have been used in the proxy connect, but if we have got a header 384 with the user-agent string specified, we erase the previously made string 385 here. */ 386 if(Curl_checkheaders(conn, "User-Agent:") && conn->allocptr.uagent) { 387 Curl_safefree(conn->allocptr.uagent); 388 conn->allocptr.uagent = NULL; 389 } 390 else if(!Curl_checkheaders(conn, "User-Agent:") && 391 data->set.str[STRING_USERAGENT]) { 392 p_uagent = conn->allocptr.uagent; 393 } 394 395 /* setup the authentication headers */ 396 result = Curl_http_output_auth(conn, p_request, p_stream_uri, FALSE); 397 if(result) 398 return result; 399 400 p_proxyuserpwd = conn->allocptr.proxyuserpwd; 401 p_userpwd = conn->allocptr.userpwd; 402 403 /* Referrer */ 404 Curl_safefree(conn->allocptr.ref); 405 if(data->change.referer && !Curl_checkheaders(conn, "Referer:")) 406 conn->allocptr.ref = aprintf("Referer: %s\r\n", data->change.referer); 407 else 408 conn->allocptr.ref = NULL; 409 410 p_referrer = conn->allocptr.ref; 411 412 /* 413 * Range Header 414 * Only applies to PLAY, PAUSE, RECORD 415 * 416 * Go ahead and use the Range stuff supplied for HTTP 417 */ 418 if(data->state.use_range && 419 (rtspreq & (RTSPREQ_PLAY | RTSPREQ_PAUSE | RTSPREQ_RECORD))) { 420 421 /* Check to see if there is a range set in the custom headers */ 422 if(!Curl_checkheaders(conn, "Range:") && data->state.range) { 423 Curl_safefree(conn->allocptr.rangeline); 424 conn->allocptr.rangeline = aprintf("Range: %s\r\n", data->state.range); 425 p_range = conn->allocptr.rangeline; 426 } 427 } 428 429 /* 430 * Sanity check the custom headers 431 */ 432 if(Curl_checkheaders(conn, "CSeq:")) { 433 failf(data, "CSeq cannot be set as a custom header."); 434 return CURLE_RTSP_CSEQ_ERROR; 435 } 436 if(Curl_checkheaders(conn, "Session:")) { 437 failf(data, "Session ID cannot be set as a custom header."); 438 return CURLE_BAD_FUNCTION_ARGUMENT; 439 } 440 441 /* Initialize a dynamic send buffer */ 442 req_buffer = Curl_add_buffer_init(); 443 444 if(!req_buffer) 445 return CURLE_OUT_OF_MEMORY; 446 447 result = 448 Curl_add_bufferf(req_buffer, 449 "%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */ 450 "CSeq: %ld\r\n", /* CSeq */ 451 p_request, p_stream_uri, rtsp->CSeq_sent); 452 if(result) 453 return result; 454 455 /* 456 * Rather than do a normal alloc line, keep the session_id unformatted 457 * to make comparison easier 458 */ 459 if(p_session_id) { 460 result = Curl_add_bufferf(req_buffer, "Session: %s\r\n", p_session_id); 461 if(result) 462 return result; 463 } 464 465 /* 466 * Shared HTTP-like options 467 */ 468 result = Curl_add_bufferf(req_buffer, 469 "%s" /* transport */ 470 "%s" /* accept */ 471 "%s" /* accept-encoding */ 472 "%s" /* range */ 473 "%s" /* referrer */ 474 "%s" /* user-agent */ 475 "%s" /* proxyuserpwd */ 476 "%s" /* userpwd */ 477 , 478 p_transport ? p_transport : "", 479 p_accept ? p_accept : "", 480 p_accept_encoding ? p_accept_encoding : "", 481 p_range ? p_range : "", 482 p_referrer ? p_referrer : "", 483 p_uagent ? p_uagent : "", 484 p_proxyuserpwd ? p_proxyuserpwd : "", 485 p_userpwd ? p_userpwd : ""); 486 487 /* 488 * Free userpwd now --- cannot reuse this for Negotiate and possibly NTLM 489 * with basic and digest, it will be freed anyway by the next request 490 */ 491 Curl_safefree (conn->allocptr.userpwd); 492 conn->allocptr.userpwd = NULL; 493 494 if(result) 495 return result; 496 497 if((rtspreq == RTSPREQ_SETUP) || (rtspreq == RTSPREQ_DESCRIBE)) { 498 result = Curl_add_timecondition(data, req_buffer); 499 if(result) 500 return result; 501 } 502 503 result = Curl_add_custom_headers(conn, FALSE, req_buffer); 504 if(result) 505 return result; 506 507 if(rtspreq == RTSPREQ_ANNOUNCE || 508 rtspreq == RTSPREQ_SET_PARAMETER || 509 rtspreq == RTSPREQ_GET_PARAMETER) { 510 511 if(data->set.upload) { 512 putsize = data->state.infilesize; 513 data->set.httpreq = HTTPREQ_PUT; 514 515 } 516 else { 517 postsize = (data->state.infilesize != -1)? 518 data->state.infilesize: 519 (data->set.postfields? (curl_off_t)strlen(data->set.postfields):0); 520 data->set.httpreq = HTTPREQ_POST; 521 } 522 523 if(putsize > 0 || postsize > 0) { 524 /* As stated in the http comments, it is probably not wise to 525 * actually set a custom Content-Length in the headers */ 526 if(!Curl_checkheaders(conn, "Content-Length:")) { 527 result = Curl_add_bufferf(req_buffer, 528 "Content-Length: %" CURL_FORMAT_CURL_OFF_T"\r\n", 529 (data->set.upload ? putsize : postsize)); 530 if(result) 531 return result; 532 } 533 534 if(rtspreq == RTSPREQ_SET_PARAMETER || 535 rtspreq == RTSPREQ_GET_PARAMETER) { 536 if(!Curl_checkheaders(conn, "Content-Type:")) { 537 result = Curl_add_bufferf(req_buffer, 538 "Content-Type: text/parameters\r\n"); 539 if(result) 540 return result; 541 } 542 } 543 544 if(rtspreq == RTSPREQ_ANNOUNCE) { 545 if(!Curl_checkheaders(conn, "Content-Type:")) { 546 result = Curl_add_bufferf(req_buffer, 547 "Content-Type: application/sdp\r\n"); 548 if(result) 549 return result; 550 } 551 } 552 553 data->state.expect100header = FALSE; /* RTSP posts are simple/small */ 554 } 555 else if(rtspreq == RTSPREQ_GET_PARAMETER) { 556 /* Check for an empty GET_PARAMETER (heartbeat) request */ 557 data->set.httpreq = HTTPREQ_HEAD; 558 data->set.opt_no_body = TRUE; 559 } 560 } 561 562 /* RTSP never allows chunked transfer */ 563 data->req.forbidchunk = TRUE; 564 /* Finish the request buffer */ 565 result = Curl_add_buffer(req_buffer, "\r\n", 2); 566 if(result) 567 return result; 568 569 if(postsize > 0) { 570 result = Curl_add_buffer(req_buffer, data->set.postfields, 571 (size_t)postsize); 572 if(result) 573 return result; 574 } 575 576 /* issue the request */ 577 result = Curl_add_buffer_send(req_buffer, conn, 578 &data->info.request_size, 0, FIRSTSOCKET); 579 if(result) { 580 failf(data, "Failed sending RTSP request"); 581 return result; 582 } 583 584 Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, 585 putsize?FIRSTSOCKET:-1, 586 putsize?&http->writebytecount:NULL); 587 588 /* Increment the CSeq on success */ 589 data->state.rtsp_next_client_CSeq++; 590 591 if(http->writebytecount) { 592 /* if a request-body has been sent off, we make sure this progress is 593 noted properly */ 594 Curl_pgrsSetUploadCounter(data, http->writebytecount); 595 if(Curl_pgrsUpdate(conn)) 596 result = CURLE_ABORTED_BY_CALLBACK; 597 } 598 599 return result; 600} 601 602 603static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data, 604 struct connectdata *conn, 605 ssize_t *nread, 606 bool *readmore) { 607 struct SingleRequest *k = &data->req; 608 struct rtsp_conn *rtspc = &(conn->proto.rtspc); 609 610 char *rtp; /* moving pointer to rtp data */ 611 ssize_t rtp_dataleft; /* how much data left to parse in this round */ 612 char *scratch; 613 CURLcode result; 614 615 if(rtspc->rtp_buf) { 616 /* There was some leftover data the last time. Merge buffers */ 617 char *newptr = realloc(rtspc->rtp_buf, rtspc->rtp_bufsize + *nread); 618 if(!newptr) { 619 Curl_safefree(rtspc->rtp_buf); 620 rtspc->rtp_buf = NULL; 621 rtspc->rtp_bufsize = 0; 622 return CURLE_OUT_OF_MEMORY; 623 } 624 rtspc->rtp_buf = newptr; 625 memcpy(rtspc->rtp_buf + rtspc->rtp_bufsize, k->str, *nread); 626 rtspc->rtp_bufsize += *nread; 627 rtp = rtspc->rtp_buf; 628 rtp_dataleft = rtspc->rtp_bufsize; 629 } 630 else { 631 /* Just parse the request buffer directly */ 632 rtp = k->str; 633 rtp_dataleft = *nread; 634 } 635 636 while((rtp_dataleft > 0) && 637 (rtp[0] == '$')) { 638 if(rtp_dataleft > 4) { 639 int rtp_length; 640 641 /* Parse the header */ 642 /* The channel identifier immediately follows and is 1 byte */ 643 rtspc->rtp_channel = RTP_PKT_CHANNEL(rtp); 644 645 /* The length is two bytes */ 646 rtp_length = RTP_PKT_LENGTH(rtp); 647 648 if(rtp_dataleft < rtp_length + 4) { 649 /* Need more - incomplete payload*/ 650 *readmore = TRUE; 651 break; 652 } 653 else { 654 /* We have the full RTP interleaved packet 655 * Write out the header including the leading '$' */ 656 DEBUGF(infof(data, "RTP write channel %d rtp_length %d\n", 657 rtspc->rtp_channel, rtp_length)); 658 result = rtp_client_write(conn, &rtp[0], rtp_length + 4); 659 if(result) { 660 failf(data, "Got an error writing an RTP packet"); 661 *readmore = FALSE; 662 Curl_safefree(rtspc->rtp_buf); 663 rtspc->rtp_buf = NULL; 664 rtspc->rtp_bufsize = 0; 665 return result; 666 } 667 668 /* Move forward in the buffer */ 669 rtp_dataleft -= rtp_length + 4; 670 rtp += rtp_length + 4; 671 672 if(data->set.rtspreq == RTSPREQ_RECEIVE) { 673 /* If we are in a passive receive, give control back 674 * to the app as often as we can. 675 */ 676 k->keepon &= ~KEEP_RECV; 677 } 678 } 679 } 680 else { 681 /* Need more - incomplete header */ 682 *readmore = TRUE; 683 break; 684 } 685 } 686 687 if(rtp_dataleft != 0 && rtp[0] == '$') { 688 DEBUGF(infof(data, "RTP Rewinding %zd %s\n", rtp_dataleft, 689 *readmore ? "(READMORE)" : "")); 690 691 /* Store the incomplete RTP packet for a "rewind" */ 692 scratch = malloc(rtp_dataleft); 693 if(!scratch) { 694 Curl_safefree(rtspc->rtp_buf); 695 rtspc->rtp_buf = NULL; 696 rtspc->rtp_bufsize = 0; 697 return CURLE_OUT_OF_MEMORY; 698 } 699 memcpy(scratch, rtp, rtp_dataleft); 700 Curl_safefree(rtspc->rtp_buf); 701 rtspc->rtp_buf = scratch; 702 rtspc->rtp_bufsize = rtp_dataleft; 703 704 /* As far as the transfer is concerned, this data is consumed */ 705 *nread = 0; 706 return CURLE_OK; 707 } 708 else { 709 /* Fix up k->str to point just after the last RTP packet */ 710 k->str += *nread - rtp_dataleft; 711 712 /* either all of the data has been read or... 713 * rtp now points at the next byte to parse 714 */ 715 if(rtp_dataleft > 0) 716 DEBUGASSERT(k->str[0] == rtp[0]); 717 718 DEBUGASSERT(rtp_dataleft <= *nread); /* sanity check */ 719 720 *nread = rtp_dataleft; 721 } 722 723 /* If we get here, we have finished with the leftover/merge buffer */ 724 Curl_safefree(rtspc->rtp_buf); 725 rtspc->rtp_buf = NULL; 726 rtspc->rtp_bufsize = 0; 727 728 return CURLE_OK; 729} 730 731static 732CURLcode rtp_client_write(struct connectdata *conn, char *ptr, size_t len) 733{ 734 struct Curl_easy *data = conn->data; 735 size_t wrote; 736 curl_write_callback writeit; 737 738 if(len == 0) { 739 failf (data, "Cannot write a 0 size RTP packet."); 740 return CURLE_WRITE_ERROR; 741 } 742 743 writeit = data->set.fwrite_rtp?data->set.fwrite_rtp:data->set.fwrite_func; 744 wrote = writeit(ptr, 1, len, data->set.rtp_out); 745 746 if(CURL_WRITEFUNC_PAUSE == wrote) { 747 failf (data, "Cannot pause RTP"); 748 return CURLE_WRITE_ERROR; 749 } 750 751 if(wrote != len) { 752 failf (data, "Failed writing RTP data"); 753 return CURLE_WRITE_ERROR; 754 } 755 756 return CURLE_OK; 757} 758 759CURLcode Curl_rtsp_parseheader(struct connectdata *conn, 760 char *header) 761{ 762 struct Curl_easy *data = conn->data; 763 long CSeq = 0; 764 765 if(checkprefix("CSeq:", header)) { 766 /* Store the received CSeq. Match is verified in rtsp_done */ 767 int nc = sscanf(&header[4], ": %ld", &CSeq); 768 if(nc == 1) { 769 struct RTSP *rtsp = data->req.protop; 770 rtsp->CSeq_recv = CSeq; /* mark the request */ 771 data->state.rtsp_CSeq_recv = CSeq; /* update the handle */ 772 } 773 else { 774 failf(data, "Unable to read the CSeq header: [%s]", header); 775 return CURLE_RTSP_CSEQ_ERROR; 776 } 777 } 778 else if(checkprefix("Session:", header)) { 779 char *start; 780 781 /* Find the first non-space letter */ 782 start = header + 8; 783 while(*start && ISSPACE(*start)) 784 start++; 785 786 if(!*start) { 787 failf(data, "Got a blank Session ID"); 788 } 789 else if(data->set.str[STRING_RTSP_SESSION_ID]) { 790 /* If the Session ID is set, then compare */ 791 if(strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], 792 strlen(data->set.str[STRING_RTSP_SESSION_ID])) != 0) { 793 failf(data, "Got RTSP Session ID Line [%s], but wanted ID [%s]", 794 start, data->set.str[STRING_RTSP_SESSION_ID]); 795 return CURLE_RTSP_SESSION_ERROR; 796 } 797 } 798 else { 799 /* If the Session ID is not set, and we find it in a response, then 800 set it */ 801 802 /* The session ID can be an alphanumeric or a 'safe' character 803 * 804 * RFC 2326 15.1 Base Syntax: 805 * safe = "\$" | "-" | "_" | "." | "+" 806 * */ 807 char *end = start; 808 while(*end && 809 (ISALNUM(*end) || *end == '-' || *end == '_' || *end == '.' || 810 *end == '+' || 811 (*end == '\\' && *(end + 1) && *(end + 1) == '$' && (++end, 1)))) 812 end++; 813 814 /* Copy the id substring into a new buffer */ 815 data->set.str[STRING_RTSP_SESSION_ID] = malloc(end - start + 1); 816 if(data->set.str[STRING_RTSP_SESSION_ID] == NULL) 817 return CURLE_OUT_OF_MEMORY; 818 memcpy(data->set.str[STRING_RTSP_SESSION_ID], start, end - start); 819 (data->set.str[STRING_RTSP_SESSION_ID])[end - start] = '\0'; 820 } 821 } 822 return CURLE_OK; 823} 824 825#endif /* CURL_DISABLE_RTSP */ 826