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