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