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