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