Coverage Report

Created: 2025-10-10 06:31

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