/src/PROJ/curl/lib/rtsp.c
Line | Count | Source (jump to first uncovered line) |
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 | 0 | #define RTP_PKT_LENGTH(p) ((((unsigned int)((unsigned char)((p)[2]))) << 8) | \ |
49 | 0 | ((unsigned 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 an RTSP response. |
62 | | * @param data the transfer |
63 | | * @param conn the connection |
64 | | * @param buf data read from connection |
65 | | * @param blen amount of data in buf |
66 | | * @param is_eos TRUE iff this is the last write |
67 | | * @param readmore out, TRUE iff complete buf was consumed and more data |
68 | | * is needed |
69 | | */ |
70 | | static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, |
71 | | const char *buf, |
72 | | size_t blen, |
73 | | bool is_eos, |
74 | | bool *done); |
75 | | |
76 | | static CURLcode rtsp_setup_connection(struct Curl_easy *data, |
77 | | struct connectdata *conn); |
78 | | static unsigned int rtsp_conncheck(struct Curl_easy *data, |
79 | | struct connectdata *check, |
80 | | unsigned int checks_to_perform); |
81 | | |
82 | | /* this returns the socket to wait for in the DO and DOING state for the multi |
83 | | interface and then we're always _sending_ a request and thus we wait for |
84 | | the single socket to become writable only */ |
85 | | static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn, |
86 | | curl_socket_t *socks) |
87 | 0 | { |
88 | | /* write mode */ |
89 | 0 | (void)data; |
90 | 0 | socks[0] = conn->sock[FIRSTSOCKET]; |
91 | 0 | return GETSOCK_WRITESOCK(0); |
92 | 0 | } |
93 | | |
94 | | static |
95 | | CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len); |
96 | | static |
97 | | CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport); |
98 | | |
99 | | |
100 | | /* |
101 | | * RTSP handler interface. |
102 | | */ |
103 | | const struct Curl_handler Curl_handler_rtsp = { |
104 | | "RTSP", /* scheme */ |
105 | | rtsp_setup_connection, /* setup_connection */ |
106 | | rtsp_do, /* do_it */ |
107 | | rtsp_done, /* done */ |
108 | | ZERO_NULL, /* do_more */ |
109 | | rtsp_connect, /* connect_it */ |
110 | | ZERO_NULL, /* connecting */ |
111 | | ZERO_NULL, /* doing */ |
112 | | ZERO_NULL, /* proto_getsock */ |
113 | | rtsp_getsock_do, /* doing_getsock */ |
114 | | ZERO_NULL, /* domore_getsock */ |
115 | | ZERO_NULL, /* perform_getsock */ |
116 | | rtsp_disconnect, /* disconnect */ |
117 | | rtsp_rtp_write_resp, /* write_resp */ |
118 | | rtsp_conncheck, /* connection_check */ |
119 | | ZERO_NULL, /* attach connection */ |
120 | | PORT_RTSP, /* defport */ |
121 | | CURLPROTO_RTSP, /* protocol */ |
122 | | CURLPROTO_RTSP, /* family */ |
123 | | PROTOPT_NONE /* flags */ |
124 | | }; |
125 | | |
126 | 0 | #define MAX_RTP_BUFFERSIZE 1000000 /* arbitrary */ |
127 | | |
128 | | static CURLcode rtsp_setup_connection(struct Curl_easy *data, |
129 | | struct connectdata *conn) |
130 | 0 | { |
131 | 0 | struct RTSP *rtsp; |
132 | 0 | (void)conn; |
133 | |
|
134 | 0 | data->req.p.rtsp = rtsp = calloc(1, sizeof(struct RTSP)); |
135 | 0 | if(!rtsp) |
136 | 0 | return CURLE_OUT_OF_MEMORY; |
137 | | |
138 | 0 | Curl_dyn_init(&conn->proto.rtspc.buf, MAX_RTP_BUFFERSIZE); |
139 | 0 | return CURLE_OK; |
140 | 0 | } |
141 | | |
142 | | |
143 | | /* |
144 | | * Function to check on various aspects of a connection. |
145 | | */ |
146 | | static unsigned int rtsp_conncheck(struct Curl_easy *data, |
147 | | struct connectdata *conn, |
148 | | unsigned int checks_to_perform) |
149 | 0 | { |
150 | 0 | unsigned int ret_val = CONNRESULT_NONE; |
151 | 0 | (void)data; |
152 | |
|
153 | 0 | if(checks_to_perform & CONNCHECK_ISDEAD) { |
154 | 0 | bool input_pending; |
155 | 0 | if(!Curl_conn_is_alive(data, conn, &input_pending)) |
156 | 0 | ret_val |= CONNRESULT_DEAD; |
157 | 0 | } |
158 | |
|
159 | 0 | return ret_val; |
160 | 0 | } |
161 | | |
162 | | |
163 | | static CURLcode rtsp_connect(struct Curl_easy *data, bool *done) |
164 | 0 | { |
165 | 0 | CURLcode httpStatus; |
166 | |
|
167 | 0 | httpStatus = Curl_http_connect(data, done); |
168 | | |
169 | | /* Initialize the CSeq if not already done */ |
170 | 0 | if(data->state.rtsp_next_client_CSeq == 0) |
171 | 0 | data->state.rtsp_next_client_CSeq = 1; |
172 | 0 | if(data->state.rtsp_next_server_CSeq == 0) |
173 | 0 | data->state.rtsp_next_server_CSeq = 1; |
174 | |
|
175 | 0 | data->conn->proto.rtspc.rtp_channel = -1; |
176 | |
|
177 | 0 | return httpStatus; |
178 | 0 | } |
179 | | |
180 | | static CURLcode rtsp_disconnect(struct Curl_easy *data, |
181 | | struct connectdata *conn, bool dead) |
182 | 0 | { |
183 | 0 | (void) dead; |
184 | 0 | (void) data; |
185 | 0 | Curl_dyn_free(&conn->proto.rtspc.buf); |
186 | 0 | return CURLE_OK; |
187 | 0 | } |
188 | | |
189 | | |
190 | | static CURLcode rtsp_done(struct Curl_easy *data, |
191 | | CURLcode status, bool premature) |
192 | 0 | { |
193 | 0 | struct RTSP *rtsp = data->req.p.rtsp; |
194 | 0 | CURLcode httpStatus; |
195 | | |
196 | | /* Bypass HTTP empty-reply checks on receive */ |
197 | 0 | if(data->set.rtspreq == RTSPREQ_RECEIVE) |
198 | 0 | premature = TRUE; |
199 | |
|
200 | 0 | httpStatus = Curl_http_done(data, status, premature); |
201 | |
|
202 | 0 | if(rtsp && !status && !httpStatus) { |
203 | | /* Check the sequence numbers */ |
204 | 0 | long CSeq_sent = rtsp->CSeq_sent; |
205 | 0 | long CSeq_recv = rtsp->CSeq_recv; |
206 | 0 | if((data->set.rtspreq != RTSPREQ_RECEIVE) && (CSeq_sent != CSeq_recv)) { |
207 | 0 | failf(data, |
208 | 0 | "The CSeq of this request %ld did not match the response %ld", |
209 | 0 | CSeq_sent, CSeq_recv); |
210 | 0 | return CURLE_RTSP_CSEQ_ERROR; |
211 | 0 | } |
212 | 0 | if(data->set.rtspreq == RTSPREQ_RECEIVE && |
213 | 0 | (data->conn->proto.rtspc.rtp_channel == -1)) { |
214 | 0 | infof(data, "Got an RTP Receive with a CSeq of %ld", CSeq_recv); |
215 | 0 | } |
216 | 0 | } |
217 | | |
218 | 0 | return httpStatus; |
219 | 0 | } |
220 | | |
221 | | static CURLcode rtsp_do(struct Curl_easy *data, bool *done) |
222 | 0 | { |
223 | 0 | struct connectdata *conn = data->conn; |
224 | 0 | CURLcode result = CURLE_OK; |
225 | 0 | Curl_RtspReq rtspreq = data->set.rtspreq; |
226 | 0 | struct RTSP *rtsp = data->req.p.rtsp; |
227 | 0 | struct dynbuf req_buffer; |
228 | 0 | curl_off_t postsize = 0; /* for ANNOUNCE and SET_PARAMETER */ |
229 | 0 | curl_off_t putsize = 0; /* for ANNOUNCE and SET_PARAMETER */ |
230 | |
|
231 | 0 | const char *p_request = NULL; |
232 | 0 | const char *p_session_id = NULL; |
233 | 0 | const char *p_accept = NULL; |
234 | 0 | const char *p_accept_encoding = NULL; |
235 | 0 | const char *p_range = NULL; |
236 | 0 | const char *p_referrer = NULL; |
237 | 0 | const char *p_stream_uri = NULL; |
238 | 0 | const char *p_transport = NULL; |
239 | 0 | const char *p_uagent = NULL; |
240 | 0 | const char *p_proxyuserpwd = NULL; |
241 | 0 | const char *p_userpwd = NULL; |
242 | |
|
243 | 0 | *done = TRUE; |
244 | |
|
245 | 0 | rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq; |
246 | 0 | rtsp->CSeq_recv = 0; |
247 | | |
248 | | /* Setup the first_* fields to allow auth details get sent |
249 | | to this origin */ |
250 | |
|
251 | 0 | if(!data->state.first_host) { |
252 | 0 | data->state.first_host = strdup(conn->host.name); |
253 | 0 | if(!data->state.first_host) |
254 | 0 | return CURLE_OUT_OF_MEMORY; |
255 | | |
256 | 0 | data->state.first_remote_port = conn->remote_port; |
257 | 0 | data->state.first_remote_protocol = conn->handler->protocol; |
258 | 0 | } |
259 | | |
260 | | /* Setup the 'p_request' pointer to the proper p_request string |
261 | | * Since all RTSP requests are included here, there is no need to |
262 | | * support custom requests like HTTP. |
263 | | **/ |
264 | 0 | data->req.no_body = TRUE; /* most requests don't contain a body */ |
265 | 0 | switch(rtspreq) { |
266 | 0 | default: |
267 | 0 | failf(data, "Got invalid RTSP request"); |
268 | 0 | return CURLE_BAD_FUNCTION_ARGUMENT; |
269 | 0 | case RTSPREQ_OPTIONS: |
270 | 0 | p_request = "OPTIONS"; |
271 | 0 | break; |
272 | 0 | case RTSPREQ_DESCRIBE: |
273 | 0 | p_request = "DESCRIBE"; |
274 | 0 | data->req.no_body = FALSE; |
275 | 0 | break; |
276 | 0 | case RTSPREQ_ANNOUNCE: |
277 | 0 | p_request = "ANNOUNCE"; |
278 | 0 | break; |
279 | 0 | case RTSPREQ_SETUP: |
280 | 0 | p_request = "SETUP"; |
281 | 0 | break; |
282 | 0 | case RTSPREQ_PLAY: |
283 | 0 | p_request = "PLAY"; |
284 | 0 | break; |
285 | 0 | case RTSPREQ_PAUSE: |
286 | 0 | p_request = "PAUSE"; |
287 | 0 | break; |
288 | 0 | case RTSPREQ_TEARDOWN: |
289 | 0 | p_request = "TEARDOWN"; |
290 | 0 | break; |
291 | 0 | case RTSPREQ_GET_PARAMETER: |
292 | | /* GET_PARAMETER's no_body status is determined later */ |
293 | 0 | p_request = "GET_PARAMETER"; |
294 | 0 | data->req.no_body = FALSE; |
295 | 0 | break; |
296 | 0 | case RTSPREQ_SET_PARAMETER: |
297 | 0 | p_request = "SET_PARAMETER"; |
298 | 0 | break; |
299 | 0 | case RTSPREQ_RECORD: |
300 | 0 | p_request = "RECORD"; |
301 | 0 | break; |
302 | 0 | case RTSPREQ_RECEIVE: |
303 | 0 | p_request = ""; |
304 | | /* Treat interleaved RTP as body */ |
305 | 0 | data->req.no_body = FALSE; |
306 | 0 | break; |
307 | 0 | case RTSPREQ_LAST: |
308 | 0 | failf(data, "Got invalid RTSP request: RTSPREQ_LAST"); |
309 | 0 | return CURLE_BAD_FUNCTION_ARGUMENT; |
310 | 0 | } |
311 | | |
312 | 0 | if(rtspreq == RTSPREQ_RECEIVE) { |
313 | 0 | Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1); |
314 | |
|
315 | 0 | return result; |
316 | 0 | } |
317 | | |
318 | 0 | p_session_id = data->set.str[STRING_RTSP_SESSION_ID]; |
319 | 0 | if(!p_session_id && |
320 | 0 | (rtspreq & ~(RTSPREQ_OPTIONS | RTSPREQ_DESCRIBE | RTSPREQ_SETUP))) { |
321 | 0 | failf(data, "Refusing to issue an RTSP request [%s] without a session ID.", |
322 | 0 | p_request); |
323 | 0 | return CURLE_BAD_FUNCTION_ARGUMENT; |
324 | 0 | } |
325 | | |
326 | | /* Stream URI. Default to server '*' if not specified */ |
327 | 0 | if(data->set.str[STRING_RTSP_STREAM_URI]) { |
328 | 0 | p_stream_uri = data->set.str[STRING_RTSP_STREAM_URI]; |
329 | 0 | } |
330 | 0 | else { |
331 | 0 | p_stream_uri = "*"; |
332 | 0 | } |
333 | | |
334 | | /* Transport Header for SETUP requests */ |
335 | 0 | p_transport = Curl_checkheaders(data, STRCONST("Transport")); |
336 | 0 | if(rtspreq == RTSPREQ_SETUP && !p_transport) { |
337 | | /* New Transport: setting? */ |
338 | 0 | if(data->set.str[STRING_RTSP_TRANSPORT]) { |
339 | 0 | Curl_safefree(data->state.aptr.rtsp_transport); |
340 | |
|
341 | 0 | data->state.aptr.rtsp_transport = |
342 | 0 | aprintf("Transport: %s\r\n", |
343 | 0 | data->set.str[STRING_RTSP_TRANSPORT]); |
344 | 0 | if(!data->state.aptr.rtsp_transport) |
345 | 0 | return CURLE_OUT_OF_MEMORY; |
346 | 0 | } |
347 | 0 | else { |
348 | 0 | failf(data, |
349 | 0 | "Refusing to issue an RTSP SETUP without a Transport: header."); |
350 | 0 | return CURLE_BAD_FUNCTION_ARGUMENT; |
351 | 0 | } |
352 | | |
353 | 0 | p_transport = data->state.aptr.rtsp_transport; |
354 | 0 | } |
355 | | |
356 | | /* Accept Headers for DESCRIBE requests */ |
357 | 0 | if(rtspreq == RTSPREQ_DESCRIBE) { |
358 | | /* Accept Header */ |
359 | 0 | p_accept = Curl_checkheaders(data, STRCONST("Accept"))? |
360 | 0 | NULL:"Accept: application/sdp\r\n"; |
361 | | |
362 | | /* Accept-Encoding header */ |
363 | 0 | if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && |
364 | 0 | data->set.str[STRING_ENCODING]) { |
365 | 0 | Curl_safefree(data->state.aptr.accept_encoding); |
366 | 0 | data->state.aptr.accept_encoding = |
367 | 0 | aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); |
368 | |
|
369 | 0 | if(!data->state.aptr.accept_encoding) |
370 | 0 | return CURLE_OUT_OF_MEMORY; |
371 | | |
372 | 0 | p_accept_encoding = data->state.aptr.accept_encoding; |
373 | 0 | } |
374 | 0 | } |
375 | | |
376 | | /* The User-Agent string might have been allocated in url.c already, because |
377 | | it might have been used in the proxy connect, but if we have got a header |
378 | | with the user-agent string specified, we erase the previously made string |
379 | | here. */ |
380 | 0 | if(Curl_checkheaders(data, STRCONST("User-Agent")) && |
381 | 0 | data->state.aptr.uagent) { |
382 | 0 | Curl_safefree(data->state.aptr.uagent); |
383 | 0 | } |
384 | 0 | else if(!Curl_checkheaders(data, STRCONST("User-Agent")) && |
385 | 0 | data->set.str[STRING_USERAGENT]) { |
386 | 0 | p_uagent = data->state.aptr.uagent; |
387 | 0 | } |
388 | | |
389 | | /* setup the authentication headers */ |
390 | 0 | result = Curl_http_output_auth(data, conn, p_request, HTTPREQ_GET, |
391 | 0 | p_stream_uri, FALSE); |
392 | 0 | if(result) |
393 | 0 | return result; |
394 | | |
395 | 0 | p_proxyuserpwd = data->state.aptr.proxyuserpwd; |
396 | 0 | p_userpwd = data->state.aptr.userpwd; |
397 | | |
398 | | /* Referrer */ |
399 | 0 | Curl_safefree(data->state.aptr.ref); |
400 | 0 | if(data->state.referer && !Curl_checkheaders(data, STRCONST("Referer"))) |
401 | 0 | data->state.aptr.ref = aprintf("Referer: %s\r\n", data->state.referer); |
402 | |
|
403 | 0 | p_referrer = data->state.aptr.ref; |
404 | | |
405 | | /* |
406 | | * Range Header |
407 | | * Only applies to PLAY, PAUSE, RECORD |
408 | | * |
409 | | * Go ahead and use the Range stuff supplied for HTTP |
410 | | */ |
411 | 0 | if(data->state.use_range && |
412 | 0 | (rtspreq & (RTSPREQ_PLAY | RTSPREQ_PAUSE | RTSPREQ_RECORD))) { |
413 | | |
414 | | /* Check to see if there is a range set in the custom headers */ |
415 | 0 | if(!Curl_checkheaders(data, STRCONST("Range")) && data->state.range) { |
416 | 0 | Curl_safefree(data->state.aptr.rangeline); |
417 | 0 | data->state.aptr.rangeline = aprintf("Range: %s\r\n", data->state.range); |
418 | 0 | p_range = data->state.aptr.rangeline; |
419 | 0 | } |
420 | 0 | } |
421 | | |
422 | | /* |
423 | | * Sanity check the custom headers |
424 | | */ |
425 | 0 | if(Curl_checkheaders(data, STRCONST("CSeq"))) { |
426 | 0 | failf(data, "CSeq cannot be set as a custom header."); |
427 | 0 | return CURLE_RTSP_CSEQ_ERROR; |
428 | 0 | } |
429 | 0 | if(Curl_checkheaders(data, STRCONST("Session"))) { |
430 | 0 | failf(data, "Session ID cannot be set as a custom header."); |
431 | 0 | return CURLE_BAD_FUNCTION_ARGUMENT; |
432 | 0 | } |
433 | | |
434 | | /* Initialize a dynamic send buffer */ |
435 | 0 | Curl_dyn_init(&req_buffer, DYN_RTSP_REQ_HEADER); |
436 | |
|
437 | 0 | result = |
438 | 0 | Curl_dyn_addf(&req_buffer, |
439 | 0 | "%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */ |
440 | 0 | "CSeq: %ld\r\n", /* CSeq */ |
441 | 0 | p_request, p_stream_uri, rtsp->CSeq_sent); |
442 | 0 | if(result) |
443 | 0 | return result; |
444 | | |
445 | | /* |
446 | | * Rather than do a normal alloc line, keep the session_id unformatted |
447 | | * to make comparison easier |
448 | | */ |
449 | 0 | if(p_session_id) { |
450 | 0 | result = Curl_dyn_addf(&req_buffer, "Session: %s\r\n", p_session_id); |
451 | 0 | if(result) |
452 | 0 | return result; |
453 | 0 | } |
454 | | |
455 | | /* |
456 | | * Shared HTTP-like options |
457 | | */ |
458 | 0 | result = Curl_dyn_addf(&req_buffer, |
459 | 0 | "%s" /* transport */ |
460 | 0 | "%s" /* accept */ |
461 | 0 | "%s" /* accept-encoding */ |
462 | 0 | "%s" /* range */ |
463 | 0 | "%s" /* referrer */ |
464 | 0 | "%s" /* user-agent */ |
465 | 0 | "%s" /* proxyuserpwd */ |
466 | 0 | "%s" /* userpwd */ |
467 | 0 | , |
468 | 0 | p_transport ? p_transport : "", |
469 | 0 | p_accept ? p_accept : "", |
470 | 0 | p_accept_encoding ? p_accept_encoding : "", |
471 | 0 | p_range ? p_range : "", |
472 | 0 | p_referrer ? p_referrer : "", |
473 | 0 | p_uagent ? p_uagent : "", |
474 | 0 | p_proxyuserpwd ? p_proxyuserpwd : "", |
475 | 0 | p_userpwd ? p_userpwd : ""); |
476 | | |
477 | | /* |
478 | | * Free userpwd now --- cannot reuse this for Negotiate and possibly NTLM |
479 | | * with basic and digest, it will be freed anyway by the next request |
480 | | */ |
481 | 0 | Curl_safefree(data->state.aptr.userpwd); |
482 | |
|
483 | 0 | if(result) |
484 | 0 | return result; |
485 | | |
486 | 0 | if((rtspreq == RTSPREQ_SETUP) || (rtspreq == RTSPREQ_DESCRIBE)) { |
487 | 0 | result = Curl_add_timecondition(data, &req_buffer); |
488 | 0 | if(result) |
489 | 0 | return result; |
490 | 0 | } |
491 | | |
492 | 0 | result = Curl_add_custom_headers(data, FALSE, &req_buffer); |
493 | 0 | if(result) |
494 | 0 | return result; |
495 | | |
496 | 0 | if(rtspreq == RTSPREQ_ANNOUNCE || |
497 | 0 | rtspreq == RTSPREQ_SET_PARAMETER || |
498 | 0 | rtspreq == RTSPREQ_GET_PARAMETER) { |
499 | |
|
500 | 0 | if(data->state.upload) { |
501 | 0 | putsize = data->state.infilesize; |
502 | 0 | data->state.httpreq = HTTPREQ_PUT; |
503 | |
|
504 | 0 | } |
505 | 0 | else { |
506 | 0 | postsize = (data->state.infilesize != -1)? |
507 | 0 | data->state.infilesize: |
508 | 0 | (data->set.postfields? (curl_off_t)strlen(data->set.postfields):0); |
509 | 0 | data->state.httpreq = HTTPREQ_POST; |
510 | 0 | } |
511 | |
|
512 | 0 | if(putsize > 0 || postsize > 0) { |
513 | | /* As stated in the http comments, it is probably not wise to |
514 | | * actually set a custom Content-Length in the headers */ |
515 | 0 | if(!Curl_checkheaders(data, STRCONST("Content-Length"))) { |
516 | 0 | result = |
517 | 0 | Curl_dyn_addf(&req_buffer, |
518 | 0 | "Content-Length: %" CURL_FORMAT_CURL_OFF_T"\r\n", |
519 | 0 | (data->state.upload ? putsize : postsize)); |
520 | 0 | if(result) |
521 | 0 | return result; |
522 | 0 | } |
523 | | |
524 | 0 | if(rtspreq == RTSPREQ_SET_PARAMETER || |
525 | 0 | rtspreq == RTSPREQ_GET_PARAMETER) { |
526 | 0 | if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { |
527 | 0 | result = Curl_dyn_addn(&req_buffer, |
528 | 0 | STRCONST("Content-Type: " |
529 | 0 | "text/parameters\r\n")); |
530 | 0 | if(result) |
531 | 0 | return result; |
532 | 0 | } |
533 | 0 | } |
534 | | |
535 | 0 | if(rtspreq == RTSPREQ_ANNOUNCE) { |
536 | 0 | if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { |
537 | 0 | result = Curl_dyn_addn(&req_buffer, |
538 | 0 | STRCONST("Content-Type: " |
539 | 0 | "application/sdp\r\n")); |
540 | 0 | if(result) |
541 | 0 | return result; |
542 | 0 | } |
543 | 0 | } |
544 | | |
545 | 0 | data->state.expect100header = FALSE; /* RTSP posts are simple/small */ |
546 | 0 | } |
547 | 0 | else if(rtspreq == RTSPREQ_GET_PARAMETER) { |
548 | | /* Check for an empty GET_PARAMETER (heartbeat) request */ |
549 | 0 | data->state.httpreq = HTTPREQ_HEAD; |
550 | 0 | data->req.no_body = TRUE; |
551 | 0 | } |
552 | 0 | } |
553 | | |
554 | | /* RTSP never allows chunked transfer */ |
555 | 0 | data->req.forbidchunk = TRUE; |
556 | | /* Finish the request buffer */ |
557 | 0 | result = Curl_dyn_addn(&req_buffer, STRCONST("\r\n")); |
558 | 0 | if(result) |
559 | 0 | return result; |
560 | | |
561 | 0 | if(postsize > 0) { |
562 | 0 | result = Curl_dyn_addn(&req_buffer, data->set.postfields, |
563 | 0 | (size_t)postsize); |
564 | 0 | if(result) |
565 | 0 | return result; |
566 | 0 | } |
567 | | |
568 | | /* issue the request */ |
569 | 0 | result = Curl_buffer_send(&req_buffer, data, data->req.p.http, |
570 | 0 | &data->info.request_size, 0, FIRSTSOCKET); |
571 | 0 | if(result) { |
572 | 0 | failf(data, "Failed sending RTSP request"); |
573 | 0 | return result; |
574 | 0 | } |
575 | | |
576 | 0 | Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, putsize?FIRSTSOCKET:-1); |
577 | | |
578 | | /* Increment the CSeq on success */ |
579 | 0 | data->state.rtsp_next_client_CSeq++; |
580 | |
|
581 | 0 | if(data->req.writebytecount) { |
582 | | /* if a request-body has been sent off, we make sure this progress is |
583 | | noted properly */ |
584 | 0 | Curl_pgrsSetUploadCounter(data, data->req.writebytecount); |
585 | 0 | if(Curl_pgrsUpdate(data)) |
586 | 0 | result = CURLE_ABORTED_BY_CALLBACK; |
587 | 0 | } |
588 | |
|
589 | 0 | return result; |
590 | 0 | } |
591 | | |
592 | | /** |
593 | | * write any BODY bytes missing to the client, ignore the rest. |
594 | | */ |
595 | | static CURLcode rtp_write_body_junk(struct Curl_easy *data, |
596 | | const char *buf, |
597 | | size_t blen) |
598 | 0 | { |
599 | 0 | struct rtsp_conn *rtspc = &(data->conn->proto.rtspc); |
600 | 0 | curl_off_t body_remain; |
601 | 0 | bool in_body; |
602 | |
|
603 | 0 | in_body = (data->req.headerline && !rtspc->in_header) && |
604 | 0 | (data->req.size >= 0) && |
605 | 0 | (data->req.bytecount < data->req.size); |
606 | 0 | body_remain = in_body? (data->req.size - data->req.bytecount) : 0; |
607 | 0 | DEBUGASSERT(body_remain >= 0); |
608 | 0 | if(body_remain) { |
609 | 0 | if((curl_off_t)blen > body_remain) |
610 | 0 | blen = (size_t)body_remain; |
611 | 0 | return Curl_client_write(data, CLIENTWRITE_BODY, (char *)buf, blen); |
612 | 0 | } |
613 | 0 | return CURLE_OK; |
614 | 0 | } |
615 | | |
616 | | static CURLcode rtsp_filter_rtp(struct Curl_easy *data, |
617 | | const char *buf, |
618 | | size_t blen, |
619 | | size_t *pconsumed) |
620 | 0 | { |
621 | 0 | struct rtsp_conn *rtspc = &(data->conn->proto.rtspc); |
622 | 0 | CURLcode result = CURLE_OK; |
623 | 0 | size_t skip_len = 0; |
624 | |
|
625 | 0 | *pconsumed = 0; |
626 | 0 | while(blen) { |
627 | 0 | bool in_body = (data->req.headerline && !rtspc->in_header) && |
628 | 0 | (data->req.size >= 0) && |
629 | 0 | (data->req.bytecount < data->req.size); |
630 | 0 | switch(rtspc->state) { |
631 | | |
632 | 0 | case RTP_PARSE_SKIP: { |
633 | 0 | DEBUGASSERT(Curl_dyn_len(&rtspc->buf) == 0); |
634 | 0 | while(blen && buf[0] != '$') { |
635 | 0 | if(!in_body && buf[0] == 'R' && |
636 | 0 | data->set.rtspreq != RTSPREQ_RECEIVE) { |
637 | 0 | if(strncmp(buf, "RTSP/", (blen < 5) ? blen : 5) == 0) { |
638 | | /* This could be the next response, no consume and return */ |
639 | 0 | if(*pconsumed) { |
640 | 0 | DEBUGF(infof(data, "RTP rtsp_filter_rtp[SKIP] RTSP/ prefix, " |
641 | 0 | "skipping %zd bytes of junk", *pconsumed)); |
642 | 0 | } |
643 | 0 | rtspc->state = RTP_PARSE_SKIP; |
644 | 0 | rtspc->in_header = TRUE; |
645 | 0 | goto out; |
646 | 0 | } |
647 | 0 | } |
648 | | /* junk/BODY, consume without buffering */ |
649 | 0 | *pconsumed += 1; |
650 | 0 | ++buf; |
651 | 0 | --blen; |
652 | 0 | ++skip_len; |
653 | 0 | } |
654 | 0 | if(blen && buf[0] == '$') { |
655 | | /* possible start of an RTP message, buffer */ |
656 | 0 | if(skip_len) { |
657 | | /* end of junk/BODY bytes, flush */ |
658 | 0 | result = rtp_write_body_junk(data, |
659 | 0 | (char *)(buf - skip_len), skip_len); |
660 | 0 | skip_len = 0; |
661 | 0 | if(result) |
662 | 0 | goto out; |
663 | 0 | } |
664 | 0 | if(Curl_dyn_addn(&rtspc->buf, buf, 1)) { |
665 | 0 | result = CURLE_OUT_OF_MEMORY; |
666 | 0 | goto out; |
667 | 0 | } |
668 | 0 | *pconsumed += 1; |
669 | 0 | ++buf; |
670 | 0 | --blen; |
671 | 0 | rtspc->state = RTP_PARSE_CHANNEL; |
672 | 0 | } |
673 | 0 | break; |
674 | 0 | } |
675 | | |
676 | 0 | case RTP_PARSE_CHANNEL: { |
677 | 0 | int idx = ((unsigned char)buf[0]) / 8; |
678 | 0 | int off = ((unsigned char)buf[0]) % 8; |
679 | 0 | DEBUGASSERT(Curl_dyn_len(&rtspc->buf) == 1); |
680 | 0 | if(!(data->state.rtp_channel_mask[idx] & (1 << off))) { |
681 | | /* invalid channel number, junk or BODY data */ |
682 | 0 | rtspc->state = RTP_PARSE_SKIP; |
683 | 0 | DEBUGASSERT(skip_len == 0); |
684 | | /* we do not consume this byte, it is BODY data */ |
685 | 0 | DEBUGF(infof(data, "RTSP: invalid RTP channel %d, skipping", idx)); |
686 | 0 | if(*pconsumed == 0) { |
687 | | /* We did not consume the initial '$' in our buffer, but had |
688 | | * it from an earlier call. We cannot un-consume it and have |
689 | | * to write it directly as BODY data */ |
690 | 0 | result = rtp_write_body_junk(data, Curl_dyn_ptr(&rtspc->buf), 1); |
691 | 0 | if(result) |
692 | 0 | goto out; |
693 | 0 | } |
694 | 0 | else { |
695 | | /* count the '$' as skip and continue */ |
696 | 0 | skip_len = 1; |
697 | 0 | } |
698 | 0 | Curl_dyn_free(&rtspc->buf); |
699 | 0 | break; |
700 | 0 | } |
701 | | /* a valid channel, so we expect this to be a real RTP message */ |
702 | 0 | rtspc->rtp_channel = (unsigned char)buf[0]; |
703 | 0 | if(Curl_dyn_addn(&rtspc->buf, buf, 1)) { |
704 | 0 | result = CURLE_OUT_OF_MEMORY; |
705 | 0 | goto out; |
706 | 0 | } |
707 | 0 | *pconsumed += 1; |
708 | 0 | ++buf; |
709 | 0 | --blen; |
710 | 0 | rtspc->state = RTP_PARSE_LEN; |
711 | 0 | break; |
712 | 0 | } |
713 | | |
714 | 0 | case RTP_PARSE_LEN: { |
715 | 0 | size_t rtp_len = Curl_dyn_len(&rtspc->buf); |
716 | 0 | const char *rtp_buf; |
717 | 0 | DEBUGASSERT(rtp_len >= 2 && rtp_len < 4); |
718 | 0 | if(Curl_dyn_addn(&rtspc->buf, buf, 1)) { |
719 | 0 | result = CURLE_OUT_OF_MEMORY; |
720 | 0 | goto out; |
721 | 0 | } |
722 | 0 | *pconsumed += 1; |
723 | 0 | ++buf; |
724 | 0 | --blen; |
725 | 0 | if(rtp_len == 2) |
726 | 0 | break; |
727 | 0 | rtp_buf = Curl_dyn_ptr(&rtspc->buf); |
728 | 0 | rtspc->rtp_len = RTP_PKT_LENGTH(rtp_buf) + 4; |
729 | 0 | rtspc->state = RTP_PARSE_DATA; |
730 | 0 | break; |
731 | 0 | } |
732 | | |
733 | 0 | case RTP_PARSE_DATA: { |
734 | 0 | size_t rtp_len = Curl_dyn_len(&rtspc->buf); |
735 | 0 | size_t needed; |
736 | 0 | DEBUGASSERT(rtp_len < rtspc->rtp_len); |
737 | 0 | needed = rtspc->rtp_len - rtp_len; |
738 | 0 | if(needed <= blen) { |
739 | 0 | if(Curl_dyn_addn(&rtspc->buf, buf, needed)) { |
740 | 0 | result = CURLE_OUT_OF_MEMORY; |
741 | 0 | goto out; |
742 | 0 | } |
743 | 0 | *pconsumed += needed; |
744 | 0 | buf += needed; |
745 | 0 | blen -= needed; |
746 | | /* complete RTP message in buffer */ |
747 | 0 | DEBUGF(infof(data, "RTP write channel %d rtp_len %zu", |
748 | 0 | rtspc->rtp_channel, rtspc->rtp_len)); |
749 | 0 | result = rtp_client_write(data, Curl_dyn_ptr(&rtspc->buf), |
750 | 0 | rtspc->rtp_len); |
751 | 0 | Curl_dyn_free(&rtspc->buf); |
752 | 0 | rtspc->state = RTP_PARSE_SKIP; |
753 | 0 | if(result) |
754 | 0 | goto out; |
755 | 0 | } |
756 | 0 | else { |
757 | 0 | if(Curl_dyn_addn(&rtspc->buf, buf, blen)) { |
758 | 0 | result = CURLE_OUT_OF_MEMORY; |
759 | 0 | goto out; |
760 | 0 | } |
761 | 0 | *pconsumed += blen; |
762 | 0 | buf += blen; |
763 | 0 | blen = 0; |
764 | 0 | } |
765 | 0 | break; |
766 | 0 | } |
767 | | |
768 | 0 | default: |
769 | 0 | DEBUGASSERT(0); |
770 | 0 | return CURLE_RECV_ERROR; |
771 | 0 | } |
772 | 0 | } |
773 | 0 | out: |
774 | 0 | if(!result && skip_len) |
775 | 0 | result = rtp_write_body_junk(data, (char *)(buf - skip_len), skip_len); |
776 | 0 | return result; |
777 | 0 | } |
778 | | |
779 | | static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, |
780 | | const char *buf, |
781 | | size_t blen, |
782 | | bool is_eos, |
783 | | bool *done) |
784 | 0 | { |
785 | 0 | struct rtsp_conn *rtspc = &(data->conn->proto.rtspc); |
786 | 0 | CURLcode result = CURLE_OK; |
787 | 0 | size_t consumed = 0; |
788 | |
|
789 | 0 | if(!data->req.header) |
790 | 0 | rtspc->in_header = FALSE; |
791 | 0 | *done = FALSE; |
792 | 0 | if(!blen) { |
793 | 0 | goto out; |
794 | 0 | } |
795 | | |
796 | 0 | DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, eos=%d)", |
797 | 0 | blen, rtspc->in_header, is_eos)); |
798 | | |
799 | | /* If header parsing is not onging, extract RTP messages */ |
800 | 0 | if(!rtspc->in_header) { |
801 | 0 | result = rtsp_filter_rtp(data, buf, blen, &consumed); |
802 | 0 | if(result) |
803 | 0 | goto out; |
804 | 0 | buf += consumed; |
805 | 0 | blen -= consumed; |
806 | | /* either we consumed all or are at the start of header parsing */ |
807 | 0 | if(blen && !data->req.header) |
808 | 0 | DEBUGF(infof(data, "RTSP: %zu bytes, possibly excess in response body", |
809 | 0 | blen)); |
810 | 0 | } |
811 | | |
812 | | /* we want to parse headers, do so */ |
813 | 0 | if(data->req.header && blen) { |
814 | 0 | rtspc->in_header = TRUE; |
815 | 0 | result = Curl_http_write_resp_hds(data, buf, blen, &consumed, done); |
816 | 0 | if(result) |
817 | 0 | goto out; |
818 | | |
819 | 0 | buf += consumed; |
820 | 0 | blen -= consumed; |
821 | |
|
822 | 0 | if(!data->req.header) |
823 | 0 | rtspc->in_header = FALSE; |
824 | |
|
825 | 0 | if(!rtspc->in_header) { |
826 | | /* If header parsing is done, extract interleaved RTP messages */ |
827 | 0 | if(data->req.size <= -1) { |
828 | | /* Respect section 4.4 of rfc2326: If the Content-Length header is |
829 | | absent, a length 0 must be assumed. */ |
830 | 0 | data->req.size = 0; |
831 | 0 | data->req.download_done = TRUE; |
832 | 0 | } |
833 | 0 | result = rtsp_filter_rtp(data, buf, blen, &consumed); |
834 | 0 | if(result) |
835 | 0 | goto out; |
836 | 0 | blen -= consumed; |
837 | 0 | } |
838 | 0 | } |
839 | | |
840 | 0 | if(rtspc->state != RTP_PARSE_SKIP) |
841 | 0 | *done = FALSE; |
842 | | /* we SHOULD have consumed all bytes, unless the response is borked. |
843 | | * In which case we write out the left over bytes, letting the client |
844 | | * writer deal with it (it will report EXCESS and fail the transfer). */ |
845 | 0 | DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, done=%d " |
846 | 0 | " rtspc->state=%d, req.size=%" CURL_FORMAT_CURL_OFF_T ")", |
847 | 0 | blen, rtspc->in_header, *done, rtspc->state, data->req.size)); |
848 | 0 | if(!result && (is_eos || blen)) { |
849 | 0 | result = Curl_client_write(data, CLIENTWRITE_BODY| |
850 | 0 | (is_eos? CLIENTWRITE_EOS:0), |
851 | 0 | (char *)buf, blen); |
852 | 0 | } |
853 | |
|
854 | 0 | out: |
855 | 0 | if((data->set.rtspreq == RTSPREQ_RECEIVE) && |
856 | 0 | (rtspc->state == RTP_PARSE_SKIP)) { |
857 | | /* In special mode RECEIVE, we just process one chunk of network |
858 | | * data, so we stop the transfer here, if we have no incomplete |
859 | | * RTP message pending. */ |
860 | 0 | data->req.download_done = TRUE; |
861 | 0 | } |
862 | 0 | return result; |
863 | 0 | } |
864 | | |
865 | | static |
866 | | CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len) |
867 | 0 | { |
868 | 0 | size_t wrote; |
869 | 0 | curl_write_callback writeit; |
870 | 0 | void *user_ptr; |
871 | |
|
872 | 0 | if(len == 0) { |
873 | 0 | failf(data, "Cannot write a 0 size RTP packet."); |
874 | 0 | return CURLE_WRITE_ERROR; |
875 | 0 | } |
876 | | |
877 | | /* If the user has configured CURLOPT_INTERLEAVEFUNCTION then use that |
878 | | function and any configured CURLOPT_INTERLEAVEDATA to write out the RTP |
879 | | data. Otherwise, use the CURLOPT_WRITEFUNCTION with the CURLOPT_WRITEDATA |
880 | | pointer to write out the RTP data. */ |
881 | 0 | if(data->set.fwrite_rtp) { |
882 | 0 | writeit = data->set.fwrite_rtp; |
883 | 0 | user_ptr = data->set.rtp_out; |
884 | 0 | } |
885 | 0 | else { |
886 | 0 | writeit = data->set.fwrite_func; |
887 | 0 | user_ptr = data->set.out; |
888 | 0 | } |
889 | |
|
890 | 0 | Curl_set_in_callback(data, true); |
891 | 0 | wrote = writeit((char *)ptr, 1, len, user_ptr); |
892 | 0 | Curl_set_in_callback(data, false); |
893 | |
|
894 | 0 | if(CURL_WRITEFUNC_PAUSE == wrote) { |
895 | 0 | failf(data, "Cannot pause RTP"); |
896 | 0 | return CURLE_WRITE_ERROR; |
897 | 0 | } |
898 | | |
899 | 0 | if(wrote != len) { |
900 | 0 | failf(data, "Failed writing RTP data"); |
901 | 0 | return CURLE_WRITE_ERROR; |
902 | 0 | } |
903 | | |
904 | 0 | return CURLE_OK; |
905 | 0 | } |
906 | | |
907 | | CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) |
908 | 0 | { |
909 | 0 | if(checkprefix("CSeq:", header)) { |
910 | 0 | long CSeq = 0; |
911 | 0 | char *endp; |
912 | 0 | char *p = &header[5]; |
913 | 0 | while(ISBLANK(*p)) |
914 | 0 | p++; |
915 | 0 | CSeq = strtol(p, &endp, 10); |
916 | 0 | if(p != endp) { |
917 | 0 | struct RTSP *rtsp = data->req.p.rtsp; |
918 | 0 | rtsp->CSeq_recv = CSeq; /* mark the request */ |
919 | 0 | data->state.rtsp_CSeq_recv = CSeq; /* update the handle */ |
920 | 0 | } |
921 | 0 | else { |
922 | 0 | failf(data, "Unable to read the CSeq header: [%s]", header); |
923 | 0 | return CURLE_RTSP_CSEQ_ERROR; |
924 | 0 | } |
925 | 0 | } |
926 | 0 | else if(checkprefix("Session:", header)) { |
927 | 0 | char *start; |
928 | 0 | char *end; |
929 | 0 | size_t idlen; |
930 | | |
931 | | /* Find the first non-space letter */ |
932 | 0 | start = header + 8; |
933 | 0 | while(*start && ISBLANK(*start)) |
934 | 0 | start++; |
935 | |
|
936 | 0 | if(!*start) { |
937 | 0 | failf(data, "Got a blank Session ID"); |
938 | 0 | return CURLE_RTSP_SESSION_ERROR; |
939 | 0 | } |
940 | | |
941 | | /* Find the end of Session ID |
942 | | * |
943 | | * Allow any non whitespace content, up to the field separator or end of |
944 | | * line. RFC 2326 isn't 100% clear on the session ID and for example |
945 | | * gstreamer does url-encoded session ID's not covered by the standard. |
946 | | */ |
947 | 0 | end = start; |
948 | 0 | while(*end && *end != ';' && !ISSPACE(*end)) |
949 | 0 | end++; |
950 | 0 | idlen = end - start; |
951 | |
|
952 | 0 | if(data->set.str[STRING_RTSP_SESSION_ID]) { |
953 | | |
954 | | /* If the Session ID is set, then compare */ |
955 | 0 | if(strlen(data->set.str[STRING_RTSP_SESSION_ID]) != idlen || |
956 | 0 | strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], idlen)) { |
957 | 0 | failf(data, "Got RTSP Session ID Line [%s], but wanted ID [%s]", |
958 | 0 | start, data->set.str[STRING_RTSP_SESSION_ID]); |
959 | 0 | return CURLE_RTSP_SESSION_ERROR; |
960 | 0 | } |
961 | 0 | } |
962 | 0 | else { |
963 | | /* If the Session ID is not set, and we find it in a response, then set |
964 | | * it. |
965 | | */ |
966 | | |
967 | | /* Copy the id substring into a new buffer */ |
968 | 0 | data->set.str[STRING_RTSP_SESSION_ID] = Curl_memdup0(start, idlen); |
969 | 0 | if(!data->set.str[STRING_RTSP_SESSION_ID]) |
970 | 0 | return CURLE_OUT_OF_MEMORY; |
971 | 0 | } |
972 | 0 | } |
973 | 0 | else if(checkprefix("Transport:", header)) { |
974 | 0 | CURLcode result; |
975 | 0 | result = rtsp_parse_transport(data, header + 10); |
976 | 0 | if(result) |
977 | 0 | return result; |
978 | 0 | } |
979 | 0 | return CURLE_OK; |
980 | 0 | } |
981 | | |
982 | | static |
983 | | CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport) |
984 | 0 | { |
985 | | /* If we receive multiple Transport response-headers, the linterleaved |
986 | | channels of each response header is recorded and used together for |
987 | | subsequent data validity checks.*/ |
988 | | /* e.g.: ' RTP/AVP/TCP;unicast;interleaved=5-6' */ |
989 | 0 | char *start; |
990 | 0 | char *end; |
991 | 0 | start = transport; |
992 | 0 | while(start && *start) { |
993 | 0 | while(*start && ISBLANK(*start) ) |
994 | 0 | start++; |
995 | 0 | end = strchr(start, ';'); |
996 | 0 | if(checkprefix("interleaved=", start)) { |
997 | 0 | long chan1, chan2, chan; |
998 | 0 | char *endp; |
999 | 0 | char *p = start + 12; |
1000 | 0 | chan1 = strtol(p, &endp, 10); |
1001 | 0 | if(p != endp && chan1 >= 0 && chan1 <= 255) { |
1002 | 0 | unsigned char *rtp_channel_mask = data->state.rtp_channel_mask; |
1003 | 0 | chan2 = chan1; |
1004 | 0 | if(*endp == '-') { |
1005 | 0 | p = endp + 1; |
1006 | 0 | chan2 = strtol(p, &endp, 10); |
1007 | 0 | if(p == endp || chan2 < 0 || chan2 > 255) { |
1008 | 0 | infof(data, "Unable to read the interleaved parameter from " |
1009 | 0 | "Transport header: [%s]", transport); |
1010 | 0 | chan2 = chan1; |
1011 | 0 | } |
1012 | 0 | } |
1013 | 0 | for(chan = chan1; chan <= chan2; chan++) { |
1014 | 0 | long idx = chan / 8; |
1015 | 0 | long off = chan % 8; |
1016 | 0 | rtp_channel_mask[idx] |= (unsigned char)(1 << off); |
1017 | 0 | } |
1018 | 0 | } |
1019 | 0 | else { |
1020 | 0 | infof(data, "Unable to read the interleaved parameter from " |
1021 | 0 | "Transport header: [%s]", transport); |
1022 | 0 | } |
1023 | 0 | break; |
1024 | 0 | } |
1025 | | /* skip to next parameter */ |
1026 | 0 | start = (!end) ? end : (end + 1); |
1027 | 0 | } |
1028 | 0 | return CURLE_OK; |
1029 | 0 | } |
1030 | | |
1031 | | |
1032 | | #endif /* CURL_DISABLE_RTSP or using Hyper */ |