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