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