curl_rtmp.c (14706B)
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 "url.h" 33 #include "curlx/nonblock.h" /* for curlx_nonblock */ 34 #include "progress.h" /* for Curl_pgrsSetUploadSize */ 35 #include "transfer.h" 36 #include "curlx/warnless.h" 37 #include <curl/curl.h> 38 #include <librtmp/rtmp.h> 39 40 /* The last 3 #include files should be in this order */ 41 #include "curl_printf.h" 42 #include "curl_memory.h" 43 #include "memdebug.h" 44 45 #if defined(_WIN32) && !defined(USE_LWIPSOCK) 46 #define setsockopt(a,b,c,d,e) (setsockopt)(a,b,c,(const char *)d,(int)e) 47 #define SET_RCVTIMEO(tv,s) int tv = s*1000 48 #elif defined(LWIP_SO_SNDRCVTIMEO_NONSTANDARD) 49 #define SET_RCVTIMEO(tv,s) int tv = s*1000 50 #else 51 #define SET_RCVTIMEO(tv,s) struct timeval tv = {s,0} 52 #endif 53 54 #define DEF_BUFTIME (2*60*60*1000) /* 2 hours */ 55 56 /* meta key for storing RTMP* at connection */ 57 #define CURL_META_RTMP_CONN "meta:proto:rtmp:conn" 58 59 60 static CURLcode rtmp_setup_connection(struct Curl_easy *data, 61 struct connectdata *conn); 62 static CURLcode rtmp_do(struct Curl_easy *data, bool *done); 63 static CURLcode rtmp_done(struct Curl_easy *data, CURLcode, bool premature); 64 static CURLcode rtmp_connect(struct Curl_easy *data, bool *done); 65 static CURLcode rtmp_disconnect(struct Curl_easy *data, 66 struct connectdata *conn, bool dead); 67 68 static Curl_recv rtmp_recv; 69 static Curl_send rtmp_send; 70 71 /* 72 * RTMP protocol handler.h, based on https://rtmpdump.mplayerhq.hu 73 */ 74 75 const struct Curl_handler Curl_handler_rtmp = { 76 "rtmp", /* scheme */ 77 rtmp_setup_connection, /* setup_connection */ 78 rtmp_do, /* do_it */ 79 rtmp_done, /* done */ 80 ZERO_NULL, /* do_more */ 81 rtmp_connect, /* connect_it */ 82 ZERO_NULL, /* connecting */ 83 ZERO_NULL, /* doing */ 84 ZERO_NULL, /* proto_getsock */ 85 ZERO_NULL, /* doing_getsock */ 86 ZERO_NULL, /* domore_getsock */ 87 ZERO_NULL, /* perform_getsock */ 88 rtmp_disconnect, /* disconnect */ 89 ZERO_NULL, /* write_resp */ 90 ZERO_NULL, /* write_resp_hd */ 91 ZERO_NULL, /* connection_check */ 92 ZERO_NULL, /* attach connection */ 93 ZERO_NULL, /* follow */ 94 PORT_RTMP, /* defport */ 95 CURLPROTO_RTMP, /* protocol */ 96 CURLPROTO_RTMP, /* family */ 97 PROTOPT_NONE /* flags */ 98 }; 99 100 const struct Curl_handler Curl_handler_rtmpt = { 101 "rtmpt", /* scheme */ 102 rtmp_setup_connection, /* setup_connection */ 103 rtmp_do, /* do_it */ 104 rtmp_done, /* done */ 105 ZERO_NULL, /* do_more */ 106 rtmp_connect, /* connect_it */ 107 ZERO_NULL, /* connecting */ 108 ZERO_NULL, /* doing */ 109 ZERO_NULL, /* proto_getsock */ 110 ZERO_NULL, /* doing_getsock */ 111 ZERO_NULL, /* domore_getsock */ 112 ZERO_NULL, /* perform_getsock */ 113 rtmp_disconnect, /* disconnect */ 114 ZERO_NULL, /* write_resp */ 115 ZERO_NULL, /* write_resp_hd */ 116 ZERO_NULL, /* connection_check */ 117 ZERO_NULL, /* attach connection */ 118 ZERO_NULL, /* follow */ 119 PORT_RTMPT, /* defport */ 120 CURLPROTO_RTMPT, /* protocol */ 121 CURLPROTO_RTMPT, /* family */ 122 PROTOPT_NONE /* flags */ 123 }; 124 125 const struct Curl_handler Curl_handler_rtmpe = { 126 "rtmpe", /* scheme */ 127 rtmp_setup_connection, /* setup_connection */ 128 rtmp_do, /* do_it */ 129 rtmp_done, /* done */ 130 ZERO_NULL, /* do_more */ 131 rtmp_connect, /* connect_it */ 132 ZERO_NULL, /* connecting */ 133 ZERO_NULL, /* doing */ 134 ZERO_NULL, /* proto_getsock */ 135 ZERO_NULL, /* doing_getsock */ 136 ZERO_NULL, /* domore_getsock */ 137 ZERO_NULL, /* perform_getsock */ 138 rtmp_disconnect, /* disconnect */ 139 ZERO_NULL, /* write_resp */ 140 ZERO_NULL, /* write_resp_hd */ 141 ZERO_NULL, /* connection_check */ 142 ZERO_NULL, /* attach connection */ 143 ZERO_NULL, /* follow */ 144 PORT_RTMP, /* defport */ 145 CURLPROTO_RTMPE, /* protocol */ 146 CURLPROTO_RTMPE, /* family */ 147 PROTOPT_NONE /* flags */ 148 }; 149 150 const struct Curl_handler Curl_handler_rtmpte = { 151 "rtmpte", /* scheme */ 152 rtmp_setup_connection, /* setup_connection */ 153 rtmp_do, /* do_it */ 154 rtmp_done, /* done */ 155 ZERO_NULL, /* do_more */ 156 rtmp_connect, /* connect_it */ 157 ZERO_NULL, /* connecting */ 158 ZERO_NULL, /* doing */ 159 ZERO_NULL, /* proto_getsock */ 160 ZERO_NULL, /* doing_getsock */ 161 ZERO_NULL, /* domore_getsock */ 162 ZERO_NULL, /* perform_getsock */ 163 rtmp_disconnect, /* disconnect */ 164 ZERO_NULL, /* write_resp */ 165 ZERO_NULL, /* write_resp_hd */ 166 ZERO_NULL, /* connection_check */ 167 ZERO_NULL, /* attach connection */ 168 ZERO_NULL, /* follow */ 169 PORT_RTMPT, /* defport */ 170 CURLPROTO_RTMPTE, /* protocol */ 171 CURLPROTO_RTMPTE, /* family */ 172 PROTOPT_NONE /* flags */ 173 }; 174 175 const struct Curl_handler Curl_handler_rtmps = { 176 "rtmps", /* scheme */ 177 rtmp_setup_connection, /* setup_connection */ 178 rtmp_do, /* do_it */ 179 rtmp_done, /* done */ 180 ZERO_NULL, /* do_more */ 181 rtmp_connect, /* connect_it */ 182 ZERO_NULL, /* connecting */ 183 ZERO_NULL, /* doing */ 184 ZERO_NULL, /* proto_getsock */ 185 ZERO_NULL, /* doing_getsock */ 186 ZERO_NULL, /* domore_getsock */ 187 ZERO_NULL, /* perform_getsock */ 188 rtmp_disconnect, /* disconnect */ 189 ZERO_NULL, /* write_resp */ 190 ZERO_NULL, /* write_resp_hd */ 191 ZERO_NULL, /* connection_check */ 192 ZERO_NULL, /* attach connection */ 193 ZERO_NULL, /* follow */ 194 PORT_RTMPS, /* defport */ 195 CURLPROTO_RTMPS, /* protocol */ 196 CURLPROTO_RTMP, /* family */ 197 PROTOPT_NONE /* flags */ 198 }; 199 200 const struct Curl_handler Curl_handler_rtmpts = { 201 "rtmpts", /* scheme */ 202 rtmp_setup_connection, /* setup_connection */ 203 rtmp_do, /* do_it */ 204 rtmp_done, /* done */ 205 ZERO_NULL, /* do_more */ 206 rtmp_connect, /* connect_it */ 207 ZERO_NULL, /* connecting */ 208 ZERO_NULL, /* doing */ 209 ZERO_NULL, /* proto_getsock */ 210 ZERO_NULL, /* doing_getsock */ 211 ZERO_NULL, /* domore_getsock */ 212 ZERO_NULL, /* perform_getsock */ 213 rtmp_disconnect, /* disconnect */ 214 ZERO_NULL, /* write_resp */ 215 ZERO_NULL, /* write_resp_hd */ 216 ZERO_NULL, /* connection_check */ 217 ZERO_NULL, /* attach connection */ 218 ZERO_NULL, /* follow */ 219 PORT_RTMPS, /* defport */ 220 CURLPROTO_RTMPTS, /* protocol */ 221 CURLPROTO_RTMPT, /* family */ 222 PROTOPT_NONE /* flags */ 223 }; 224 225 static void rtmp_conn_dtor(void *key, size_t klen, void *entry) 226 { 227 RTMP *r = entry; 228 (void)key; 229 (void)klen; 230 RTMP_Close(r); 231 RTMP_Free(r); 232 } 233 234 static CURLcode rtmp_setup_connection(struct Curl_easy *data, 235 struct connectdata *conn) 236 { 237 RTMP *r = RTMP_Alloc(); 238 if(!r || 239 Curl_conn_meta_set(conn, CURL_META_RTMP_CONN, r, rtmp_conn_dtor)) 240 return CURLE_OUT_OF_MEMORY; 241 242 RTMP_Init(r); 243 RTMP_SetBufferMS(r, DEF_BUFTIME); 244 if(!RTMP_SetupURL(r, data->state.url)) { 245 RTMP_Free(r); 246 return CURLE_URL_MALFORMAT; 247 } 248 return CURLE_OK; 249 } 250 251 static CURLcode rtmp_connect(struct Curl_easy *data, bool *done) 252 { 253 struct connectdata *conn = data->conn; 254 RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN); 255 SET_RCVTIMEO(tv, 10); 256 257 if(!r) 258 return CURLE_FAILED_INIT; 259 260 r->m_sb.sb_socket = (int)conn->sock[FIRSTSOCKET]; 261 262 /* We have to know if it is a write before we send the 263 * connect request packet 264 */ 265 if(data->state.upload) 266 r->Link.protocol |= RTMP_FEATURE_WRITE; 267 268 /* For plain streams, use the buffer toggle trick to keep data flowing */ 269 if(!(r->Link.lFlags & RTMP_LF_LIVE) && 270 !(r->Link.protocol & RTMP_FEATURE_HTTP)) 271 r->Link.lFlags |= RTMP_LF_BUFX; 272 273 (void)curlx_nonblock(r->m_sb.sb_socket, FALSE); 274 setsockopt(r->m_sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO, 275 (char *)&tv, sizeof(tv)); 276 277 if(!RTMP_Connect1(r, NULL)) 278 return CURLE_FAILED_INIT; 279 280 /* Clients must send a periodic BytesReceived report to the server */ 281 r->m_bSendCounter = TRUE; 282 283 *done = TRUE; 284 conn->recv[FIRSTSOCKET] = rtmp_recv; 285 conn->send[FIRSTSOCKET] = rtmp_send; 286 return CURLE_OK; 287 } 288 289 static CURLcode rtmp_do(struct Curl_easy *data, bool *done) 290 { 291 struct connectdata *conn = data->conn; 292 RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN); 293 294 if(!r || !RTMP_ConnectStream(r, 0)) 295 return CURLE_FAILED_INIT; 296 297 if(data->state.upload) { 298 Curl_pgrsSetUploadSize(data, data->state.infilesize); 299 Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); 300 } 301 else 302 Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); 303 *done = TRUE; 304 return CURLE_OK; 305 } 306 307 static CURLcode rtmp_done(struct Curl_easy *data, CURLcode status, 308 bool premature) 309 { 310 (void)data; /* unused */ 311 (void)status; /* unused */ 312 (void)premature; /* unused */ 313 314 return CURLE_OK; 315 } 316 317 static CURLcode rtmp_disconnect(struct Curl_easy *data, 318 struct connectdata *conn, 319 bool dead_connection) 320 { 321 RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN); 322 (void)data; 323 (void)dead_connection; 324 if(r) 325 Curl_conn_meta_remove(conn, CURL_META_RTMP_CONN); 326 return CURLE_OK; 327 } 328 329 static CURLcode rtmp_recv(struct Curl_easy *data, int sockindex, char *buf, 330 size_t len, size_t *pnread) 331 { 332 struct connectdata *conn = data->conn; 333 RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN); 334 CURLcode result = CURLE_OK; 335 ssize_t nread; 336 337 (void)sockindex; /* unused */ 338 *pnread = 0; 339 if(!r) 340 return CURLE_FAILED_INIT; 341 342 nread = RTMP_Read(r, buf, curlx_uztosi(len)); 343 if(nread < 0) { 344 if(r->m_read.status == RTMP_READ_COMPLETE || 345 r->m_read.status == RTMP_READ_EOF) { 346 data->req.size = data->req.bytecount; 347 } 348 else 349 result = CURLE_RECV_ERROR; 350 } 351 else 352 *pnread = (size_t)nread; 353 354 return result; 355 } 356 357 static CURLcode rtmp_send(struct Curl_easy *data, int sockindex, 358 const void *buf, size_t len, bool eos, 359 size_t *pnwritten) 360 { 361 struct connectdata *conn = data->conn; 362 RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN); 363 ssize_t nwritten; 364 365 (void)sockindex; /* unused */ 366 (void)eos; /* unused */ 367 *pnwritten = 0; 368 if(!r) 369 return CURLE_FAILED_INIT; 370 371 nwritten = RTMP_Write(r, (const char *)buf, curlx_uztosi(len)); 372 if(nwritten < 0) 373 return CURLE_SEND_ERROR; 374 375 *pnwritten = (size_t)nwritten; 376 return CURLE_OK; 377 } 378 379 void Curl_rtmp_version(char *version, size_t len) 380 { 381 char suff[2]; 382 if(RTMP_LIB_VERSION & 0xff) { 383 suff[0] = (RTMP_LIB_VERSION & 0xff) + 'a' - 1; 384 suff[1] = '\0'; 385 } 386 else 387 suff[0] = '\0'; 388 389 msnprintf(version, len, "librtmp/%d.%d%s", 390 RTMP_LIB_VERSION >> 16, (RTMP_LIB_VERSION >> 8) & 0xff, 391 suff); 392 } 393 394 #endif /* USE_LIBRTMP */