1/***************************************************************************
2 *                      _   _ ____  _
3 *  Project         ___| | | |  _ \| |
4 *                 / __| | | | |_) | |
5 *                | (__| |_| |  _ <| |___
6 *                 \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 2012 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al.
9 * Copyright (C) 2010, Howard Chu, <hyc@highlandsun.com>
10 *
11 * This software is licensed as described in the file COPYING, which
12 * you should have received as part of this distribution. The terms
13 * are also available at https://curl.haxx.se/docs/copyright.html.
14 *
15 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
16 * copies of the Software, and permit persons to whom the Software is
17 * furnished to do so, under the terms of the COPYING file.
18 *
19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20 * KIND, either express or implied.
21 *
22 ***************************************************************************/
23
24#include "curl_setup.h"
25
26#ifdef USE_LIBRTMP
27
28#include "urldata.h"
29#include "nonblock.h" /* for curlx_nonblock */
30#include "progress.h" /* for Curl_pgrsSetUploadSize */
31#include "transfer.h"
32#include "warnless.h"
33#include <curl/curl.h>
34#include <librtmp/rtmp.h>
35#include "curl_memory.h"
36/* The last #include file should be: */
37#include "memdebug.h"
38
39#ifdef _WIN32
40#define setsockopt(a,b,c,d,e) (setsockopt)(a,b,c,(const char *)d,(int)e)
41#define SET_RCVTIMEO(tv,s)   int tv = s*1000
42#else
43#define SET_RCVTIMEO(tv,s)   struct timeval tv = {s,0}
44#endif
45
46#define DEF_BUFTIME    (2*60*60*1000)    /* 2 hours */
47
48static CURLcode rtmp_setup_connection(struct connectdata *conn);
49static CURLcode rtmp_do(struct connectdata *conn, bool *done);
50static CURLcode rtmp_done(struct connectdata *conn, CURLcode, bool premature);
51static CURLcode rtmp_connect(struct connectdata *conn, bool *done);
52static CURLcode rtmp_disconnect(struct connectdata *conn, bool dead);
53
54static Curl_recv rtmp_recv;
55static Curl_send rtmp_send;
56
57/*
58 * RTMP protocol handler.h, based on https://rtmpdump.mplayerhq.hu
59 */
60
61const struct Curl_handler Curl_handler_rtmp = {
62  "RTMP",                               /* scheme */
63  rtmp_setup_connection,                /* setup_connection */
64  rtmp_do,                              /* do_it */
65  rtmp_done,                            /* done */
66  ZERO_NULL,                            /* do_more */
67  rtmp_connect,                         /* connect_it */
68  ZERO_NULL,                            /* connecting */
69  ZERO_NULL,                            /* doing */
70  ZERO_NULL,                            /* proto_getsock */
71  ZERO_NULL,                            /* doing_getsock */
72  ZERO_NULL,                            /* domore_getsock */
73  ZERO_NULL,                            /* perform_getsock */
74  rtmp_disconnect,                      /* disconnect */
75  ZERO_NULL,                            /* readwrite */
76  PORT_RTMP,                            /* defport */
77  CURLPROTO_RTMP,                       /* protocol */
78  PROTOPT_NONE                          /* flags*/
79};
80
81const struct Curl_handler Curl_handler_rtmpt = {
82  "RTMPT",                              /* scheme */
83  rtmp_setup_connection,                /* setup_connection */
84  rtmp_do,                              /* do_it */
85  rtmp_done,                            /* done */
86  ZERO_NULL,                            /* do_more */
87  rtmp_connect,                         /* connect_it */
88  ZERO_NULL,                            /* connecting */
89  ZERO_NULL,                            /* doing */
90  ZERO_NULL,                            /* proto_getsock */
91  ZERO_NULL,                            /* doing_getsock */
92  ZERO_NULL,                            /* domore_getsock */
93  ZERO_NULL,                            /* perform_getsock */
94  rtmp_disconnect,                      /* disconnect */
95  ZERO_NULL,                            /* readwrite */
96  PORT_RTMPT,                           /* defport */
97  CURLPROTO_RTMPT,                      /* protocol */
98  PROTOPT_NONE                          /* flags*/
99};
100
101const struct Curl_handler Curl_handler_rtmpe = {
102  "RTMPE",                              /* scheme */
103  rtmp_setup_connection,                /* setup_connection */
104  rtmp_do,                              /* do_it */
105  rtmp_done,                            /* done */
106  ZERO_NULL,                            /* do_more */
107  rtmp_connect,                         /* connect_it */
108  ZERO_NULL,                            /* connecting */
109  ZERO_NULL,                            /* doing */
110  ZERO_NULL,                            /* proto_getsock */
111  ZERO_NULL,                            /* doing_getsock */
112  ZERO_NULL,                            /* domore_getsock */
113  ZERO_NULL,                            /* perform_getsock */
114  rtmp_disconnect,                      /* disconnect */
115  ZERO_NULL,                            /* readwrite */
116  PORT_RTMP,                            /* defport */
117  CURLPROTO_RTMPE,                      /* protocol */
118  PROTOPT_NONE                          /* flags*/
119};
120
121const struct Curl_handler Curl_handler_rtmpte = {
122  "RTMPTE",                             /* scheme */
123  rtmp_setup_connection,                /* setup_connection */
124  rtmp_do,                              /* do_it */
125  rtmp_done,                            /* done */
126  ZERO_NULL,                            /* do_more */
127  rtmp_connect,                         /* connect_it */
128  ZERO_NULL,                            /* connecting */
129  ZERO_NULL,                            /* doing */
130  ZERO_NULL,                            /* proto_getsock */
131  ZERO_NULL,                            /* doing_getsock */
132  ZERO_NULL,                            /* domore_getsock */
133  ZERO_NULL,                            /* perform_getsock */
134  rtmp_disconnect,                      /* disconnect */
135  ZERO_NULL,                            /* readwrite */
136  PORT_RTMPT,                           /* defport */
137  CURLPROTO_RTMPTE,                     /* protocol */
138  PROTOPT_NONE                          /* flags*/
139};
140
141const struct Curl_handler Curl_handler_rtmps = {
142  "RTMPS",                              /* scheme */
143  rtmp_setup_connection,                /* setup_connection */
144  rtmp_do,                              /* do_it */
145  rtmp_done,                            /* done */
146  ZERO_NULL,                            /* do_more */
147  rtmp_connect,                         /* connect_it */
148  ZERO_NULL,                            /* connecting */
149  ZERO_NULL,                            /* doing */
150  ZERO_NULL,                            /* proto_getsock */
151  ZERO_NULL,                            /* doing_getsock */
152  ZERO_NULL,                            /* domore_getsock */
153  ZERO_NULL,                            /* perform_getsock */
154  rtmp_disconnect,                      /* disconnect */
155  ZERO_NULL,                            /* readwrite */
156  PORT_RTMPS,                           /* defport */
157  CURLPROTO_RTMPS,                      /* protocol */
158  PROTOPT_NONE                          /* flags*/
159};
160
161const struct Curl_handler Curl_handler_rtmpts = {
162  "RTMPTS",                             /* scheme */
163  rtmp_setup_connection,                /* setup_connection */
164  rtmp_do,                              /* do_it */
165  rtmp_done,                            /* done */
166  ZERO_NULL,                            /* do_more */
167  rtmp_connect,                         /* connect_it */
168  ZERO_NULL,                            /* connecting */
169  ZERO_NULL,                            /* doing */
170  ZERO_NULL,                            /* proto_getsock */
171  ZERO_NULL,                            /* doing_getsock */
172  ZERO_NULL,                            /* domore_getsock */
173  ZERO_NULL,                            /* perform_getsock */
174  rtmp_disconnect,                      /* disconnect */
175  ZERO_NULL,                            /* readwrite */
176  PORT_RTMPS,                           /* defport */
177  CURLPROTO_RTMPTS,                     /* protocol */
178  PROTOPT_NONE                          /* flags*/
179};
180
181static CURLcode rtmp_setup_connection(struct connectdata *conn)
182{
183  RTMP *r = RTMP_Alloc();
184  if(!r)
185    return CURLE_OUT_OF_MEMORY;
186
187  RTMP_Init(r);
188  RTMP_SetBufferMS(r, DEF_BUFTIME);
189  if(!RTMP_SetupURL(r, conn->data->change.url)) {
190    RTMP_Free(r);
191    return CURLE_URL_MALFORMAT;
192  }
193  conn->proto.generic = r;
194  return CURLE_OK;
195}
196
197static CURLcode rtmp_connect(struct connectdata *conn, bool *done)
198{
199  RTMP *r = conn->proto.generic;
200  SET_RCVTIMEO(tv, 10);
201
202  r->m_sb.sb_socket = conn->sock[FIRSTSOCKET];
203
204  /* We have to know if it's a write before we send the
205   * connect request packet
206   */
207  if(conn->data->set.upload)
208    r->Link.protocol |= RTMP_FEATURE_WRITE;
209
210  /* For plain streams, use the buffer toggle trick to keep data flowing */
211  if(!(r->Link.lFlags & RTMP_LF_LIVE) &&
212     !(r->Link.protocol & RTMP_FEATURE_HTTP))
213    r->Link.lFlags |= RTMP_LF_BUFX;
214
215  (void)curlx_nonblock(r->m_sb.sb_socket, FALSE);
216  setsockopt(r->m_sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO,
217             (char *)&tv, sizeof(tv));
218
219  if(!RTMP_Connect1(r, NULL))
220    return CURLE_FAILED_INIT;
221
222  /* Clients must send a periodic BytesReceived report to the server */
223  r->m_bSendCounter = true;
224
225  *done = TRUE;
226  conn->recv[FIRSTSOCKET] = rtmp_recv;
227  conn->send[FIRSTSOCKET] = rtmp_send;
228  return CURLE_OK;
229}
230
231static CURLcode rtmp_do(struct connectdata *conn, bool *done)
232{
233  RTMP *r = conn->proto.generic;
234
235  if(!RTMP_ConnectStream(r, 0))
236    return CURLE_FAILED_INIT;
237
238  if(conn->data->set.upload) {
239    Curl_pgrsSetUploadSize(conn->data, conn->data->state.infilesize);
240    Curl_setup_transfer(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL);
241  }
242  else
243    Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, NULL, -1, NULL);
244  *done = TRUE;
245  return CURLE_OK;
246}
247
248static CURLcode rtmp_done(struct connectdata *conn, CURLcode status,
249                          bool premature)
250{
251  (void)conn; /* unused */
252  (void)status; /* unused */
253  (void)premature; /* unused */
254
255  return CURLE_OK;
256}
257
258static CURLcode rtmp_disconnect(struct connectdata *conn,
259                                bool dead_connection)
260{
261  RTMP *r = conn->proto.generic;
262  (void)dead_connection;
263  if(r) {
264    conn->proto.generic = NULL;
265    RTMP_Close(r);
266    RTMP_Free(r);
267  }
268  return CURLE_OK;
269}
270
271static ssize_t rtmp_recv(struct connectdata *conn, int sockindex, char *buf,
272                         size_t len, CURLcode *err)
273{
274  RTMP *r = conn->proto.generic;
275  ssize_t nread;
276
277  (void)sockindex; /* unused */
278
279  nread = RTMP_Read(r, buf, curlx_uztosi(len));
280  if(nread < 0) {
281    if(r->m_read.status == RTMP_READ_COMPLETE ||
282        r->m_read.status == RTMP_READ_EOF) {
283      conn->data->req.size = conn->data->req.bytecount;
284      nread = 0;
285    }
286    else
287      *err = CURLE_RECV_ERROR;
288  }
289  return nread;
290}
291
292static ssize_t rtmp_send(struct connectdata *conn, int sockindex,
293                         const void *buf, size_t len, CURLcode *err)
294{
295  RTMP *r = conn->proto.generic;
296  ssize_t num;
297
298  (void)sockindex; /* unused */
299
300  num = RTMP_Write(r, (char *)buf, curlx_uztosi(len));
301  if(num < 0)
302    *err = CURLE_SEND_ERROR;
303
304  return num;
305}
306#endif  /* USE_LIBRTMP */
307