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