Coverage Report

Created: 2026-10-22 06:49

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