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