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