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