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