Coverage Report

Created: 2026-06-01 07:00

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