quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

rtsp.c (34173B)


      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
      9  *
     10  * This software is licensed as described in the file COPYING, which
     11  * you should have received as part of this distribution. The terms
     12  * are also available at https://curl.se/docs/copyright.html.
     13  *
     14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
     15  * copies of the Software, and permit persons to whom the Software is
     16  * furnished to do so, under the terms of the COPYING file.
     17  *
     18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     19  * KIND, either express or implied.
     20  *
     21  * SPDX-License-Identifier: curl
     22  *
     23  ***************************************************************************/
     24 
     25 #include "curl_setup.h"
     26 
     27 #if !defined(CURL_DISABLE_RTSP)
     28 
     29 #include "urldata.h"
     30 #include <curl/curl.h>
     31 #include "transfer.h"
     32 #include "sendf.h"
     33 #include "multiif.h"
     34 #include "http.h"
     35 #include "url.h"
     36 #include "progress.h"
     37 #include "rtsp.h"
     38 #include "strcase.h"
     39 #include "select.h"
     40 #include "connect.h"
     41 #include "cfilters.h"
     42 #include "strdup.h"
     43 #include "curlx/strparse.h"
     44 /* The last 3 #include files should be in this order */
     45 #include "curl_printf.h"
     46 #include "curl_memory.h"
     47 #include "memdebug.h"
     48 
     49 
     50 /* meta key for storing protocol meta at easy handle */
     51 #define CURL_META_RTSP_EASY   "meta:proto:rtsp:easy"
     52 /* meta key for storing protocol meta at connection */
     53 #define CURL_META_RTSP_CONN   "meta:proto:rtsp:conn"
     54 
     55 typedef enum {
     56   RTP_PARSE_SKIP,
     57   RTP_PARSE_CHANNEL,
     58   RTP_PARSE_LEN,
     59   RTP_PARSE_DATA
     60 } rtp_parse_st;
     61 
     62 /* RTSP Connection data
     63  * Currently, only used for tracking incomplete RTP data reads */
     64 struct rtsp_conn {
     65   struct dynbuf buf;
     66   int rtp_channel;
     67   size_t rtp_len;
     68   rtp_parse_st state;
     69   BIT(in_header);
     70 };
     71 
     72 /* RTSP transfer data */
     73 struct RTSP {
     74   long CSeq_sent; /* CSeq of this request */
     75   long CSeq_recv; /* CSeq received */
     76 };
     77 
     78 
     79 #define RTP_PKT_LENGTH(p) ((((unsigned int)((unsigned char)((p)[2]))) << 8) | \
     80                             ((unsigned int)((unsigned char)((p)[3]))))
     81 
     82 /* protocol-specific functions set up to be called by the main engine */
     83 static CURLcode rtsp_do(struct Curl_easy *data, bool *done);
     84 static CURLcode rtsp_done(struct Curl_easy *data, CURLcode, bool premature);
     85 static CURLcode rtsp_connect(struct Curl_easy *data, bool *done);
     86 static int rtsp_getsock_do(struct Curl_easy *data,
     87                            struct connectdata *conn, curl_socket_t *socks);
     88 
     89 /*
     90  * Parse and write out an RTSP response.
     91  * @param data     the transfer
     92  * @param conn     the connection
     93  * @param buf      data read from connection
     94  * @param blen     amount of data in buf
     95  * @param is_eos   TRUE iff this is the last write
     96  * @param readmore out, TRUE iff complete buf was consumed and more data
     97  *                 is needed
     98  */
     99 static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data,
    100                                     const char *buf,
    101                                     size_t blen,
    102                                     bool is_eos);
    103 
    104 static CURLcode rtsp_setup_connection(struct Curl_easy *data,
    105                                       struct connectdata *conn);
    106 static unsigned int rtsp_conncheck(struct Curl_easy *data,
    107                                    struct connectdata *check,
    108                                    unsigned int checks_to_perform);
    109 
    110 /* this returns the socket to wait for in the DO and DOING state for the multi
    111    interface and then we are always _sending_ a request and thus we wait for
    112    the single socket to become writable only */
    113 static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn,
    114                            curl_socket_t *socks)
    115 {
    116   /* write mode */
    117   (void)data;
    118   socks[0] = conn->sock[FIRSTSOCKET];
    119   return GETSOCK_WRITESOCK(0);
    120 }
    121 
    122 static
    123 CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len);
    124 static
    125 CURLcode rtsp_parse_transport(struct Curl_easy *data, const char *transport);
    126 
    127 
    128 /*
    129  * RTSP handler interface.
    130  */
    131 const struct Curl_handler Curl_handler_rtsp = {
    132   "rtsp",                               /* scheme */
    133   rtsp_setup_connection,                /* setup_connection */
    134   rtsp_do,                              /* do_it */
    135   rtsp_done,                            /* done */
    136   ZERO_NULL,                            /* do_more */
    137   rtsp_connect,                         /* connect_it */
    138   ZERO_NULL,                            /* connecting */
    139   ZERO_NULL,                            /* doing */
    140   ZERO_NULL,                            /* proto_getsock */
    141   rtsp_getsock_do,                      /* doing_getsock */
    142   ZERO_NULL,                            /* domore_getsock */
    143   ZERO_NULL,                            /* perform_getsock */
    144   ZERO_NULL,                            /* disconnect */
    145   rtsp_rtp_write_resp,                  /* write_resp */
    146   ZERO_NULL,                            /* write_resp_hd */
    147   rtsp_conncheck,                       /* connection_check */
    148   ZERO_NULL,                            /* attach connection */
    149   Curl_http_follow,                     /* follow */
    150   PORT_RTSP,                            /* defport */
    151   CURLPROTO_RTSP,                       /* protocol */
    152   CURLPROTO_RTSP,                       /* family */
    153   PROTOPT_NONE                          /* flags */
    154 };
    155 
    156 #define MAX_RTP_BUFFERSIZE 1000000 /* arbitrary */
    157 
    158 static void rtsp_easy_dtor(void *key, size_t klen, void *entry)
    159 {
    160   struct RTSP *rtsp = entry;
    161   (void)key;
    162   (void)klen;
    163   free(rtsp);
    164 }
    165 
    166 static void rtsp_conn_dtor(void *key, size_t klen, void *entry)
    167 {
    168   struct rtsp_conn *rtspc = entry;
    169   (void)key;
    170   (void)klen;
    171   curlx_dyn_free(&rtspc->buf);
    172   free(rtspc);
    173 }
    174 
    175 static CURLcode rtsp_setup_connection(struct Curl_easy *data,
    176                                       struct connectdata *conn)
    177 {
    178   struct rtsp_conn *rtspc;
    179   struct RTSP *rtsp;
    180 
    181   rtspc = calloc(1, sizeof(*rtspc));
    182   if(!rtspc)
    183     return CURLE_OUT_OF_MEMORY;
    184   curlx_dyn_init(&rtspc->buf, MAX_RTP_BUFFERSIZE);
    185   if(Curl_conn_meta_set(conn, CURL_META_RTSP_CONN, rtspc, rtsp_conn_dtor))
    186     return CURLE_OUT_OF_MEMORY;
    187 
    188   rtsp = calloc(1, sizeof(struct RTSP));
    189   if(!rtsp ||
    190      Curl_meta_set(data, CURL_META_RTSP_EASY, rtsp, rtsp_easy_dtor))
    191     return CURLE_OUT_OF_MEMORY;
    192 
    193   return CURLE_OK;
    194 }
    195 
    196 
    197 /*
    198  * Function to check on various aspects of a connection.
    199  */
    200 static unsigned int rtsp_conncheck(struct Curl_easy *data,
    201                                    struct connectdata *conn,
    202                                    unsigned int checks_to_perform)
    203 {
    204   unsigned int ret_val = CONNRESULT_NONE;
    205   (void)data;
    206 
    207   if(checks_to_perform & CONNCHECK_ISDEAD) {
    208     bool input_pending;
    209     if(!Curl_conn_is_alive(data, conn, &input_pending))
    210       ret_val |= CONNRESULT_DEAD;
    211   }
    212 
    213   return ret_val;
    214 }
    215 
    216 
    217 static CURLcode rtsp_connect(struct Curl_easy *data, bool *done)
    218 {
    219   struct rtsp_conn *rtspc =
    220     Curl_conn_meta_get(data->conn, CURL_META_RTSP_CONN);
    221   CURLcode httpStatus;
    222 
    223   if(!rtspc)
    224     return CURLE_FAILED_INIT;
    225 
    226   httpStatus = Curl_http_connect(data, done);
    227 
    228   /* Initialize the CSeq if not already done */
    229   if(data->state.rtsp_next_client_CSeq == 0)
    230     data->state.rtsp_next_client_CSeq = 1;
    231   if(data->state.rtsp_next_server_CSeq == 0)
    232     data->state.rtsp_next_server_CSeq = 1;
    233 
    234   rtspc->rtp_channel = -1;
    235 
    236   return httpStatus;
    237 }
    238 
    239 static CURLcode rtsp_done(struct Curl_easy *data,
    240                           CURLcode status, bool premature)
    241 {
    242   struct rtsp_conn *rtspc =
    243     Curl_conn_meta_get(data->conn, CURL_META_RTSP_CONN);
    244   struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY);
    245   CURLcode httpStatus;
    246 
    247   if(!rtspc || !rtsp)
    248     return CURLE_FAILED_INIT;
    249 
    250   /* Bypass HTTP empty-reply checks on receive */
    251   if(data->set.rtspreq == RTSPREQ_RECEIVE)
    252     premature = TRUE;
    253 
    254   httpStatus = Curl_http_done(data, status, premature);
    255 
    256   if(!status && !httpStatus) {
    257     /* Check the sequence numbers */
    258     long CSeq_sent = rtsp->CSeq_sent;
    259     long CSeq_recv = rtsp->CSeq_recv;
    260     if((data->set.rtspreq != RTSPREQ_RECEIVE) && (CSeq_sent != CSeq_recv)) {
    261       failf(data,
    262             "The CSeq of this request %ld did not match the response %ld",
    263             CSeq_sent, CSeq_recv);
    264       return CURLE_RTSP_CSEQ_ERROR;
    265     }
    266     if(data->set.rtspreq == RTSPREQ_RECEIVE && (rtspc->rtp_channel == -1)) {
    267       infof(data, "Got an RTP Receive with a CSeq of %ld", CSeq_recv);
    268     }
    269     if(data->set.rtspreq == RTSPREQ_RECEIVE &&
    270        data->req.eos_written) {
    271       failf(data, "Server prematurely closed the RTSP connection.");
    272       return CURLE_RECV_ERROR;
    273     }
    274   }
    275 
    276   return httpStatus;
    277 }
    278 
    279 static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
    280 {
    281   struct connectdata *conn = data->conn;
    282   CURLcode result = CURLE_OK;
    283   Curl_RtspReq rtspreq = data->set.rtspreq;
    284   struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY);
    285   struct dynbuf req_buffer;
    286   unsigned char httpversion = 11; /* RTSP is close to HTTP/1.1, sort of... */
    287 
    288   const char *p_request = NULL;
    289   const char *p_session_id = NULL;
    290   const char *p_accept = NULL;
    291   const char *p_accept_encoding = NULL;
    292   const char *p_range = NULL;
    293   const char *p_referrer = NULL;
    294   const char *p_stream_uri = NULL;
    295   const char *p_transport = NULL;
    296   const char *p_uagent = NULL;
    297   const char *p_proxyuserpwd = NULL;
    298   const char *p_userpwd = NULL;
    299 
    300   *done = TRUE;
    301   if(!rtsp)
    302     return CURLE_FAILED_INIT;
    303 
    304   /* Initialize a dynamic send buffer */
    305   curlx_dyn_init(&req_buffer, DYN_RTSP_REQ_HEADER);
    306 
    307   rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq;
    308   rtsp->CSeq_recv = 0;
    309 
    310   /* Setup the first_* fields to allow auth details get sent
    311      to this origin */
    312 
    313   if(!data->state.first_host) {
    314     data->state.first_host = strdup(conn->host.name);
    315     if(!data->state.first_host)
    316       return CURLE_OUT_OF_MEMORY;
    317 
    318     data->state.first_remote_port = conn->remote_port;
    319     data->state.first_remote_protocol = conn->handler->protocol;
    320   }
    321 
    322   /* Setup the 'p_request' pointer to the proper p_request string
    323    * Since all RTSP requests are included here, there is no need to
    324    * support custom requests like HTTP.
    325    **/
    326   data->req.no_body = TRUE; /* most requests do not contain a body */
    327   switch(rtspreq) {
    328   default:
    329     failf(data, "Got invalid RTSP request");
    330     return CURLE_BAD_FUNCTION_ARGUMENT;
    331   case RTSPREQ_OPTIONS:
    332     p_request = "OPTIONS";
    333     break;
    334   case RTSPREQ_DESCRIBE:
    335     p_request = "DESCRIBE";
    336     data->req.no_body = FALSE;
    337     break;
    338   case RTSPREQ_ANNOUNCE:
    339     p_request = "ANNOUNCE";
    340     break;
    341   case RTSPREQ_SETUP:
    342     p_request = "SETUP";
    343     break;
    344   case RTSPREQ_PLAY:
    345     p_request = "PLAY";
    346     break;
    347   case RTSPREQ_PAUSE:
    348     p_request = "PAUSE";
    349     break;
    350   case RTSPREQ_TEARDOWN:
    351     p_request = "TEARDOWN";
    352     break;
    353   case RTSPREQ_GET_PARAMETER:
    354     /* GET_PARAMETER's no_body status is determined later */
    355     p_request = "GET_PARAMETER";
    356     data->req.no_body = FALSE;
    357     break;
    358   case RTSPREQ_SET_PARAMETER:
    359     p_request = "SET_PARAMETER";
    360     break;
    361   case RTSPREQ_RECORD:
    362     p_request = "RECORD";
    363     break;
    364   case RTSPREQ_RECEIVE:
    365     p_request = "";
    366     /* Treat interleaved RTP as body */
    367     data->req.no_body = FALSE;
    368     break;
    369   case RTSPREQ_LAST:
    370     failf(data, "Got invalid RTSP request: RTSPREQ_LAST");
    371     return CURLE_BAD_FUNCTION_ARGUMENT;
    372   }
    373 
    374   if(rtspreq == RTSPREQ_RECEIVE) {
    375     Curl_xfer_setup1(data, CURL_XFER_RECV, -1, TRUE);
    376     goto out;
    377   }
    378 
    379   p_session_id = data->set.str[STRING_RTSP_SESSION_ID];
    380   if(!p_session_id &&
    381      (rtspreq & ~(Curl_RtspReq)(RTSPREQ_OPTIONS |
    382                                 RTSPREQ_DESCRIBE |
    383                                 RTSPREQ_SETUP))) {
    384     failf(data, "Refusing to issue an RTSP request [%s] without a session ID.",
    385           p_request);
    386     result = CURLE_BAD_FUNCTION_ARGUMENT;
    387     goto out;
    388   }
    389 
    390   /* Stream URI. Default to server '*' if not specified */
    391   if(data->set.str[STRING_RTSP_STREAM_URI]) {
    392     p_stream_uri = data->set.str[STRING_RTSP_STREAM_URI];
    393   }
    394   else {
    395     p_stream_uri = "*";
    396   }
    397 
    398   /* Transport Header for SETUP requests */
    399   p_transport = Curl_checkheaders(data, STRCONST("Transport"));
    400   if(rtspreq == RTSPREQ_SETUP && !p_transport) {
    401     /* New Transport: setting? */
    402     if(data->set.str[STRING_RTSP_TRANSPORT]) {
    403       free(data->state.aptr.rtsp_transport);
    404       data->state.aptr.rtsp_transport =
    405         aprintf("Transport: %s\r\n",
    406                 data->set.str[STRING_RTSP_TRANSPORT]);
    407       if(!data->state.aptr.rtsp_transport)
    408         return CURLE_OUT_OF_MEMORY;
    409     }
    410     else {
    411       failf(data,
    412             "Refusing to issue an RTSP SETUP without a Transport: header.");
    413       result = CURLE_BAD_FUNCTION_ARGUMENT;
    414       goto out;
    415     }
    416 
    417     p_transport = data->state.aptr.rtsp_transport;
    418   }
    419 
    420   /* Accept Headers for DESCRIBE requests */
    421   if(rtspreq == RTSPREQ_DESCRIBE) {
    422     /* Accept Header */
    423     p_accept = Curl_checkheaders(data, STRCONST("Accept")) ?
    424       NULL : "Accept: application/sdp\r\n";
    425 
    426     /* Accept-Encoding header */
    427     if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) &&
    428        data->set.str[STRING_ENCODING]) {
    429       free(data->state.aptr.accept_encoding);
    430       data->state.aptr.accept_encoding =
    431         aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]);
    432 
    433       if(!data->state.aptr.accept_encoding) {
    434         result = CURLE_OUT_OF_MEMORY;
    435         goto out;
    436       }
    437       p_accept_encoding = data->state.aptr.accept_encoding;
    438     }
    439   }
    440 
    441   /* The User-Agent string might have been allocated in url.c already, because
    442      it might have been used in the proxy connect, but if we have got a header
    443      with the user-agent string specified, we erase the previously made string
    444      here. */
    445   if(Curl_checkheaders(data, STRCONST("User-Agent")) &&
    446      data->state.aptr.uagent) {
    447     Curl_safefree(data->state.aptr.uagent);
    448   }
    449   else if(!Curl_checkheaders(data, STRCONST("User-Agent")) &&
    450           data->set.str[STRING_USERAGENT]) {
    451     p_uagent = data->state.aptr.uagent;
    452   }
    453 
    454   /* setup the authentication headers */
    455   result = Curl_http_output_auth(data, conn, p_request, HTTPREQ_GET,
    456                                  p_stream_uri, FALSE);
    457   if(result)
    458     goto out;
    459 
    460 #ifndef CURL_DISABLE_PROXY
    461   p_proxyuserpwd = data->state.aptr.proxyuserpwd;
    462 #endif
    463   p_userpwd = data->state.aptr.userpwd;
    464 
    465   /* Referrer */
    466   Curl_safefree(data->state.aptr.ref);
    467   if(data->state.referer && !Curl_checkheaders(data, STRCONST("Referer")))
    468     data->state.aptr.ref = aprintf("Referer: %s\r\n", data->state.referer);
    469 
    470   p_referrer = data->state.aptr.ref;
    471 
    472   /*
    473    * Range Header
    474    * Only applies to PLAY, PAUSE, RECORD
    475    *
    476    * Go ahead and use the Range stuff supplied for HTTP
    477    */
    478   if(data->state.use_range &&
    479      (rtspreq  & (RTSPREQ_PLAY | RTSPREQ_PAUSE | RTSPREQ_RECORD))) {
    480 
    481     /* Check to see if there is a range set in the custom headers */
    482     if(!Curl_checkheaders(data, STRCONST("Range")) && data->state.range) {
    483       free(data->state.aptr.rangeline);
    484       data->state.aptr.rangeline = aprintf("Range: %s\r\n", data->state.range);
    485       p_range = data->state.aptr.rangeline;
    486     }
    487   }
    488 
    489   /*
    490    * Sanity check the custom headers
    491    */
    492   if(Curl_checkheaders(data, STRCONST("CSeq"))) {
    493     failf(data, "CSeq cannot be set as a custom header.");
    494     result = CURLE_RTSP_CSEQ_ERROR;
    495     goto out;
    496   }
    497   if(Curl_checkheaders(data, STRCONST("Session"))) {
    498     failf(data, "Session ID cannot be set as a custom header.");
    499     result = CURLE_BAD_FUNCTION_ARGUMENT;
    500     goto out;
    501   }
    502 
    503   result =
    504     curlx_dyn_addf(&req_buffer,
    505                    "%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */
    506                    "CSeq: %ld\r\n", /* CSeq */
    507                    p_request, p_stream_uri, rtsp->CSeq_sent);
    508   if(result)
    509     goto out;
    510 
    511   /*
    512    * Rather than do a normal alloc line, keep the session_id unformatted
    513    * to make comparison easier
    514    */
    515   if(p_session_id) {
    516     result = curlx_dyn_addf(&req_buffer, "Session: %s\r\n", p_session_id);
    517     if(result)
    518       goto out;
    519   }
    520 
    521   /*
    522    * Shared HTTP-like options
    523    */
    524   result = curlx_dyn_addf(&req_buffer,
    525                           "%s" /* transport */
    526                           "%s" /* accept */
    527                           "%s" /* accept-encoding */
    528                           "%s" /* range */
    529                           "%s" /* referrer */
    530                           "%s" /* user-agent */
    531                           "%s" /* proxyuserpwd */
    532                           "%s" /* userpwd */
    533                           ,
    534                           p_transport ? p_transport : "",
    535                           p_accept ? p_accept : "",
    536                           p_accept_encoding ? p_accept_encoding : "",
    537                           p_range ? p_range : "",
    538                           p_referrer ? p_referrer : "",
    539                           p_uagent ? p_uagent : "",
    540                           p_proxyuserpwd ? p_proxyuserpwd : "",
    541                           p_userpwd ? p_userpwd : "");
    542 
    543   /*
    544    * Free userpwd now --- cannot reuse this for Negotiate and possibly NTLM
    545    * with basic and digest, it will be freed anyway by the next request
    546    */
    547   Curl_safefree(data->state.aptr.userpwd);
    548 
    549   if(result)
    550     goto out;
    551 
    552   if((rtspreq == RTSPREQ_SETUP) || (rtspreq == RTSPREQ_DESCRIBE)) {
    553     result = Curl_add_timecondition(data, &req_buffer);
    554     if(result)
    555       goto out;
    556   }
    557 
    558   result = Curl_add_custom_headers(data, FALSE, httpversion, &req_buffer);
    559   if(result)
    560     goto out;
    561 
    562   if(rtspreq == RTSPREQ_ANNOUNCE ||
    563      rtspreq == RTSPREQ_SET_PARAMETER ||
    564      rtspreq == RTSPREQ_GET_PARAMETER) {
    565     curl_off_t req_clen; /* request content length */
    566 
    567     if(data->state.upload) {
    568       req_clen = data->state.infilesize;
    569       data->state.httpreq = HTTPREQ_PUT;
    570       result = Curl_creader_set_fread(data, req_clen);
    571       if(result)
    572         goto out;
    573     }
    574     else {
    575       if(data->set.postfields) {
    576         size_t plen = strlen(data->set.postfields);
    577         req_clen = (curl_off_t)plen;
    578         result = Curl_creader_set_buf(data, data->set.postfields, plen);
    579       }
    580       else if(data->state.infilesize >= 0) {
    581         req_clen = data->state.infilesize;
    582         result = Curl_creader_set_fread(data, req_clen);
    583       }
    584       else {
    585         req_clen = 0;
    586         result = Curl_creader_set_null(data);
    587       }
    588       if(result)
    589         goto out;
    590     }
    591 
    592     if(req_clen > 0) {
    593       /* As stated in the http comments, it is probably not wise to
    594        * actually set a custom Content-Length in the headers */
    595       if(!Curl_checkheaders(data, STRCONST("Content-Length"))) {
    596         result =
    597           curlx_dyn_addf(&req_buffer, "Content-Length: %" FMT_OFF_T"\r\n",
    598                          req_clen);
    599         if(result)
    600           goto out;
    601       }
    602 
    603       if(rtspreq == RTSPREQ_SET_PARAMETER ||
    604          rtspreq == RTSPREQ_GET_PARAMETER) {
    605         if(!Curl_checkheaders(data, STRCONST("Content-Type"))) {
    606           result = curlx_dyn_addn(&req_buffer,
    607                                   STRCONST("Content-Type: "
    608                                            "text/parameters\r\n"));
    609           if(result)
    610             goto out;
    611         }
    612       }
    613 
    614       if(rtspreq == RTSPREQ_ANNOUNCE) {
    615         if(!Curl_checkheaders(data, STRCONST("Content-Type"))) {
    616           result = curlx_dyn_addn(&req_buffer,
    617                                   STRCONST("Content-Type: "
    618                                            "application/sdp\r\n"));
    619           if(result)
    620             goto out;
    621         }
    622       }
    623     }
    624     else if(rtspreq == RTSPREQ_GET_PARAMETER) {
    625       /* Check for an empty GET_PARAMETER (heartbeat) request */
    626       data->state.httpreq = HTTPREQ_HEAD;
    627       data->req.no_body = TRUE;
    628     }
    629   }
    630   else {
    631     result = Curl_creader_set_null(data);
    632     if(result)
    633       goto out;
    634   }
    635 
    636   /* Finish the request buffer */
    637   result = curlx_dyn_addn(&req_buffer, STRCONST("\r\n"));
    638   if(result)
    639     goto out;
    640 
    641   Curl_xfer_setup1(data, CURL_XFER_SENDRECV, -1, TRUE);
    642 
    643   /* issue the request */
    644   result = Curl_req_send(data, &req_buffer, httpversion);
    645   if(result) {
    646     failf(data, "Failed sending RTSP request");
    647     goto out;
    648   }
    649 
    650   /* Increment the CSeq on success */
    651   data->state.rtsp_next_client_CSeq++;
    652 
    653   if(data->req.writebytecount) {
    654     /* if a request-body has been sent off, we make sure this progress is
    655        noted properly */
    656     Curl_pgrsSetUploadCounter(data, data->req.writebytecount);
    657     if(Curl_pgrsUpdate(data))
    658       result = CURLE_ABORTED_BY_CALLBACK;
    659   }
    660 out:
    661   curlx_dyn_free(&req_buffer);
    662   return result;
    663 }
    664 
    665 /**
    666  * write any BODY bytes missing to the client, ignore the rest.
    667  */
    668 static CURLcode rtp_write_body_junk(struct Curl_easy *data,
    669                                     struct rtsp_conn *rtspc,
    670                                     const char *buf,
    671                                     size_t blen)
    672 {
    673   curl_off_t body_remain;
    674   bool in_body;
    675 
    676   in_body = (data->req.headerline && !rtspc->in_header) &&
    677             (data->req.size >= 0) &&
    678             (data->req.bytecount < data->req.size);
    679   body_remain = in_body ? (data->req.size - data->req.bytecount) : 0;
    680   DEBUGASSERT(body_remain >= 0);
    681   if(body_remain) {
    682     if((curl_off_t)blen > body_remain)
    683       blen = (size_t)body_remain;
    684     return Curl_client_write(data, CLIENTWRITE_BODY, buf, blen);
    685   }
    686   return CURLE_OK;
    687 }
    688 
    689 static CURLcode rtsp_filter_rtp(struct Curl_easy *data,
    690                                 struct rtsp_conn *rtspc,
    691                                 const char *buf,
    692                                 size_t blen,
    693                                 size_t *pconsumed)
    694 {
    695   CURLcode result = CURLE_OK;
    696   size_t skip_len = 0;
    697 
    698   *pconsumed = 0;
    699   while(blen) {
    700     bool in_body = (data->req.headerline && !rtspc->in_header) &&
    701                    (data->req.size >= 0) &&
    702                    (data->req.bytecount < data->req.size);
    703     switch(rtspc->state) {
    704 
    705     case RTP_PARSE_SKIP: {
    706       DEBUGASSERT(curlx_dyn_len(&rtspc->buf) == 0);
    707       while(blen && buf[0] != '$') {
    708         if(!in_body && buf[0] == 'R' &&
    709            data->set.rtspreq != RTSPREQ_RECEIVE) {
    710           if(strncmp(buf, "RTSP/", (blen < 5) ? blen : 5) == 0) {
    711             /* This could be the next response, no consume and return */
    712             if(*pconsumed) {
    713               DEBUGF(infof(data, "RTP rtsp_filter_rtp[SKIP] RTSP/ prefix, "
    714                            "skipping %zd bytes of junk", *pconsumed));
    715             }
    716             rtspc->state = RTP_PARSE_SKIP;
    717             rtspc->in_header = TRUE;
    718             goto out;
    719           }
    720         }
    721         /* junk/BODY, consume without buffering */
    722         *pconsumed += 1;
    723         ++buf;
    724         --blen;
    725         ++skip_len;
    726       }
    727       if(blen && buf[0] == '$') {
    728         /* possible start of an RTP message, buffer */
    729         if(skip_len) {
    730           /* end of junk/BODY bytes, flush */
    731           result = rtp_write_body_junk(data, rtspc, buf - skip_len, skip_len);
    732           skip_len = 0;
    733           if(result)
    734             goto out;
    735         }
    736         if(curlx_dyn_addn(&rtspc->buf, buf, 1)) {
    737           result = CURLE_OUT_OF_MEMORY;
    738           goto out;
    739         }
    740         *pconsumed += 1;
    741         ++buf;
    742         --blen;
    743         rtspc->state = RTP_PARSE_CHANNEL;
    744       }
    745       break;
    746     }
    747 
    748     case RTP_PARSE_CHANNEL: {
    749       int idx = ((unsigned char)buf[0]) / 8;
    750       int off = ((unsigned char)buf[0]) % 8;
    751       DEBUGASSERT(curlx_dyn_len(&rtspc->buf) == 1);
    752       if(!(data->state.rtp_channel_mask[idx] & (1 << off))) {
    753         /* invalid channel number, junk or BODY data */
    754         rtspc->state = RTP_PARSE_SKIP;
    755         DEBUGASSERT(skip_len == 0);
    756         /* we do not consume this byte, it is BODY data */
    757         DEBUGF(infof(data, "RTSP: invalid RTP channel %d, skipping", idx));
    758         if(*pconsumed == 0) {
    759           /* We did not consume the initial '$' in our buffer, but had
    760            * it from an earlier call. We cannot un-consume it and have
    761            * to write it directly as BODY data */
    762           result = rtp_write_body_junk(data, rtspc,
    763                                        curlx_dyn_ptr(&rtspc->buf), 1);
    764           if(result)
    765             goto out;
    766         }
    767         else {
    768           /* count the '$' as skip and continue */
    769           skip_len = 1;
    770         }
    771         curlx_dyn_free(&rtspc->buf);
    772         break;
    773       }
    774       /* a valid channel, so we expect this to be a real RTP message */
    775       rtspc->rtp_channel = (unsigned char)buf[0];
    776       if(curlx_dyn_addn(&rtspc->buf, buf, 1)) {
    777         result = CURLE_OUT_OF_MEMORY;
    778         goto out;
    779       }
    780       *pconsumed += 1;
    781       ++buf;
    782       --blen;
    783       rtspc->state = RTP_PARSE_LEN;
    784       break;
    785     }
    786 
    787     case RTP_PARSE_LEN: {
    788       size_t rtp_len = curlx_dyn_len(&rtspc->buf);
    789       const char *rtp_buf;
    790       DEBUGASSERT(rtp_len >= 2 && rtp_len < 4);
    791       if(curlx_dyn_addn(&rtspc->buf, buf, 1)) {
    792         result = CURLE_OUT_OF_MEMORY;
    793         goto out;
    794       }
    795       *pconsumed += 1;
    796       ++buf;
    797       --blen;
    798       if(rtp_len == 2)
    799         break;
    800       rtp_buf = curlx_dyn_ptr(&rtspc->buf);
    801       rtspc->rtp_len = RTP_PKT_LENGTH(rtp_buf) + 4;
    802       rtspc->state = RTP_PARSE_DATA;
    803       break;
    804     }
    805 
    806     case RTP_PARSE_DATA: {
    807       size_t rtp_len = curlx_dyn_len(&rtspc->buf);
    808       size_t needed;
    809       DEBUGASSERT(rtp_len < rtspc->rtp_len);
    810       needed = rtspc->rtp_len - rtp_len;
    811       if(needed <= blen) {
    812         if(curlx_dyn_addn(&rtspc->buf, buf, needed)) {
    813           result = CURLE_OUT_OF_MEMORY;
    814           goto out;
    815         }
    816         *pconsumed += needed;
    817         buf += needed;
    818         blen -= needed;
    819         /* complete RTP message in buffer */
    820         DEBUGF(infof(data, "RTP write channel %d rtp_len %zu",
    821                      rtspc->rtp_channel, rtspc->rtp_len));
    822         result = rtp_client_write(data, curlx_dyn_ptr(&rtspc->buf),
    823                                   rtspc->rtp_len);
    824         curlx_dyn_free(&rtspc->buf);
    825         rtspc->state = RTP_PARSE_SKIP;
    826         if(result)
    827           goto out;
    828       }
    829       else {
    830         if(curlx_dyn_addn(&rtspc->buf, buf, blen)) {
    831           result = CURLE_OUT_OF_MEMORY;
    832           goto out;
    833         }
    834         *pconsumed += blen;
    835         buf += blen;
    836         blen = 0;
    837       }
    838       break;
    839     }
    840 
    841     default:
    842       DEBUGASSERT(0);
    843       return CURLE_RECV_ERROR;
    844     }
    845   }
    846 out:
    847   if(!result && skip_len)
    848     result = rtp_write_body_junk(data, rtspc, buf - skip_len, skip_len);
    849   return result;
    850 }
    851 
    852 static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data,
    853                                     const char *buf,
    854                                     size_t blen,
    855                                     bool is_eos)
    856 {
    857   struct rtsp_conn *rtspc =
    858     Curl_conn_meta_get(data->conn, CURL_META_RTSP_CONN);
    859   CURLcode result = CURLE_OK;
    860   size_t consumed = 0;
    861 
    862   if(!rtspc)
    863     return CURLE_FAILED_INIT;
    864 
    865   if(!data->req.header)
    866     rtspc->in_header = FALSE;
    867   if(!blen) {
    868     goto out;
    869   }
    870 
    871   DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, eos=%d)",
    872                blen, rtspc->in_header, is_eos));
    873 
    874   /* If header parsing is not ongoing, extract RTP messages */
    875   if(!rtspc->in_header) {
    876     result = rtsp_filter_rtp(data, rtspc, buf, blen, &consumed);
    877     if(result)
    878       goto out;
    879     buf += consumed;
    880     blen -= consumed;
    881     /* either we consumed all or are at the start of header parsing */
    882     if(blen && !data->req.header)
    883       DEBUGF(infof(data, "RTSP: %zu bytes, possibly excess in response body",
    884                    blen));
    885   }
    886 
    887   /* we want to parse headers, do so */
    888   if(data->req.header && blen) {
    889     rtspc->in_header = TRUE;
    890     result = Curl_http_write_resp_hds(data, buf, blen, &consumed);
    891     if(result)
    892       goto out;
    893 
    894     buf += consumed;
    895     blen -= consumed;
    896 
    897     if(!data->req.header)
    898       rtspc->in_header = FALSE;
    899 
    900     if(!rtspc->in_header) {
    901       /* If header parsing is done, extract interleaved RTP messages */
    902       if(data->req.size <= -1) {
    903         /* Respect section 4.4 of rfc2326: If the Content-Length header is
    904            absent, a length 0 must be assumed. */
    905         data->req.size = 0;
    906         data->req.download_done = TRUE;
    907       }
    908       result = rtsp_filter_rtp(data, rtspc, buf, blen, &consumed);
    909       if(result)
    910         goto out;
    911       blen -= consumed;
    912     }
    913   }
    914 
    915   if(rtspc->state != RTP_PARSE_SKIP)
    916     data->req.done = FALSE;
    917   /* we SHOULD have consumed all bytes, unless the response is borked.
    918    * In which case we write out the left over bytes, letting the client
    919    * writer deal with it (it will report EXCESS and fail the transfer). */
    920   DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, done=%d "
    921                " rtspc->state=%d, req.size=%" FMT_OFF_T ")",
    922                blen, rtspc->in_header, data->req.done, rtspc->state,
    923                data->req.size));
    924   if(!result && (is_eos || blen)) {
    925     result = Curl_client_write(data, CLIENTWRITE_BODY|
    926                                (is_eos ? CLIENTWRITE_EOS : 0), buf, blen);
    927   }
    928 
    929 out:
    930   if((data->set.rtspreq == RTSPREQ_RECEIVE) &&
    931      (rtspc->state == RTP_PARSE_SKIP)) {
    932     /* In special mode RECEIVE, we just process one chunk of network
    933      * data, so we stop the transfer here, if we have no incomplete
    934      * RTP message pending. */
    935     data->req.download_done = TRUE;
    936   }
    937   return result;
    938 }
    939 
    940 static
    941 CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len)
    942 {
    943   size_t wrote;
    944   curl_write_callback writeit;
    945   void *user_ptr;
    946 
    947   if(len == 0) {
    948     failf(data, "Cannot write a 0 size RTP packet.");
    949     return CURLE_WRITE_ERROR;
    950   }
    951 
    952   /* If the user has configured CURLOPT_INTERLEAVEFUNCTION then use that
    953      function and any configured CURLOPT_INTERLEAVEDATA to write out the RTP
    954      data. Otherwise, use the CURLOPT_WRITEFUNCTION with the CURLOPT_WRITEDATA
    955      pointer to write out the RTP data. */
    956   if(data->set.fwrite_rtp) {
    957     writeit = data->set.fwrite_rtp;
    958     user_ptr = data->set.rtp_out;
    959   }
    960   else {
    961     writeit = data->set.fwrite_func;
    962     user_ptr = data->set.out;
    963   }
    964 
    965   Curl_set_in_callback(data, TRUE);
    966   wrote = writeit((char *)CURL_UNCONST(ptr), 1, len, user_ptr);
    967   Curl_set_in_callback(data, FALSE);
    968 
    969   if(CURL_WRITEFUNC_PAUSE == wrote) {
    970     failf(data, "Cannot pause RTP");
    971     return CURLE_WRITE_ERROR;
    972   }
    973 
    974   if(wrote != len) {
    975     failf(data, "Failed writing RTP data");
    976     return CURLE_WRITE_ERROR;
    977   }
    978 
    979   return CURLE_OK;
    980 }
    981 
    982 CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header)
    983 {
    984   if(checkprefix("CSeq:", header)) {
    985     curl_off_t CSeq = 0;
    986     struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY);
    987     const char *p = &header[5];
    988     if(!rtsp)
    989       return CURLE_FAILED_INIT;
    990     curlx_str_passblanks(&p);
    991     if(curlx_str_number(&p, &CSeq, LONG_MAX)) {
    992       failf(data, "Unable to read the CSeq header: [%s]", header);
    993       return CURLE_RTSP_CSEQ_ERROR;
    994     }
    995     rtsp->CSeq_recv = (long)CSeq; /* mark the request */
    996     data->state.rtsp_CSeq_recv = (long)CSeq; /* update the handle */
    997   }
    998   else if(checkprefix("Session:", header)) {
    999     const char *start, *end;
   1000     size_t idlen;
   1001 
   1002     /* Find the first non-space letter */
   1003     start = header + 8;
   1004     curlx_str_passblanks(&start);
   1005 
   1006     if(!*start) {
   1007       failf(data, "Got a blank Session ID");
   1008       return CURLE_RTSP_SESSION_ERROR;
   1009     }
   1010 
   1011     /* Find the end of Session ID
   1012      *
   1013      * Allow any non whitespace content, up to the field separator or end of
   1014      * line. RFC 2326 is not 100% clear on the session ID and for example
   1015      * gstreamer does url-encoded session ID's not covered by the standard.
   1016      */
   1017     end = start;
   1018     while((*end > ' ') && (*end != ';'))
   1019       end++;
   1020     idlen = end - start;
   1021 
   1022     if(data->set.str[STRING_RTSP_SESSION_ID]) {
   1023 
   1024       /* If the Session ID is set, then compare */
   1025       if(strlen(data->set.str[STRING_RTSP_SESSION_ID]) != idlen ||
   1026          strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], idlen)) {
   1027         failf(data, "Got RTSP Session ID Line [%s], but wanted ID [%s]",
   1028               start, data->set.str[STRING_RTSP_SESSION_ID]);
   1029         return CURLE_RTSP_SESSION_ERROR;
   1030       }
   1031     }
   1032     else {
   1033       /* If the Session ID is not set, and we find it in a response, then set
   1034        * it.
   1035        */
   1036 
   1037       /* Copy the id substring into a new buffer */
   1038       data->set.str[STRING_RTSP_SESSION_ID] = Curl_memdup0(start, idlen);
   1039       if(!data->set.str[STRING_RTSP_SESSION_ID])
   1040         return CURLE_OUT_OF_MEMORY;
   1041     }
   1042   }
   1043   else if(checkprefix("Transport:", header)) {
   1044     CURLcode result;
   1045     result = rtsp_parse_transport(data, header + 10);
   1046     if(result)
   1047       return result;
   1048   }
   1049   return CURLE_OK;
   1050 }
   1051 
   1052 static
   1053 CURLcode rtsp_parse_transport(struct Curl_easy *data, const char *transport)
   1054 {
   1055   /* If we receive multiple Transport response-headers, the linterleaved
   1056      channels of each response header is recorded and used together for
   1057      subsequent data validity checks.*/
   1058   /* e.g.: ' RTP/AVP/TCP;unicast;interleaved=5-6' */
   1059   const char *start, *end;
   1060   start = transport;
   1061   while(start && *start) {
   1062     curlx_str_passblanks(&start);
   1063     end = strchr(start, ';');
   1064     if(checkprefix("interleaved=", start)) {
   1065       curl_off_t chan1, chan2, chan;
   1066       const char *p = start + 12;
   1067       if(!curlx_str_number(&p, &chan1, 255)) {
   1068         unsigned char *rtp_channel_mask = data->state.rtp_channel_mask;
   1069         chan2 = chan1;
   1070         if(!curlx_str_single(&p, '-')) {
   1071           if(curlx_str_number(&p, &chan2, 255)) {
   1072             infof(data, "Unable to read the interleaved parameter from "
   1073                   "Transport header: [%s]", transport);
   1074             chan2 = chan1;
   1075           }
   1076         }
   1077         for(chan = chan1; chan <= chan2; chan++) {
   1078           int idx = (int)chan / 8;
   1079           int off = (int)chan % 8;
   1080           rtp_channel_mask[idx] |= (unsigned char)(1 << off);
   1081         }
   1082       }
   1083       else {
   1084         infof(data, "Unable to read the interleaved parameter from "
   1085               "Transport header: [%s]", transport);
   1086       }
   1087       break;
   1088     }
   1089     /* skip to next parameter */
   1090     start = (!end) ? end : (end + 1);
   1091   }
   1092   return CURLE_OK;
   1093 }
   1094 
   1095 
   1096 #endif /* CURL_DISABLE_RTSP */