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