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 53static CURLcode rtmp_setup_connection(struct Curl_easy *data, 54 struct connectdata *conn); 55static CURLcode rtmp_do(struct Curl_easy *data, bool *done); 56static CURLcode rtmp_done(struct Curl_easy *data, CURLcode, bool premature); 57static CURLcode rtmp_connect(struct Curl_easy *data, bool *done); 58static CURLcode rtmp_disconnect(struct Curl_easy *data, 59 struct connectdata *conn, bool dead); 60 61static Curl_recv rtmp_recv; 62static Curl_send rtmp_send; 63 64/* 65 * RTMP protocol handler.h, based on https://rtmpdump.mplayerhq.hu 66 */ 67 68const 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, /* write_resp */ 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 91const 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, /* write_resp */ 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 114const 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, /* write_resp */ 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 137const 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, /* write_resp */ 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 160const 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, /* write_resp */ 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 183const 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, /* write_resp */ 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 206static 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 223static 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 258static 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 276static 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 286static 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 301static 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 323static 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