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