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