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