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