• 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) && !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_CHANNEL(p)   ((int)((unsigned char)((p)[1])))
49 
50 #define RTP_PKT_LENGTH(p)  ((((int)((unsigned char)((p)[2]))) << 8) | \
51                              ((int)((unsigned char)((p)[3]))))
52 
53 /* protocol-specific functions set up to be called by the main engine */
54 static CURLcode rtsp_do(struct Curl_easy *data, bool *done);
55 static CURLcode rtsp_done(struct Curl_easy *data, CURLcode, bool premature);
56 static CURLcode rtsp_connect(struct Curl_easy *data, bool *done);
57 static CURLcode rtsp_disconnect(struct Curl_easy *data,
58                                 struct connectdata *conn, bool dead);
59 static int rtsp_getsock_do(struct Curl_easy *data,
60                            struct connectdata *conn, curl_socket_t *socks);
61 
62 /*
63  * Parse and write out any available RTP data.
64  *
65  * nread: amount of data left after k->str. will be modified if RTP
66  *        data is parsed and k->str is moved up
67  * readmore: whether or not the RTP parser needs more data right away
68  */
69 static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data,
70                                    struct connectdata *conn,
71                                    ssize_t *nread,
72                                    bool *readmore);
73 
74 static CURLcode rtsp_setup_connection(struct Curl_easy *data,
75                                       struct connectdata *conn);
76 static unsigned int rtsp_conncheck(struct Curl_easy *data,
77                                    struct connectdata *check,
78                                    unsigned int checks_to_perform);
79 
80 /* this returns the socket to wait for in the DO and DOING state for the multi
81    interface and then we're always _sending_ a request and thus we wait for
82    the single socket to become writable only */
rtsp_getsock_do(struct Curl_easy * data,struct connectdata * conn,curl_socket_t * socks)83 static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn,
84                            curl_socket_t *socks)
85 {
86   /* write mode */
87   (void)data;
88   socks[0] = conn->sock[FIRSTSOCKET];
89   return GETSOCK_WRITESOCK(0);
90 }
91 
92 static
93 CURLcode rtp_client_write(struct Curl_easy *data, char *ptr, size_t len);
94 
95 
96 /*
97  * RTSP handler interface.
98  */
99 const struct Curl_handler Curl_handler_rtsp = {
100   "RTSP",                               /* scheme */
101   rtsp_setup_connection,                /* setup_connection */
102   rtsp_do,                              /* do_it */
103   rtsp_done,                            /* done */
104   ZERO_NULL,                            /* do_more */
105   rtsp_connect,                         /* connect_it */
106   ZERO_NULL,                            /* connecting */
107   ZERO_NULL,                            /* doing */
108   ZERO_NULL,                            /* proto_getsock */
109   rtsp_getsock_do,                      /* doing_getsock */
110   ZERO_NULL,                            /* domore_getsock */
111   ZERO_NULL,                            /* perform_getsock */
112   rtsp_disconnect,                      /* disconnect */
113   rtsp_rtp_readwrite,                   /* readwrite */
114   rtsp_conncheck,                       /* connection_check */
115   ZERO_NULL,                            /* attach connection */
116   PORT_RTSP,                            /* defport */
117   CURLPROTO_RTSP,                       /* protocol */
118   CURLPROTO_RTSP,                       /* family */
119   PROTOPT_NONE                          /* flags */
120 };
121 
122 
rtsp_setup_connection(struct Curl_easy * data,struct connectdata * conn)123 static CURLcode rtsp_setup_connection(struct Curl_easy *data,
124                                       struct connectdata *conn)
125 {
126   struct RTSP *rtsp;
127   (void)conn;
128 
129   data->req.p.rtsp = rtsp = calloc(1, sizeof(struct RTSP));
130   if(!rtsp)
131     return CURLE_OUT_OF_MEMORY;
132 
133   return CURLE_OK;
134 }
135 
136 
137 /*
138  * Function to check on various aspects of a connection.
139  */
rtsp_conncheck(struct Curl_easy * data,struct connectdata * conn,unsigned int checks_to_perform)140 static unsigned int rtsp_conncheck(struct Curl_easy *data,
141                                    struct connectdata *conn,
142                                    unsigned int checks_to_perform)
143 {
144   unsigned int ret_val = CONNRESULT_NONE;
145   (void)data;
146 
147   if(checks_to_perform & CONNCHECK_ISDEAD) {
148     bool input_pending;
149     if(!Curl_conn_is_alive(data, conn, &input_pending))
150       ret_val |= CONNRESULT_DEAD;
151   }
152 
153   return ret_val;
154 }
155 
156 
rtsp_connect(struct Curl_easy * data,bool * done)157 static CURLcode rtsp_connect(struct Curl_easy *data, bool *done)
158 {
159   CURLcode httpStatus;
160 
161   httpStatus = Curl_http_connect(data, done);
162 
163   /* Initialize the CSeq if not already done */
164   if(data->state.rtsp_next_client_CSeq == 0)
165     data->state.rtsp_next_client_CSeq = 1;
166   if(data->state.rtsp_next_server_CSeq == 0)
167     data->state.rtsp_next_server_CSeq = 1;
168 
169   data->conn->proto.rtspc.rtp_channel = -1;
170 
171   return httpStatus;
172 }
173 
rtsp_disconnect(struct Curl_easy * data,struct connectdata * conn,bool dead)174 static CURLcode rtsp_disconnect(struct Curl_easy *data,
175                                 struct connectdata *conn, bool dead)
176 {
177   (void) dead;
178   (void) data;
179   Curl_safefree(conn->proto.rtspc.rtp_buf);
180   return CURLE_OK;
181 }
182 
183 
rtsp_done(struct Curl_easy * data,CURLcode status,bool premature)184 static CURLcode rtsp_done(struct Curl_easy *data,
185                           CURLcode status, bool premature)
186 {
187   struct RTSP *rtsp = data->req.p.rtsp;
188   CURLcode httpStatus;
189 
190   /* Bypass HTTP empty-reply checks on receive */
191   if(data->set.rtspreq == RTSPREQ_RECEIVE)
192     premature = TRUE;
193 
194   httpStatus = Curl_http_done(data, status, premature);
195 
196   if(rtsp && !status && !httpStatus) {
197     /* Check the sequence numbers */
198     long CSeq_sent = rtsp->CSeq_sent;
199     long CSeq_recv = rtsp->CSeq_recv;
200     if((data->set.rtspreq != RTSPREQ_RECEIVE) && (CSeq_sent != CSeq_recv)) {
201       failf(data,
202             "The CSeq of this request %ld did not match the response %ld",
203             CSeq_sent, CSeq_recv);
204       return CURLE_RTSP_CSEQ_ERROR;
205     }
206     if(data->set.rtspreq == RTSPREQ_RECEIVE &&
207             (data->conn->proto.rtspc.rtp_channel == -1)) {
208       infof(data, "Got an RTP Receive with a CSeq of %ld", CSeq_recv);
209     }
210   }
211 
212   return httpStatus;
213 }
214 
rtsp_do(struct Curl_easy * data,bool * done)215 static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
216 {
217   struct connectdata *conn = data->conn;
218   CURLcode result = CURLE_OK;
219   Curl_RtspReq rtspreq = data->set.rtspreq;
220   struct RTSP *rtsp = data->req.p.rtsp;
221   struct dynbuf req_buffer;
222   curl_off_t postsize = 0; /* for ANNOUNCE and SET_PARAMETER */
223   curl_off_t putsize = 0; /* for ANNOUNCE and SET_PARAMETER */
224 
225   const char *p_request = NULL;
226   const char *p_session_id = NULL;
227   const char *p_accept = NULL;
228   const char *p_accept_encoding = NULL;
229   const char *p_range = NULL;
230   const char *p_referrer = NULL;
231   const char *p_stream_uri = NULL;
232   const char *p_transport = NULL;
233   const char *p_uagent = NULL;
234   const char *p_proxyuserpwd = NULL;
235   const char *p_userpwd = NULL;
236 
237   *done = TRUE;
238 
239   rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq;
240   rtsp->CSeq_recv = 0;
241 
242   /* Setup the first_* fields to allow auth details get sent
243      to this origin */
244 
245   if(!data->state.first_host) {
246     data->state.first_host = strdup(conn->host.name);
247     if(!data->state.first_host)
248       return CURLE_OUT_OF_MEMORY;
249 
250     data->state.first_remote_port = conn->remote_port;
251     data->state.first_remote_protocol = conn->handler->protocol;
252   }
253 
254   /* Setup the 'p_request' pointer to the proper p_request string
255    * Since all RTSP requests are included here, there is no need to
256    * support custom requests like HTTP.
257    **/
258   data->req.no_body = TRUE; /* most requests don't contain a body */
259   switch(rtspreq) {
260   default:
261     failf(data, "Got invalid RTSP request");
262     return CURLE_BAD_FUNCTION_ARGUMENT;
263   case RTSPREQ_OPTIONS:
264     p_request = "OPTIONS";
265     break;
266   case RTSPREQ_DESCRIBE:
267     p_request = "DESCRIBE";
268     data->req.no_body = FALSE;
269     break;
270   case RTSPREQ_ANNOUNCE:
271     p_request = "ANNOUNCE";
272     break;
273   case RTSPREQ_SETUP:
274     p_request = "SETUP";
275     break;
276   case RTSPREQ_PLAY:
277     p_request = "PLAY";
278     break;
279   case RTSPREQ_PAUSE:
280     p_request = "PAUSE";
281     break;
282   case RTSPREQ_TEARDOWN:
283     p_request = "TEARDOWN";
284     break;
285   case RTSPREQ_GET_PARAMETER:
286     /* GET_PARAMETER's no_body status is determined later */
287     p_request = "GET_PARAMETER";
288     data->req.no_body = FALSE;
289     break;
290   case RTSPREQ_SET_PARAMETER:
291     p_request = "SET_PARAMETER";
292     break;
293   case RTSPREQ_RECORD:
294     p_request = "RECORD";
295     break;
296   case RTSPREQ_RECEIVE:
297     p_request = "";
298     /* Treat interleaved RTP as body */
299     data->req.no_body = FALSE;
300     break;
301   case RTSPREQ_LAST:
302     failf(data, "Got invalid RTSP request: RTSPREQ_LAST");
303     return CURLE_BAD_FUNCTION_ARGUMENT;
304   }
305 
306   if(rtspreq == RTSPREQ_RECEIVE) {
307     Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1);
308 
309     return result;
310   }
311 
312   p_session_id = data->set.str[STRING_RTSP_SESSION_ID];
313   if(!p_session_id &&
314      (rtspreq & ~(RTSPREQ_OPTIONS | RTSPREQ_DESCRIBE | RTSPREQ_SETUP))) {
315     failf(data, "Refusing to issue an RTSP request [%s] without a session ID.",
316           p_request);
317     return CURLE_BAD_FUNCTION_ARGUMENT;
318   }
319 
320   /* Stream URI. Default to server '*' if not specified */
321   if(data->set.str[STRING_RTSP_STREAM_URI]) {
322     p_stream_uri = data->set.str[STRING_RTSP_STREAM_URI];
323   }
324   else {
325     p_stream_uri = "*";
326   }
327 
328   /* Transport Header for SETUP requests */
329   p_transport = Curl_checkheaders(data, STRCONST("Transport"));
330   if(rtspreq == RTSPREQ_SETUP && !p_transport) {
331     /* New Transport: setting? */
332     if(data->set.str[STRING_RTSP_TRANSPORT]) {
333       Curl_safefree(data->state.aptr.rtsp_transport);
334 
335       data->state.aptr.rtsp_transport =
336         aprintf("Transport: %s\r\n",
337                 data->set.str[STRING_RTSP_TRANSPORT]);
338       if(!data->state.aptr.rtsp_transport)
339         return CURLE_OUT_OF_MEMORY;
340     }
341     else {
342       failf(data,
343             "Refusing to issue an RTSP SETUP without a Transport: header.");
344       return CURLE_BAD_FUNCTION_ARGUMENT;
345     }
346 
347     p_transport = data->state.aptr.rtsp_transport;
348   }
349 
350   /* Accept Headers for DESCRIBE requests */
351   if(rtspreq == RTSPREQ_DESCRIBE) {
352     /* Accept Header */
353     p_accept = Curl_checkheaders(data, STRCONST("Accept"))?
354       NULL:"Accept: application/sdp\r\n";
355 
356     /* Accept-Encoding header */
357     if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) &&
358        data->set.str[STRING_ENCODING]) {
359       Curl_safefree(data->state.aptr.accept_encoding);
360       data->state.aptr.accept_encoding =
361         aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]);
362 
363       if(!data->state.aptr.accept_encoding)
364         return CURLE_OUT_OF_MEMORY;
365 
366       p_accept_encoding = data->state.aptr.accept_encoding;
367     }
368   }
369 
370   /* The User-Agent string might have been allocated in url.c already, because
371      it might have been used in the proxy connect, but if we have got a header
372      with the user-agent string specified, we erase the previously made string
373      here. */
374   if(Curl_checkheaders(data, STRCONST("User-Agent")) &&
375      data->state.aptr.uagent) {
376     Curl_safefree(data->state.aptr.uagent);
377     data->state.aptr.uagent = NULL;
378   }
379   else if(!Curl_checkheaders(data, STRCONST("User-Agent")) &&
380           data->set.str[STRING_USERAGENT]) {
381     p_uagent = data->state.aptr.uagent;
382   }
383 
384   /* setup the authentication headers */
385   result = Curl_http_output_auth(data, conn, p_request, HTTPREQ_GET,
386                                  p_stream_uri, FALSE);
387   if(result)
388     return result;
389 
390   p_proxyuserpwd = data->state.aptr.proxyuserpwd;
391   p_userpwd = data->state.aptr.userpwd;
392 
393   /* Referrer */
394   Curl_safefree(data->state.aptr.ref);
395   if(data->state.referer && !Curl_checkheaders(data, STRCONST("Referer")))
396     data->state.aptr.ref = aprintf("Referer: %s\r\n", data->state.referer);
397   else
398     data->state.aptr.ref = NULL;
399 
400   p_referrer = data->state.aptr.ref;
401 
402   /*
403    * Range Header
404    * Only applies to PLAY, PAUSE, RECORD
405    *
406    * Go ahead and use the Range stuff supplied for HTTP
407    */
408   if(data->state.use_range &&
409      (rtspreq  & (RTSPREQ_PLAY | RTSPREQ_PAUSE | RTSPREQ_RECORD))) {
410 
411     /* Check to see if there is a range set in the custom headers */
412     if(!Curl_checkheaders(data, STRCONST("Range")) && data->state.range) {
413       Curl_safefree(data->state.aptr.rangeline);
414       data->state.aptr.rangeline = aprintf("Range: %s\r\n", data->state.range);
415       p_range = data->state.aptr.rangeline;
416     }
417   }
418 
419   /*
420    * Sanity check the custom headers
421    */
422   if(Curl_checkheaders(data, STRCONST("CSeq"))) {
423     failf(data, "CSeq cannot be set as a custom header.");
424     return CURLE_RTSP_CSEQ_ERROR;
425   }
426   if(Curl_checkheaders(data, STRCONST("Session"))) {
427     failf(data, "Session ID cannot be set as a custom header.");
428     return CURLE_BAD_FUNCTION_ARGUMENT;
429   }
430 
431   /* Initialize a dynamic send buffer */
432   Curl_dyn_init(&req_buffer, DYN_RTSP_REQ_HEADER);
433 
434   result =
435     Curl_dyn_addf(&req_buffer,
436                   "%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */
437                   "CSeq: %ld\r\n", /* CSeq */
438                   p_request, p_stream_uri, rtsp->CSeq_sent);
439   if(result)
440     return result;
441 
442   /*
443    * Rather than do a normal alloc line, keep the session_id unformatted
444    * to make comparison easier
445    */
446   if(p_session_id) {
447     result = Curl_dyn_addf(&req_buffer, "Session: %s\r\n", p_session_id);
448     if(result)
449       return result;
450   }
451 
452   /*
453    * Shared HTTP-like options
454    */
455   result = Curl_dyn_addf(&req_buffer,
456                          "%s" /* transport */
457                          "%s" /* accept */
458                          "%s" /* accept-encoding */
459                          "%s" /* range */
460                          "%s" /* referrer */
461                          "%s" /* user-agent */
462                          "%s" /* proxyuserpwd */
463                          "%s" /* userpwd */
464                          ,
465                          p_transport ? p_transport : "",
466                          p_accept ? p_accept : "",
467                          p_accept_encoding ? p_accept_encoding : "",
468                          p_range ? p_range : "",
469                          p_referrer ? p_referrer : "",
470                          p_uagent ? p_uagent : "",
471                          p_proxyuserpwd ? p_proxyuserpwd : "",
472                          p_userpwd ? p_userpwd : "");
473 
474   /*
475    * Free userpwd now --- cannot reuse this for Negotiate and possibly NTLM
476    * with basic and digest, it will be freed anyway by the next request
477    */
478   Curl_safefree(data->state.aptr.userpwd);
479   data->state.aptr.userpwd = NULL;
480 
481   if(result)
482     return result;
483 
484   if((rtspreq == RTSPREQ_SETUP) || (rtspreq == RTSPREQ_DESCRIBE)) {
485     result = Curl_add_timecondition(data, &req_buffer);
486     if(result)
487       return result;
488   }
489 
490   result = Curl_add_custom_headers(data, FALSE, &req_buffer);
491   if(result)
492     return result;
493 
494   if(rtspreq == RTSPREQ_ANNOUNCE ||
495      rtspreq == RTSPREQ_SET_PARAMETER ||
496      rtspreq == RTSPREQ_GET_PARAMETER) {
497 
498     if(data->set.upload) {
499       putsize = data->state.infilesize;
500       data->state.httpreq = HTTPREQ_PUT;
501 
502     }
503     else {
504       postsize = (data->state.infilesize != -1)?
505         data->state.infilesize:
506         (data->set.postfields? (curl_off_t)strlen(data->set.postfields):0);
507       data->state.httpreq = HTTPREQ_POST;
508     }
509 
510     if(putsize > 0 || postsize > 0) {
511       /* As stated in the http comments, it is probably not wise to
512        * actually set a custom Content-Length in the headers */
513       if(!Curl_checkheaders(data, STRCONST("Content-Length"))) {
514         result =
515           Curl_dyn_addf(&req_buffer,
516                         "Content-Length: %" CURL_FORMAT_CURL_OFF_T"\r\n",
517                         (data->set.upload ? putsize : postsize));
518         if(result)
519           return result;
520       }
521 
522       if(rtspreq == RTSPREQ_SET_PARAMETER ||
523          rtspreq == RTSPREQ_GET_PARAMETER) {
524         if(!Curl_checkheaders(data, STRCONST("Content-Type"))) {
525           result = Curl_dyn_addn(&req_buffer,
526                                  STRCONST("Content-Type: "
527                                           "text/parameters\r\n"));
528           if(result)
529             return result;
530         }
531       }
532 
533       if(rtspreq == RTSPREQ_ANNOUNCE) {
534         if(!Curl_checkheaders(data, STRCONST("Content-Type"))) {
535           result = Curl_dyn_addn(&req_buffer,
536                                  STRCONST("Content-Type: "
537                                           "application/sdp\r\n"));
538           if(result)
539             return result;
540         }
541       }
542 
543       data->state.expect100header = FALSE; /* RTSP posts are simple/small */
544     }
545     else if(rtspreq == RTSPREQ_GET_PARAMETER) {
546       /* Check for an empty GET_PARAMETER (heartbeat) request */
547       data->state.httpreq = HTTPREQ_HEAD;
548       data->req.no_body = TRUE;
549     }
550   }
551 
552   /* RTSP never allows chunked transfer */
553   data->req.forbidchunk = TRUE;
554   /* Finish the request buffer */
555   result = Curl_dyn_addn(&req_buffer, STRCONST("\r\n"));
556   if(result)
557     return result;
558 
559   if(postsize > 0) {
560     result = Curl_dyn_addn(&req_buffer, data->set.postfields,
561                            (size_t)postsize);
562     if(result)
563       return result;
564   }
565 
566   /* issue the request */
567   result = Curl_buffer_send(&req_buffer, data, data->req.p.http,
568                             &data->info.request_size, 0, FIRSTSOCKET);
569   if(result) {
570     failf(data, "Failed sending RTSP request");
571     return result;
572   }
573 
574   Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, putsize?FIRSTSOCKET:-1);
575 
576   /* Increment the CSeq on success */
577   data->state.rtsp_next_client_CSeq++;
578 
579   if(data->req.writebytecount) {
580     /* if a request-body has been sent off, we make sure this progress is
581        noted properly */
582     Curl_pgrsSetUploadCounter(data, data->req.writebytecount);
583     if(Curl_pgrsUpdate(data))
584       result = CURLE_ABORTED_BY_CALLBACK;
585   }
586 
587   return result;
588 }
589 
590 
rtsp_rtp_readwrite(struct Curl_easy * data,struct connectdata * conn,ssize_t * nread,bool * readmore)591 static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data,
592                                    struct connectdata *conn,
593                                    ssize_t *nread,
594                                    bool *readmore) {
595   struct SingleRequest *k = &data->req;
596   struct rtsp_conn *rtspc = &(conn->proto.rtspc);
597 
598   char *rtp; /* moving pointer to rtp data */
599   ssize_t rtp_dataleft; /* how much data left to parse in this round */
600   char *scratch;
601   CURLcode result;
602 
603   if(rtspc->rtp_buf) {
604     /* There was some leftover data the last time. Merge buffers */
605     char *newptr = Curl_saferealloc(rtspc->rtp_buf,
606                                     rtspc->rtp_bufsize + *nread);
607     if(!newptr) {
608       rtspc->rtp_buf = NULL;
609       rtspc->rtp_bufsize = 0;
610       return CURLE_OUT_OF_MEMORY;
611     }
612     rtspc->rtp_buf = newptr;
613     memcpy(rtspc->rtp_buf + rtspc->rtp_bufsize, k->str, *nread);
614     rtspc->rtp_bufsize += *nread;
615     rtp = rtspc->rtp_buf;
616     rtp_dataleft = rtspc->rtp_bufsize;
617   }
618   else {
619     /* Just parse the request buffer directly */
620     rtp = k->str;
621     rtp_dataleft = *nread;
622   }
623 
624   while((rtp_dataleft > 0) &&
625         (rtp[0] == '$')) {
626     if(rtp_dataleft > 4) {
627       int rtp_length;
628 
629       /* Parse the header */
630       /* The channel identifier immediately follows and is 1 byte */
631       rtspc->rtp_channel = RTP_PKT_CHANNEL(rtp);
632 
633       /* The length is two bytes */
634       rtp_length = RTP_PKT_LENGTH(rtp);
635 
636       if(rtp_dataleft < rtp_length + 4) {
637         /* Need more - incomplete payload */
638         *readmore = TRUE;
639         break;
640       }
641       /* We have the full RTP interleaved packet
642        * Write out the header including the leading '$' */
643       DEBUGF(infof(data, "RTP write channel %d rtp_length %d",
644              rtspc->rtp_channel, rtp_length));
645       result = rtp_client_write(data, &rtp[0], rtp_length + 4);
646       if(result) {
647         failf(data, "Got an error writing an RTP packet");
648         *readmore = FALSE;
649         Curl_safefree(rtspc->rtp_buf);
650         rtspc->rtp_buf = NULL;
651         rtspc->rtp_bufsize = 0;
652         return result;
653       }
654 
655       /* Move forward in the buffer */
656       rtp_dataleft -= rtp_length + 4;
657       rtp += rtp_length + 4;
658 
659       if(data->set.rtspreq == RTSPREQ_RECEIVE) {
660         /* If we are in a passive receive, give control back
661          * to the app as often as we can.
662          */
663         k->keepon &= ~KEEP_RECV;
664       }
665     }
666     else {
667       /* Need more - incomplete header */
668       *readmore = TRUE;
669       break;
670     }
671   }
672 
673   if(rtp_dataleft && rtp[0] == '$') {
674     DEBUGF(infof(data, "RTP Rewinding %zd %s", rtp_dataleft,
675           *readmore ? "(READMORE)" : ""));
676 
677     /* Store the incomplete RTP packet for a "rewind" */
678     scratch = malloc(rtp_dataleft);
679     if(!scratch) {
680       Curl_safefree(rtspc->rtp_buf);
681       rtspc->rtp_buf = NULL;
682       rtspc->rtp_bufsize = 0;
683       return CURLE_OUT_OF_MEMORY;
684     }
685     memcpy(scratch, rtp, rtp_dataleft);
686     Curl_safefree(rtspc->rtp_buf);
687     rtspc->rtp_buf = scratch;
688     rtspc->rtp_bufsize = rtp_dataleft;
689 
690     /* As far as the transfer is concerned, this data is consumed */
691     *nread = 0;
692     return CURLE_OK;
693   }
694   /* Fix up k->str to point just after the last RTP packet */
695   k->str += *nread - rtp_dataleft;
696 
697   /* either all of the data has been read or...
698    * rtp now points at the next byte to parse
699    */
700   if(rtp_dataleft > 0)
701     DEBUGASSERT(k->str[0] == rtp[0]);
702 
703   DEBUGASSERT(rtp_dataleft <= *nread); /* sanity check */
704 
705   *nread = rtp_dataleft;
706 
707   /* If we get here, we have finished with the leftover/merge buffer */
708   Curl_safefree(rtspc->rtp_buf);
709   rtspc->rtp_buf = NULL;
710   rtspc->rtp_bufsize = 0;
711 
712   return CURLE_OK;
713 }
714 
715 static
rtp_client_write(struct Curl_easy * data,char * ptr,size_t len)716 CURLcode rtp_client_write(struct Curl_easy *data, char *ptr, size_t len)
717 {
718   size_t wrote;
719   curl_write_callback writeit;
720   void *user_ptr;
721 
722   if(len == 0) {
723     failf(data, "Cannot write a 0 size RTP packet.");
724     return CURLE_WRITE_ERROR;
725   }
726 
727   /* If the user has configured CURLOPT_INTERLEAVEFUNCTION then use that
728      function and any configured CURLOPT_INTERLEAVEDATA to write out the RTP
729      data. Otherwise, use the CURLOPT_WRITEFUNCTION with the CURLOPT_WRITEDATA
730      pointer to write out the RTP data. */
731   if(data->set.fwrite_rtp) {
732     writeit = data->set.fwrite_rtp;
733     user_ptr = data->set.rtp_out;
734   }
735   else {
736     writeit = data->set.fwrite_func;
737     user_ptr = data->set.out;
738   }
739 
740   Curl_set_in_callback(data, true);
741   wrote = writeit(ptr, 1, len, user_ptr);
742   Curl_set_in_callback(data, false);
743 
744   if(CURL_WRITEFUNC_PAUSE == wrote) {
745     failf(data, "Cannot pause RTP");
746     return CURLE_WRITE_ERROR;
747   }
748 
749   if(wrote != len) {
750     failf(data, "Failed writing RTP data");
751     return CURLE_WRITE_ERROR;
752   }
753 
754   return CURLE_OK;
755 }
756 
Curl_rtsp_parseheader(struct Curl_easy * data,char * header)757 CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header)
758 {
759   if(checkprefix("CSeq:", header)) {
760     long CSeq = 0;
761     char *endp;
762     char *p = &header[5];
763     while(ISBLANK(*p))
764       p++;
765     CSeq = strtol(p, &endp, 10);
766     if(p != endp) {
767       struct RTSP *rtsp = data->req.p.rtsp;
768       rtsp->CSeq_recv = CSeq; /* mark the request */
769       data->state.rtsp_CSeq_recv = CSeq; /* update the handle */
770     }
771     else {
772       failf(data, "Unable to read the CSeq header: [%s]", header);
773       return CURLE_RTSP_CSEQ_ERROR;
774     }
775   }
776   else if(checkprefix("Session:", header)) {
777     char *start;
778     char *end;
779     size_t idlen;
780 
781     /* Find the first non-space letter */
782     start = header + 8;
783     while(*start && ISBLANK(*start))
784       start++;
785 
786     if(!*start) {
787       failf(data, "Got a blank Session ID");
788       return CURLE_RTSP_SESSION_ERROR;
789     }
790 
791     /* Find the end of Session ID
792      *
793      * Allow any non whitespace content, up to the field separator or end of
794      * line. RFC 2326 isn't 100% clear on the session ID and for example
795      * gstreamer does url-encoded session ID's not covered by the standard.
796      */
797     end = start;
798     while(*end && *end != ';' && !ISSPACE(*end))
799       end++;
800     idlen = end - start;
801 
802     if(data->set.str[STRING_RTSP_SESSION_ID]) {
803 
804       /* If the Session ID is set, then compare */
805       if(strlen(data->set.str[STRING_RTSP_SESSION_ID]) != idlen ||
806          strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], idlen) != 0) {
807         failf(data, "Got RTSP Session ID Line [%s], but wanted ID [%s]",
808               start, data->set.str[STRING_RTSP_SESSION_ID]);
809         return CURLE_RTSP_SESSION_ERROR;
810       }
811     }
812     else {
813       /* If the Session ID is not set, and we find it in a response, then set
814        * it.
815        */
816 
817       /* Copy the id substring into a new buffer */
818       data->set.str[STRING_RTSP_SESSION_ID] = malloc(idlen + 1);
819       if(!data->set.str[STRING_RTSP_SESSION_ID])
820         return CURLE_OUT_OF_MEMORY;
821       memcpy(data->set.str[STRING_RTSP_SESSION_ID], start, idlen);
822       (data->set.str[STRING_RTSP_SESSION_ID])[idlen] = '\0';
823     }
824   }
825   return CURLE_OK;
826 }
827 
828 #endif /* CURL_DISABLE_RTSP or using Hyper */
829