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