From patchwork Wed May 7 20:09:51 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bernd Kuhls X-Patchwork-Id: 346806 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from whitealder.osuosl.org (whitealder.osuosl.org [140.211.166.138]) by ozlabs.org (Postfix) with ESMTP id EB3D61401B1 for ; Thu, 8 May 2014 06:12:52 +1000 (EST) Received: from localhost (localhost [127.0.0.1]) by whitealder.osuosl.org (Postfix) with ESMTP id 1D93A8C315; Wed, 7 May 2014 20:12:52 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from whitealder.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id vaT55fj50OxM; Wed, 7 May 2014 20:12:20 +0000 (UTC) Received: from ash.osuosl.org (ash.osuosl.org [140.211.166.34]) by whitealder.osuosl.org (Postfix) with ESMTP id 29ECA8C1D6; Wed, 7 May 2014 20:11:07 +0000 (UTC) X-Original-To: buildroot@lists.busybox.net Delivered-To: buildroot@osuosl.org Received: from silver.osuosl.org (silver.osuosl.org [140.211.166.136]) by ash.osuosl.org (Postfix) with ESMTP id E54281D076D for ; Wed, 7 May 2014 20:10:31 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by silver.osuosl.org (Postfix) with ESMTP id DF7BD2E2D4 for ; Wed, 7 May 2014 20:10:31 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from silver.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id h8iaKDv0LjQb for ; Wed, 7 May 2014 20:10:27 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from mailout11.t-online.de (mailout11.t-online.de [194.25.134.85]) by silver.osuosl.org (Postfix) with ESMTPS id E0EFA266D6 for ; Wed, 7 May 2014 20:10:25 +0000 (UTC) Received: from fwd03.aul.t-online.de (fwd03.aul.t-online.de [172.20.27.148]) by mailout11.t-online.de (Postfix) with SMTP id 9843E24FD9E for ; Wed, 7 May 2014 22:09:43 +0200 (CEST) Received: from fli4l.lan.fli4l (EqqtbgZXoh2pWq9sAlIJovr7aDN8Cqtvg3CzT5agFuVSKtgddWF0jjrzB+wGV4qZrc@[79.247.128.159]) by fwd03.t-online.de with esmtp id 1Wi8AX-1GHzJQ0; Wed, 7 May 2014 22:10:21 +0200 Received: from fli4lbuild64.lan.fli4l ([192.168.1.51]:41664) by fli4l.lan.fli4l with esmtpsa (TLSv1.2:DHE-RSA-AES256-GCM-SHA384:256) (Exim 4.80.1) (envelope-from ) id 1Wi8AW-0004UJ-K6; Wed, 07 May 2014 22:10:20 +0200 From: Bernd Kuhls To: buildroot@buildroot.org Date: Wed, 7 May 2014 22:09:51 +0200 Message-Id: <1399493406-7247-18-git-send-email-bernd.kuhls@t-online.de> X-Mailer: git-send-email 1.7.10.4 In-Reply-To: <1399493406-7247-1-git-send-email-bernd.kuhls@t-online.de> References: <1399493406-7247-1-git-send-email-bernd.kuhls@t-online.de> X-ID: EqqtbgZXoh2pWq9sAlIJovr7aDN8Cqtvg3CzT5agFuVSKtgddWF0jjrzB+wGV4qZrc X-TOI-MSGID: 1f303a58-4707-433a-814b-d653cccf6b61 Cc: Bernd Kuhls Subject: [Buildroot] [PATCH v5 17/32] rtmpdump: Add KSV patch X-BeenThere: buildroot@busybox.net X-Mailman-Version: 2.1.14 Precedence: list List-Id: Discussion and development of buildroot List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: buildroot-bounces@busybox.net Sender: buildroot-bounces@busybox.net Signed-off-by: Bernd Kuhls --- package/rtmpdump/rtmpdump-0001-ksv.patch | 3397 ++++++++++++++++++++++++++++++ 1 file changed, 3397 insertions(+) create mode 100644 package/rtmpdump/rtmpdump-0001-ksv.patch diff --git a/package/rtmpdump/rtmpdump-0001-ksv.patch b/package/rtmpdump/rtmpdump-0001-ksv.patch new file mode 100644 index 0000000..2536187 --- /dev/null +++ b/package/rtmpdump/rtmpdump-0001-ksv.patch @@ -0,0 +1,3397 @@ +Several fixes made or collected by KSV: +http://stream-recorder.com/forum/customized-rtmpdump-binaries-patch-file-t16103.html + +This patch file is the -p1 converted version of Patch.diff, contained in +rtmpdump-2.4.zip, downloaded from https://github.com/K-S-V/Scripts/releases +It includes "Update 21/03/2014" as latest update. + +Signed-off-by: Bernd Kuhls + +diff -uNr librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/librtmp/amf.c librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/librtmp/amf.c +--- librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/librtmp/amf.c 2014-03-02 19:20:23.000000000 +0100 ++++ librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/librtmp/amf.c 2014-05-04 17:55:17.513338440 +0200 +@@ -618,6 +618,9 @@ + return -1; + } + ++ if (*pBuffer == AMF_NULL) ++ bDecodeName = 0; ++ + if (bDecodeName && nSize < 4) + { /* at least name (length + at least 1 byte) and 1 byte of data */ + RTMP_Log(RTMP_LOGDEBUG, +@@ -729,13 +732,13 @@ + } + case AMF_DATE: + { +- RTMP_Log(RTMP_LOGDEBUG, "AMF_DATE"); +- + if (nSize < 10) + return -1; + + prop->p_vu.p_number = AMF_DecodeNumber(pBuffer); + prop->p_UTCoffset = AMF_DecodeInt16(pBuffer + 8); ++ RTMP_Log(RTMP_LOGDEBUG, "AMF_DATE: %f, UTC offset: %d", prop->p_vu.p_number, ++ prop->p_UTCoffset); + + nSize -= 10; + break; +@@ -807,8 +810,8 @@ + } + else + { +- name.av_val = "no-name."; +- name.av_len = sizeof("no-name.") - 1; ++ name.av_val = "no-name"; ++ name.av_len = sizeof ("no-name") - 1; + } + if (name.av_len > 18) + name.av_len = 18; +@@ -1068,17 +1071,18 @@ + + /*std::string str = className; */ + +- RTMP_Log(RTMP_LOGDEBUG, +- "Class name: %s, externalizable: %d, dynamic: %d, classMembers: %d", +- cd.cd_name.av_val, cd.cd_externalizable, cd.cd_dynamic, +- cd.cd_num); ++ RTMP_Log(RTMP_LOGDEBUG, "Class name: %.*s, externalizable: %d, dynamic: %d, classMembers: %d", ++ cd.cd_name.av_len, cd.cd_name.av_val, cd.cd_externalizable, cd.cd_dynamic, cd.cd_num); + + for (i = 0; i < cd.cd_num; i++) +- { +- AVal memberName; +- len = AMF3ReadString(pBuffer, &memberName); +- RTMP_Log(RTMP_LOGDEBUG, "Member: %s", memberName.av_val); +- AMF3CD_AddProp(&cd, &memberName); ++ { ++ AVal memberName = {NULL, 0}; ++ len = AMF3ReadString(pBuffer, &memberName); ++ if (memberName.av_val) ++ { ++ RTMP_Log(RTMP_LOGDEBUG, "Member: %s", memberName.av_val); ++ AMF3CD_AddProp(&cd, &memberName); ++ } + nSize -= len; + pBuffer += len; + } +@@ -1259,7 +1263,8 @@ + { + if (!(cd->cd_num & 0x0f)) + cd->cd_props = realloc(cd->cd_props, (cd->cd_num + 16) * sizeof(AVal)); +- cd->cd_props[cd->cd_num++] = *prop; ++ if (cd->cd_props) ++ cd->cd_props[cd->cd_num++] = *prop; + } + + AVal * +diff -uNr librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/librtmp/handshake.h librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/librtmp/handshake.h +--- librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/librtmp/handshake.h 2014-03-02 19:20:23.000000000 +0100 ++++ librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/librtmp/handshake.h 2014-05-04 17:55:17.517338389 +0200 +@@ -707,7 +707,7 @@ + uint32_t uptime; + + uint8_t clientbuf[RTMP_SIG_SIZE + 4], *clientsig=clientbuf+4; +- uint8_t serversig[RTMP_SIG_SIZE], client2[RTMP_SIG_SIZE], *reply; ++ uint8_t serversig[RTMP_SIG_SIZE], serversig1[RTMP_SIG_SIZE], client2[RTMP_SIG_SIZE], *reply; + uint8_t type; + getoff *getdh = NULL, *getdig = NULL; + +@@ -760,7 +760,7 @@ + #else + ip = (int32_t *)(clientsig+8); + for (i = 2; i < RTMP_SIG_SIZE/4; i++) +- *ip++ = rand(); ++ *ip++ = ((rand() & 0xFFFF) << 16) | (rand() & 0xFFFF); + #endif + + /* set handshake digest */ +@@ -825,6 +825,8 @@ + + if (ReadN(r, (char *)serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE) + return FALSE; ++ if (ReadN(r, (char *) serversig1, RTMP_SIG_SIZE) != RTMP_SIG_SIZE) ++ return FALSE; + + /* decode server response */ + memcpy(&uptime, serversig, 4); +@@ -834,7 +836,7 @@ + RTMP_Log(RTMP_LOGDEBUG, "%s: FMS Version : %d.%d.%d.%d", __FUNCTION__, serversig[4], + serversig[5], serversig[6], serversig[7]); + +- if (FP9HandShake && type == 3 && !serversig[4]) ++ if (FP9HandShake && type == 3 && (!serversig[4] || !serversig1[4])) + FP9HandShake = FALSE; + + #ifdef _DEBUG +@@ -914,7 +916,7 @@ + #else + ip = (int32_t *)reply; + for (i = 0; i < RTMP_SIG_SIZE/4; i++) +- *ip++ = rand(); ++ *ip++ = ((rand() & 0xFFFF) << 16) | (rand() & 0xFFFF); + #endif + /* calculate response now */ + signatureResp = reply+RTMP_SIG_SIZE-SHA256_DIGEST_LENGTH; +@@ -965,16 +967,22 @@ + __FUNCTION__); + RTMP_LogHex(RTMP_LOGDEBUG, reply, RTMP_SIG_SIZE); + #endif +- if (!WriteN(r, (char *)reply, RTMP_SIG_SIZE)) +- return FALSE; +- +- /* 2nd part of handshake */ +- if (ReadN(r, (char *)serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE) +- return FALSE; ++ if (r->Link.CombineConnectPacket) ++ { ++ char *HandshakeResponse = malloc(RTMP_SIG_SIZE); ++ memcpy(HandshakeResponse, (char *) reply, RTMP_SIG_SIZE); ++ r->Link.HandshakeResponse.av_val = HandshakeResponse; ++ r->Link.HandshakeResponse.av_len = RTMP_SIG_SIZE; ++ } ++ else ++ { ++ if (!WriteN(r, (char *) reply, RTMP_SIG_SIZE)) ++ return FALSE; ++ } + + #ifdef _DEBUG + RTMP_Log(RTMP_LOGDEBUG, "%s: 2nd handshake: ", __FUNCTION__); +- RTMP_LogHex(RTMP_LOGDEBUG, serversig, RTMP_SIG_SIZE); ++ RTMP_LogHex(RTMP_LOGDEBUG, serversig1, RTMP_SIG_SIZE); + #endif + + if (FP9HandShake) +@@ -982,21 +990,21 @@ + uint8_t signature[SHA256_DIGEST_LENGTH]; + uint8_t digest[SHA256_DIGEST_LENGTH]; + +- if (serversig[4] == 0 && serversig[5] == 0 && serversig[6] == 0 +- && serversig[7] == 0) ++ if (serversig1[4] == 0 && serversig1[5] == 0 && serversig1[6] == 0 ++ && serversig1[7] == 0) + { + RTMP_Log(RTMP_LOGDEBUG, + "%s: Wait, did the server just refuse signed authentication?", + __FUNCTION__); + } + RTMP_Log(RTMP_LOGDEBUG, "%s: Server sent signature:", __FUNCTION__); +- RTMP_LogHex(RTMP_LOGDEBUG, &serversig[RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH], ++ RTMP_LogHex(RTMP_LOGDEBUG, &serversig1[RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH], + SHA256_DIGEST_LENGTH); + + /* verify server response */ + HMACsha256(&clientsig[digestPosClient], SHA256_DIGEST_LENGTH, + GenuineFMSKey, sizeof(GenuineFMSKey), digest); +- HMACsha256(serversig, RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH, digest, ++ HMACsha256(serversig1, RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH, digest, + SHA256_DIGEST_LENGTH, signature); + + /* show some information */ +@@ -1024,7 +1032,7 @@ + RTMP_Log(RTMP_LOGDEBUG, "%s: Signature calculated:", __FUNCTION__); + RTMP_LogHex(RTMP_LOGDEBUG, signature, SHA256_DIGEST_LENGTH); + if (memcmp +- (signature, &serversig[RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH], ++ (signature, &serversig1[RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH], + SHA256_DIGEST_LENGTH) != 0) + { + RTMP_Log(RTMP_LOGWARNING, "%s: Server not genuine Adobe!", __FUNCTION__); +@@ -1057,7 +1065,7 @@ + } + else + { +- if (memcmp(serversig, clientsig, RTMP_SIG_SIZE) != 0) ++ if (memcmp(serversig1, clientsig, RTMP_SIG_SIZE) != 0) + { + RTMP_Log(RTMP_LOGWARNING, "%s: client signature does not match!", + __FUNCTION__); +@@ -1099,7 +1107,7 @@ + { + encrypted = FALSE; + } +- else if (type == 6 || type == 8) ++ else if (type == 6 || type == 8 || type == 9) + { + offalg = 1; + encrypted = TRUE; +@@ -1148,7 +1156,7 @@ + #else + ip = (int32_t *)(serversig+8); + for (i = 2; i < RTMP_SIG_SIZE/4; i++) +- *ip++ = rand(); ++ *ip++ = ((rand() & 0xFFFF) << 16) | (rand() & 0xFFFF); + #endif + + /* set handshake digest */ +diff -uNr librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/librtmp/hashswf.c librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/librtmp/hashswf.c +--- librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/librtmp/hashswf.c 2014-03-02 19:20:23.000000000 +0100 ++++ librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/librtmp/hashswf.c 2014-05-04 17:55:17.517338389 +0200 +@@ -70,7 +70,7 @@ + + #endif /* CRYPTO */ + +-#define AGENT "Mozilla/5.0" ++#define AGENT "Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20100101 Firefox/21.0" + + HTTPResult + HTTP_get(struct HTTP_ctx *http, const char *url, HTTP_read_callback *cb) +@@ -116,6 +116,8 @@ + + host = p1 + 3; + path = strchr(host, '/'); ++ if (!path) ++ return HTTPRES_BAD_REQUEST; + hlen = path - host; + strncpy(hbuf, host, hlen); + hbuf[hlen] = '\0'; +@@ -200,7 +202,7 @@ + } + + p1 = strchr(sb.sb_buf, ' '); +- rc = atoi(p1 + 1); ++ rc = p1 ? atoi(p1 + 1) : 400; + http->status = rc; + + if (rc >= 300) +@@ -528,9 +530,11 @@ + + if (strncmp(buf, "url: ", 5)) + continue; +- if (strncmp(buf + 5, url, hlen)) ++ if (strncmp(buf + 5, url, strlen(buf + 5) - 1)) + continue; + r1 = strrchr(buf, '/'); ++ if (!r1) ++ continue; + i = strlen(r1); + r1[--i] = '\0'; + if (strncmp(r1, file, i)) +@@ -640,7 +644,7 @@ + HMAC_finish(in.ctx, hash, hlen); + *size = in.size; + +- fprintf(f, "date: %s\n", date); ++ fprintf(f, "date: %s\n", date[0] ? date : cctim); + fprintf(f, "size: %08x\n", in.size); + fprintf(f, "hash: "); + for (i = 0; i < SHA256_DIGEST_LENGTH; i++) +diff -uNr librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/librtmp/log.c librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/librtmp/log.c +--- librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/librtmp/log.c 2014-03-02 19:20:23.000000000 +0100 ++++ librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/librtmp/log.c 2014-05-04 17:55:17.517338389 +0200 +@@ -52,8 +52,8 @@ + vsnprintf(str, MAX_PRINT_LEN-1, format, vl); + + /* Filter out 'no-name' */ +- if ( RTMP_debuglevelav_val = p; + app->av_len = applen; + RTMP_Log(RTMP_LOGDEBUG, "Parsed app : %.*s", applen, p); +diff -uNr librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/librtmp/rtmp.c librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/librtmp/rtmp.c +--- librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/librtmp/rtmp.c 2014-03-02 19:20:23.000000000 +0100 ++++ librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/librtmp/rtmp.c 2014-05-04 17:55:17.517338389 +0200 +@@ -28,6 +28,7 @@ + #include + #include + #include ++#include + + #include "rtmp_sys.h" + #include "log.h" +@@ -68,6 +69,7 @@ + + #define RTMP_SIG_SIZE 1536 + #define RTMP_LARGE_HEADER_SIZE 12 ++#define HEX2BIN(a) (((a)&0x40)?((a)&0xf)+9:((a)&0xf)) + + static const int packetSize[] = { 12, 8, 4, 1 }; + +@@ -108,17 +110,21 @@ + RTMPT_OPEN=0, RTMPT_SEND, RTMPT_IDLE, RTMPT_CLOSE + } RTMPTCmd; + ++static int ConnectSocket(RTMP *r); + static int DumpMetaData(AMFObject *obj); + static int HandShake(RTMP *r, int FP9HandShake); + static int SocksNegotiate(RTMP *r); + ++static int SendBytesReceived(RTMP *r); ++static int SendCommand(RTMP *r, char *method, int queue); + static int SendConnectPacket(RTMP *r, RTMPPacket *cp); + static int SendCheckBW(RTMP *r); + static int SendCheckBWResult(RTMP *r, double txn); + static int SendDeleteStream(RTMP *r, double dStreamId); + static int SendFCSubscribe(RTMP *r, AVal *subscribepath); ++static int SendGetStreamLength(RTMP *r); ++static int SendInvoke(RTMP *r, AVal *command, int queue); + static int SendPlay(RTMP *r); +-static int SendBytesReceived(RTMP *r); + static int SendUsherToken(RTMP *r, AVal *usherToken); + + #if 0 /* unused */ +@@ -336,10 +342,13 @@ + r->m_nClientBW = 2500000; + r->m_nClientBW2 = 2; + r->m_nServerBW = 2500000; +- r->m_fAudioCodecs = 3191.0; ++ r->m_fAudioCodecs = 3575.0; + r->m_fVideoCodecs = 252.0; ++ r->m_fEncoding = 3.0; + r->Link.timeout = 30; + r->Link.swfAge = 30; ++ r->Link.CombineConnectPacket = TRUE; ++ r->Link.ConnectPacket = FALSE; + } + + void +@@ -357,6 +366,8 @@ + int + RTMP_IsConnected(RTMP *r) + { ++ if (r->m_sb.sb_size > 0) ++ return TRUE; + return r->m_sb.sb_socket != -1; + } + +@@ -443,6 +454,7 @@ + AVal *flashVer, + AVal *subscribepath, + AVal *usherToken, ++ AVal *WeebToken, + int dStart, + int dStop, int bLiveStream, long int timeout) + { +@@ -465,6 +477,8 @@ + RTMP_Log(RTMP_LOGDEBUG, "subscribepath : %s", subscribepath->av_val); + if (usherToken && usherToken->av_val) + RTMP_Log(RTMP_LOGDEBUG, "NetStream.Authenticate.UsherToken : %s", usherToken->av_val); ++ if (WeebToken && WeebToken->av_val) ++ RTMP_Log(RTMP_LOGDEBUG, "WeebToken: %s", WeebToken->av_val); + if (flashVer && flashVer->av_val) + RTMP_Log(RTMP_LOGDEBUG, "flashVer : %s", flashVer->av_val); + if (dStart > 0) +@@ -513,6 +527,8 @@ + r->Link.subscribepath = *subscribepath; + if (usherToken && usherToken->av_len) + r->Link.usherToken = *usherToken; ++ if (WeebToken && WeebToken->av_len) ++ r->Link.WeebToken = *WeebToken; + r->Link.seekTime = dStart; + r->Link.stopTime = dStop; + if (bLiveStream) +@@ -570,14 +586,22 @@ + "Stream is live, no seeking possible" }, + { AVC("subscribe"), OFF(Link.subscribepath), OPT_STR, 0, + "Stream to subscribe to" }, +- { AVC("jtv"), OFF(Link.usherToken), OPT_STR, 0, +- "Justin.tv authentication token" }, +- { AVC("token"), OFF(Link.token), OPT_STR, 0, ++ { AVC("jtv"), OFF(Link.usherToken), OPT_STR, 0, ++ "Justin.tv authentication token"}, ++ { AVC("weeb"), OFF(Link.WeebToken), OPT_STR, 0, ++ "Weeb.tv authentication token"}, ++ { AVC("token"), OFF(Link.token), OPT_STR, 0, + "Key for SecureToken response" }, + { AVC("swfVfy"), OFF(Link.lFlags), OPT_BOOL, RTMP_LF_SWFV, + "Perform SWF Verification" }, + { AVC("swfAge"), OFF(Link.swfAge), OPT_INT, 0, + "Number of days to use cached SWF hash" }, ++#ifdef CRYPTO ++ { AVC("swfsize"), OFF(Link.swfSize), OPT_INT, 0, ++ "Size of the decompressed SWF file"}, ++ { AVC("swfhash"), OFF(Link.swfHash), OPT_STR, 0, ++ "SHA256 hash of the decompressed SWF file"}, ++#endif + { AVC("start"), OFF(Link.seekTime), OPT_INT, 0, + "Stream start position in milliseconds" }, + { AVC("stop"), OFF(Link.stopTime), OPT_INT, 0, +@@ -765,7 +789,7 @@ + if (!ret) + return ret; + r->Link.port = port; +- r->Link.playpath = r->Link.playpath0; ++ r->Link.playpath = AVcopy(r->Link.playpath0); + + while (ptr) { + *ptr++ = '\0'; +@@ -842,9 +866,16 @@ + } + + #ifdef CRYPTO +- if ((r->Link.lFlags & RTMP_LF_SWFV) && r->Link.swfUrl.av_len) +- RTMP_HashSWF(r->Link.swfUrl.av_val, &r->Link.SWFSize, +- (unsigned char *)r->Link.SWFHash, r->Link.swfAge); ++ RTMP_Log(RTMP_LOGDEBUG, "Khalsa: %d %d %s", r->Link.swfSize, r->Link.swfHash.av_len, r->Link.swfHash.av_val); ++ if (r->Link.swfSize && r->Link.swfHash.av_len) ++ { ++ int i, j = 0; ++ for (i = 0; i < r->Link.swfHash.av_len; i += 2) ++ r->Link.SWFHash[j++] = (HEX2BIN(r->Link.swfHash.av_val[i]) << 4) | HEX2BIN(r->Link.swfHash.av_val[i + 1]); ++ r->Link.SWFSize = (uint32_t) r->Link.swfSize; ++ } ++ else if ((r->Link.lFlags & RTMP_LF_SWFV) && r->Link.swfUrl.av_len) ++ RTMP_HashSWF(r->Link.swfUrl.av_val, &r->Link.SWFSize, (unsigned char *) r->Link.SWFHash, r->Link.swfAge); + #endif + + SocksSetup(r, &r->Link.sockshost); +@@ -947,6 +978,8 @@ + } + + setsockopt(r->m_sb.sb_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &on, sizeof(on)); ++ if (r->Link.protocol & RTMP_FEATURE_HTTP) ++ setsockopt(r->m_sb.sb_socket, SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof (on)); + + return TRUE; + } +@@ -1397,41 +1430,96 @@ + ptr = buffer; + while (n > 0) + { +- int nBytes = 0, nRead; ++ int nBytes = 0, nRead, status = 0, retries = 0; + if (r->Link.protocol & RTMP_FEATURE_HTTP) + { +- int refill = 0; +- while (!r->m_resplen) +- { +- int ret; +- if (r->m_sb.sb_size < 13 || refill) +- { +- if (!r->m_unackd) +- HTTP_Post(r, RTMPT_IDLE, "", 1); +- if (RTMPSockBuf_Fill(&r->m_sb) < 1) +- { +- if (!r->m_sb.sb_timedout) +- RTMP_Close(r); +- return 0; +- } +- } +- if ((ret = HTTP_read(r, 0)) == -1) +- { +- RTMP_Log(RTMP_LOGDEBUG, "%s, No valid HTTP response found", __FUNCTION__); +- RTMP_Close(r); +- return 0; +- } +- else if (ret == -2) ++ while (!r->m_resplen) ++ { ++ /* Refill if socket buffer is empty */ ++ if (!r->m_sb.sb_size) + { +- refill = 1; ++ if (retries > 30) ++ { ++ RTMP_Close(r); ++ return 0; ++ } ++ ++ if (!r->m_unackd) ++ { ++ if (retries > 0) ++ { ++ HTTP_Post(r, RTMPT_IDLE, "", 1); ++ r->m_unackd = TRUE; ++ } ++ retries++; ++ ++ if (!r->m_bPlaying) ++ sleep(.25); ++ } ++ ++ RTMP_Log(RTMP_LOGDEBUG, "Trying to fill HTTP buffer, Retries: %d", retries); ++ status = RTMPSockBuf_Fill(&r->m_sb); ++ /* Reconnect socket when closed by some moronic servers after ++ * every HTTP data packet */ ++ if (status < 1) ++ { ++ /* Close connection on connection reset */ ++ if (status == -1) ++ { ++ RTMP_Close(r); ++ return 0; ++ } ++ ++ RTMP_Log(RTMP_LOGDEBUG, "Reconnecting socket, Status: %d", status); ++ if (ConnectSocket(r)) ++ { ++ HTTP_Post(r, RTMPT_IDLE, "", 1); ++ r->m_unackd = TRUE; ++ retries++; ++ } ++ else ++ { ++ RTMP_Close(r); ++ return 0; ++ } ++ } + } +- else ++ ++ RTMP_Log(RTMP_LOGDEBUG, "Trying to read HTTP response, Bytes Available: %d", r->m_sb.sb_size); ++ status = HTTP_read(r, 0); ++ if (status == -1) + { +- refill = 0; ++ RTMP_Log(RTMP_LOGDEBUG, "%s, No valid HTTP response found", __FUNCTION__); ++ RTMP_Close(r); ++ return 0; + } +- } +- if (r->m_resplen && !r->m_sb.sb_size) +- RTMPSockBuf_Fill(&r->m_sb); ++ else if (status == -2) ++ { ++ if (RTMPSockBuf_Fill(&r->m_sb) < 1) ++ if (!r->m_sb.sb_timedout) ++ { ++ RTMP_Close(r); ++ return 0; ++ } ++ } ++ else if (status == -3) ++ { ++ RTMP_Close(r); ++ return 0; ++ } ++ else ++ r->m_unackd = FALSE; ++ } ++ ++ /* Refill when there is still some data to be read and socket buffer ++ * is empty */ ++ if (r->m_resplen && (!r->m_sb.sb_size)) ++ { ++ if (RTMPSockBuf_Fill(&r->m_sb) < 1) ++ if (!r->m_sb.sb_timedout) ++ RTMP_Close(r); ++ } ++ + avail = r->m_sb.sb_size; + if (avail > r->m_resplen) + avail = r->m_resplen; +@@ -1458,10 +1546,9 @@ + r->m_sb.sb_size -= nRead; + nBytes = nRead; + r->m_nBytesIn += nRead; +- if (r->m_bSendCounter +- && r->m_nBytesIn > ( r->m_nBytesInSent + r->m_nClientBW / 10)) +- if (!SendBytesReceived(r)) +- return FALSE; ++ if (r->m_bSendCounter && r->m_nBytesIn > (r->m_nBytesInSent + r->m_nClientBW / 10)) ++ if (!SendBytesReceived(r)) ++ return FALSE; + } + /*RTMP_Log(RTMP_LOGDEBUG, "%s: %d bytes\n", __FUNCTION__, nBytes); */ + #ifdef _DEBUG +@@ -1472,7 +1559,8 @@ + { + RTMP_Log(RTMP_LOGDEBUG, "%s, RTMP socket closed by peer", __FUNCTION__); + /*goto again; */ +- RTMP_Close(r); ++ if (!r->m_sb.sb_timedout) ++ RTMP_Close(r); + break; + } + +@@ -1497,6 +1585,7 @@ + WriteN(RTMP *r, const char *buffer, int n) + { + const char *ptr = buffer; ++ char *ConnectPacket = 0; + #ifdef CRYPTO + char *encrypted = 0; + char buf[RTMP_BUFFER_CACHE_SIZE]; +@@ -1512,6 +1601,15 @@ + } + #endif + ++ if (r->Link.ConnectPacket) ++ { ++ char *ConnectPacket = malloc(r->Link.HandshakeResponse.av_len + n); ++ memcpy(ConnectPacket, r->Link.HandshakeResponse.av_val, r->Link.HandshakeResponse.av_len); ++ memcpy(ConnectPacket + r->Link.HandshakeResponse.av_len, ptr, n); ++ ptr = ConnectPacket; ++ n += r->Link.HandshakeResponse.av_len; ++ } ++ + while (n > 0) + { + int nBytes; +@@ -1548,6 +1646,14 @@ + free(encrypted); + #endif + ++ if (r->Link.ConnectPacket) ++ { ++ if (r->Link.HandshakeResponse.av_val) ++ free(r->Link.HandshakeResponse.av_val); ++ free(ConnectPacket); ++ r->Link.ConnectPacket = FALSE; ++ } ++ + return n == 0; + } + +@@ -1577,6 +1683,9 @@ + char pbuf[4096], *pend = pbuf + sizeof(pbuf); + char *enc; + ++ if (r->Link.CombineConnectPacket) ++ r->Link.ConnectPacket = TRUE; ++ + if (cp) + return RTMP_SendPacket(r, cp, TRUE); + +@@ -1625,7 +1734,7 @@ + enc = AMF_EncodeNamedBoolean(enc, pend, &av_fpad, FALSE); + if (!enc) + return FALSE; +- enc = AMF_EncodeNamedNumber(enc, pend, &av_capabilities, 15.0); ++ enc = AMF_EncodeNamedNumber(enc, pend, &av_capabilities, 239.0); + if (!enc) + return FALSE; + enc = AMF_EncodeNamedNumber(enc, pend, &av_audioCodecs, r->m_fAudioCodecs); +@@ -1789,7 +1898,7 @@ + packet.m_hasAbsTimestamp = 0; + packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; + +- RTMP_Log(RTMP_LOGDEBUG, "UsherToken: %s", usherToken->av_val); ++ RTMP_Log(RTMP_LOGDEBUG, "UsherToken: %.*s", usherToken->av_len, usherToken->av_val); + enc = packet.m_body; + enc = AMF_EncodeString(enc, pend, &av_NetStream_Authenticate_UsherToken); + enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); +@@ -2095,6 +2204,7 @@ + } + + SAVC(_checkbw); ++SAVC(checkBandwidth); + + static int + SendCheckBW(RTMP *r) +@@ -2112,7 +2222,7 @@ + packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; + + enc = packet.m_body; +- enc = AMF_EncodeString(enc, pend, &av__checkbw); ++ enc = AMF_EncodeString(enc, pend, &av_checkBandwidth); + enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); + *enc++ = AMF_NULL; + +@@ -2219,10 +2329,8 @@ + enc = AMF_EncodeNumber(enc, pend, -1000.0); + else + { +- if (r->Link.seekTime > 0.0) +- enc = AMF_EncodeNumber(enc, pend, r->Link.seekTime); /* resume from here */ +- else +- enc = AMF_EncodeNumber(enc, pend, 0.0); /*-2000.0);*/ /* recorded as default, -2000.0 is not reliable since that freezes the player if the stream is not found */ ++ if (r->Link.seekTime > 0.0 || r->Link.stopTime) ++ enc = AMF_EncodeNumber(enc, pend, r->Link.seekTime); /* resume from here */ + } + if (!enc) + return FALSE; +@@ -2338,7 +2446,7 @@ + int nSize; + char *buf; + +- RTMP_Log(RTMP_LOGDEBUG, "sending ctrl. type: 0x%04x", (unsigned short)nType); ++ RTMP_Log(RTMP_LOGDEBUG, "sending ctrl, type: 0x%04x", (unsigned short)nType); + + packet.m_nChannel = 0x02; /* control channel (ping) */ + packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; +@@ -2370,8 +2478,8 @@ + } + else if (nType == 0x1A) + { +- *buf = nObject & 0xff; +- } ++ *buf = nObject & 0xff; ++ } + else + { + if (nSize > 2) +@@ -2885,6 +2993,7 @@ + #endif + + ++SAVC(onBWCheck); + SAVC(onBWDone); + SAVC(onFCSubscribe); + SAVC(onFCUnsubscribe); +@@ -2897,24 +3006,24 @@ + SAVC(description); + SAVC(onStatus); + SAVC(playlist_ready); ++SAVC(cps); ++SAVC(getStreamLength); ++SAVC(sendStatus); ++SAVC(verifyClient); + static const AVal av_NetStream_Failed = AVC("NetStream.Failed"); + static const AVal av_NetStream_Play_Failed = AVC("NetStream.Play.Failed"); +-static const AVal av_NetStream_Play_StreamNotFound = +-AVC("NetStream.Play.StreamNotFound"); +-static const AVal av_NetConnection_Connect_InvalidApp = +-AVC("NetConnection.Connect.InvalidApp"); ++static const AVal av_NetStream_Play_StreamNotFound = AVC("NetStream.Play.StreamNotFound"); ++static const AVal av_NetConnection_Connect_InvalidApp = AVC("NetConnection.Connect.InvalidApp"); + static const AVal av_NetStream_Play_Start = AVC("NetStream.Play.Start"); + static const AVal av_NetStream_Play_Complete = AVC("NetStream.Play.Complete"); + static const AVal av_NetStream_Play_Stop = AVC("NetStream.Play.Stop"); + static const AVal av_NetStream_Seek_Notify = AVC("NetStream.Seek.Notify"); + static const AVal av_NetStream_Pause_Notify = AVC("NetStream.Pause.Notify"); +-static const AVal av_NetStream_Play_PublishNotify = +-AVC("NetStream.Play.PublishNotify"); +-static const AVal av_NetStream_Play_UnpublishNotify = +-AVC("NetStream.Play.UnpublishNotify"); ++static const AVal av_NetStream_Play_PublishNotify = AVC("NetStream.Play.PublishNotify"); ++static const AVal av_NetStream_Play_UnpublishNotify = AVC("NetStream.Play.UnpublishNotify"); + static const AVal av_NetStream_Publish_Start = AVC("NetStream.Publish.Start"); +-static const AVal av_NetConnection_Connect_Rejected = +-AVC("NetConnection.Connect.Rejected"); ++static const AVal av_NetConnection_Connect_Rejected = AVC("NetConnection.Connect.Rejected"); ++static const AVal av_NetConnection_confStream = AVC("NetConnection.confStream"); + + /* Returns 0 for OK/Failed/error, 1 for 'Stop or Complete' */ + static int +@@ -2924,6 +3033,11 @@ + AVal method; + double txn; + int ret = 0, nRes; ++ char pbuf[256], *pend = pbuf + sizeof (pbuf), *enc, **params = NULL; ++ char *host = r->Link.hostname.av_len ? r->Link.hostname.av_val : ""; ++ char *pageUrl = r->Link.pageUrl.av_len ? r->Link.pageUrl.av_val : ""; ++ int param_count; ++ AVal av_Command, av_Response; + if (body[0] != 0x02) /* make sure it is a string method name we start with */ + { + RTMP_Log(RTMP_LOGWARNING, "%s, Sanity failed. no string method in invoke packet", +@@ -2985,46 +3099,221 @@ + RTMP_SendServerBW(r); + RTMP_SendCtrl(r, 3, 0, 300); + } +- RTMP_SendCreateStream(r); ++ if (strstr(host, "tv-stream.to") || strstr(pageUrl, "tv-stream.to")) ++ { ++ static char auth[] = {'h', 0xC2, 0xA7, '4', 'j', 'h', 'H', '4', '3', 'd'}; ++ AVal av_auth; ++ SAVC(requestAccess); ++ av_auth.av_val = auth; ++ av_auth.av_len = sizeof (auth); ++ ++ enc = pbuf; ++ enc = AMF_EncodeString(enc, pend, &av_requestAccess); ++ enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); ++ *enc++ = AMF_NULL; ++ enc = AMF_EncodeString(enc, pend, &av_auth); ++ av_Command.av_val = pbuf; ++ av_Command.av_len = enc - pbuf; ++ SendInvoke(r, &av_Command, FALSE); ++ ++ SendCommand(r, "getConnectionCount", FALSE); ++ SendGetStreamLength(r); ++ RTMP_SendCreateStream(r); ++ } ++ else if (strstr(host, "featve.com") || strstr(pageUrl, "featve.com")) ++ { ++ AVal av_auth = AVC("yes"); ++ SAVC(youCannotPlayMe); + +- if (!(r->Link.protocol & RTMP_FEATURE_WRITE)) +- { +- /* Authenticate on Justin.tv legacy servers before sending FCSubscribe */ +- if (r->Link.usherToken.av_len) +- SendUsherToken(r, &r->Link.usherToken); +- /* Send the FCSubscribe if live stream or if subscribepath is set */ +- if (r->Link.subscribepath.av_len) +- SendFCSubscribe(r, &r->Link.subscribepath); +- else if (r->Link.lFlags & RTMP_LF_LIVE) +- SendFCSubscribe(r, &r->Link.playpath); +- } +- } ++ enc = pbuf; ++ enc = AMF_EncodeString(enc, pend, &av_youCannotPlayMe); ++ enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); ++ *enc++ = AMF_NULL; ++ enc = AMF_EncodeString(enc, pend, &av_auth); ++ av_Command.av_val = pbuf; ++ av_Command.av_len = enc - pbuf; ++ SendInvoke(r, &av_Command, FALSE); ++ ++ RTMP_SendCreateStream(r); ++ } ++ else if (strstr(host, "wfctv.com") || strstr(pageUrl, "wfctv.com")) ++ { ++ AVal av_auth1 = AVC("zoivid"); ++ AVal av_auth2 = AVC("yePi4jee"); ++ SAVC(stream_login); ++ ++ enc = pbuf; ++ enc = AMF_EncodeString(enc, pend, &av_stream_login); ++ enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); ++ *enc++ = AMF_NULL; ++ enc = AMF_EncodeString(enc, pend, &av_auth1); ++ enc = AMF_EncodeString(enc, pend, &av_auth2); ++ av_Command.av_val = pbuf; ++ av_Command.av_len = enc - pbuf; ++ SendInvoke(r, &av_Command, FALSE); ++ ++ RTMP_SendCreateStream(r); ++ } ++ else if (strstr(host, "streamscene.cc") || strstr(pageUrl, "streamscene.cc") ++ || strstr(host, "tsboard.tv") || strstr(pageUrl, "teamstream.in") ++ || strstr(host, "hdstreams.tv") || strstr(pageUrl, "teamstream.to") ++ || strstr(pageUrl, "istreams.to")) ++ { ++ SendCommand(r, "r", FALSE); ++ SendGetStreamLength(r); ++ RTMP_SendCreateStream(r); ++ } ++ else if (strstr(host, "pc3oot.us.to")) ++ { ++ SendCommand(r, "StreamPiraten", TRUE); ++ SendGetStreamLength(r); ++ RTMP_SendCreateStream(r); ++ } ++ else if (strstr(pageUrl, "axcast.com")) ++ { ++ SendCommand(r, "requestData", FALSE); ++ RTMP_SendCreateStream(r); ++ } ++ else if (strstr(pageUrl, "dhmediahosting.com")) ++ { ++ SendCommand(r, "netStreamEnable", FALSE); ++ RTMP_SendCreateStream(r); ++ } ++ else if (strstr(pageUrl, "ezcast.tv")) ++ { ++ SendCommand(r, "jaSakamCarevataKerka", TRUE); ++ RTMP_SendCreateStream(r); ++ } ++ else if (strstr(pageUrl, "liveflash.tv")) ++ { ++ SendCommand(r, "kaskatija", TRUE); ++ RTMP_SendCreateStream(r); ++ } ++ else if (strstr(pageUrl, "mips.tv")) ++ { ++ SendCommand(r, "gaolVanus", TRUE); ++ RTMP_SendCreateStream(r); ++ } ++ else if (strstr(pageUrl, "ucaster.eu")) ++ { ++ SendCommand(r, "vujkoMiLazarBarakovOdMokrino", TRUE); ++ RTMP_SendCreateStream(r); ++ } ++ else if (strstr(pageUrl, "yukons.net")) ++ { ++ SendCommand(r, "trxuwaaLahRKnaechb", TRUE); ++ RTMP_SendCreateStream(r); ++ } ++ else if (strstr(pageUrl, "yycast.com")) ++ { ++ SendCommand(r, "trajkoProkopiev", TRUE); ++ RTMP_SendCreateStream(r); ++ } ++ else if ((strstr(host, "highwebmedia.com") || strstr(pageUrl, "chaturbate.com")) ++ && (!strstr(host, "origin"))) ++ { ++ AVal av_ModelName; ++ SAVC(CheckPublicStatus); ++ ++ if (strlen(pageUrl) > 7) ++ { ++ strsplit(pageUrl + 7, FALSE, '/', ¶ms); ++ av_ModelName.av_val = params[1]; ++ av_ModelName.av_len = strlen(params[1]); ++ ++ enc = pbuf; ++ enc = AMF_EncodeString(enc, pend, &av_CheckPublicStatus); ++ enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); ++ *enc++ = AMF_NULL; ++ enc = AMF_EncodeString(enc, pend, &av_ModelName); ++ av_Command.av_val = pbuf; ++ av_Command.av_len = enc - pbuf; ++ ++ SendInvoke(r, &av_Command, FALSE); ++ } ++ else ++ { ++ RTMP_Log(RTMP_LOGERROR, "you must specify the pageUrl"); ++ RTMP_Close(r); ++ } ++ } ++ /* Weeb.tv specific authentication */ ++ else if (r->Link.WeebToken.av_len) ++ { ++ AVal av_Token, av_Username, av_Password; ++ SAVC(determineAccess); ++ ++ param_count = strsplit(r->Link.WeebToken.av_val, FALSE, ';', ¶ms); ++ if (param_count >= 1) ++ { ++ av_Token.av_val = params[0]; ++ av_Token.av_len = strlen(params[0]); ++ } ++ if (param_count >= 2) ++ { ++ av_Username.av_val = params[1]; ++ av_Username.av_len = strlen(params[1]); ++ } ++ if (param_count >= 3) ++ { ++ av_Password.av_val = params[2]; ++ av_Password.av_len = strlen(params[2]); ++ } ++ ++ enc = pbuf; ++ enc = AMF_EncodeString(enc, pend, &av_determineAccess); ++ enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); ++ *enc++ = AMF_NULL; ++ enc = AMF_EncodeString(enc, pend, &av_Token); ++ enc = AMF_EncodeString(enc, pend, &av_Username); ++ enc = AMF_EncodeString(enc, pend, &av_Password); ++ av_Command.av_val = pbuf; ++ av_Command.av_len = enc - pbuf; ++ ++ RTMP_Log(RTMP_LOGDEBUG, "WeebToken: %s", r->Link.WeebToken.av_val); ++ SendInvoke(r, &av_Command, FALSE); ++ } ++ else ++ RTMP_SendCreateStream(r); ++ } + else if (AVMATCH(&methodInvoked, &av_createStream)) +- { +- r->m_stream_id = (int)AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3)); ++ { ++ r->m_stream_id = (int) AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3)); + +- if (r->Link.protocol & RTMP_FEATURE_WRITE) +- { +- SendPublish(r); +- } +- else +- { +- if (r->Link.lFlags & RTMP_LF_PLST) +- SendPlaylist(r); +- SendPlay(r); +- RTMP_SendCtrl(r, 3, r->m_stream_id, r->m_nBufferMS); +- } +- } ++ if (!(r->Link.protocol & RTMP_FEATURE_WRITE)) ++ { ++ /* Authenticate on Justin.tv legacy servers before sending FCSubscribe */ ++ if (r->Link.usherToken.av_len) ++ SendUsherToken(r, &r->Link.usherToken); ++ /* Send the FCSubscribe if live stream or if subscribepath is set */ ++ if (r->Link.subscribepath.av_len) ++ SendFCSubscribe(r, &r->Link.subscribepath); ++ else if ((r->Link.lFlags & RTMP_LF_LIVE) && (!r->Link.WeebToken.av_len)) ++ SendFCSubscribe(r, &r->Link.playpath); ++ } ++ ++ if (r->Link.protocol & RTMP_FEATURE_WRITE) ++ { ++ SendPublish(r); ++ } ++ else ++ { ++ if (r->Link.lFlags & RTMP_LF_PLST) ++ SendPlaylist(r); ++ SendPlay(r); ++ RTMP_SendCtrl(r, 3, r->m_stream_id, r->m_nBufferMS); ++ } ++ } + else if (AVMATCH(&methodInvoked, &av_play) || +- AVMATCH(&methodInvoked, &av_publish)) +- { +- r->m_bPlaying = TRUE; +- } ++ AVMATCH(&methodInvoked, &av_publish)) ++ { ++ r->m_bPlaying = TRUE; ++ } + free(methodInvoked.av_val); + } + else if (AVMATCH(&method, &av_onBWDone)) + { +- if (!r->m_nBWCheckCounter) ++ if (!r->m_nBWCheckCounter) + SendCheckBW(r); + } + else if (AVMATCH(&method, &av_onFCSubscribe)) +@@ -3048,21 +3337,22 @@ + { + int i; + for (i = 0; i < r->m_numCalls; i++) +- if (AVMATCH(&r->m_methodCalls[i].name, &av__checkbw)) +- { +- AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE); +- break; +- } ++ if (AVMATCH(&r->m_methodCalls[i].name, &av__checkbw)) ++ { ++ AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE); ++ break; ++ } + } + else if (AVMATCH(&method, &av__error)) + { ++ int handled = FALSE; + #ifdef CRYPTO + AVal methodInvoked = {0}; + int i; + + if (r->Link.protocol & RTMP_FEATURE_WRITE) + { +- for (i=0; im_numCalls; i++) ++ for (i = 0; i < r->m_numCalls; i++) + { + if (r->m_methodCalls[i].num == txn) + { +@@ -3074,12 +3364,12 @@ + if (!methodInvoked.av_val) + { + RTMP_Log(RTMP_LOGDEBUG, "%s, received result id %f without matching request", +- __FUNCTION__, txn); ++ __FUNCTION__, txn); + goto leave; + } + + RTMP_Log(RTMP_LOGDEBUG, "%s, received error for method call <%s>", __FUNCTION__, +- methodInvoked.av_val); ++ methodInvoked.av_val); + + if (AVMATCH(&methodInvoked, &av_connect)) + { +@@ -3093,20 +3383,65 @@ + /* if PublisherAuth returns 1, then reconnect */ + PublisherAuth(r, &description); + } +- } +- else +- { +- RTMP_Log(RTMP_LOGERROR, "rtmp server sent error"); ++ handled = TRUE; + } + free(methodInvoked.av_val); +-#else +- RTMP_Log(RTMP_LOGERROR, "rtmp server sent error"); + #endif ++ double code = 0.0; ++ unsigned int parsedPort = 0; ++ AMFObject obj2; ++ AMFObjectProperty p; ++ AVal redirect; ++ SAVC(ex); ++ SAVC(redirect); ++ ++ AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &obj2); ++ if (RTMP_FindFirstMatchingProperty(&obj2, &av_ex, &p)) ++ { ++ AMFProp_GetObject(&p, &obj2); ++ if (RTMP_FindFirstMatchingProperty(&obj2, &av_code, &p)) ++ code = AMFProp_GetNumber(&p); ++ if (code == 302 && RTMP_FindFirstMatchingProperty(&obj2, &av_redirect, &p)) ++ { ++ AMFProp_GetString(&p, &redirect); ++ r->Link.redirected = TRUE; ++ ++ char *playpath = "//playpath"; ++ int len = redirect.av_len + strlen(playpath); ++ char *url = malloc(len + 1); ++ memcpy(url, redirect.av_val, redirect.av_len); ++ memcpy(url + redirect.av_len, playpath, strlen(playpath)); ++ url[len] = '\0'; ++ r->Link.tcUrl.av_val = url; ++ r->Link.tcUrl.av_len = redirect.av_len; ++ RTMP_ParseURL(url, &r->Link.protocol, &r->Link.hostname, &parsedPort, &r->Link.playpath0, &r->Link.app); ++ if (parsedPort) ++ r->Link.port = parsedPort; ++ } ++ } ++ if (r->Link.redirected) ++ { ++ handled = TRUE; ++ RTMP_Log(RTMP_LOGINFO, "rtmp server sent redirect"); ++ } ++ ++ if (!handled) ++ RTMP_Log(RTMP_LOGERROR, "rtmp server sent error"); + } + else if (AVMATCH(&method, &av_close)) + { +- RTMP_Log(RTMP_LOGERROR, "rtmp server requested close"); +- RTMP_Close(r); ++ if (r->Link.redirected) ++ { ++ r->Link.redirected = FALSE; ++ RTMP_Close(r); ++ RTMP_Log(RTMP_LOGINFO, "trying to connect with redirected url"); ++ RTMP_Connect(r, NULL); ++ } ++ else ++ { ++ RTMP_Log(RTMP_LOGERROR, "rtmp server requested close"); ++ RTMP_Close(r); ++ } + #ifdef CRYPTO + if ((r->Link.protocol & RTMP_FEATURE_WRITE) && + !(r->Link.pFlags & RTMP_PUB_CLEAN) && +@@ -3127,16 +3462,18 @@ + else if (AVMATCH(&method, &av_onStatus)) + { + AMFObject obj2; +- AVal code, level; ++ AVal code, level, description; + AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &obj2); + AMFProp_GetString(AMF_GetProp(&obj2, &av_code, -1), &code); + AMFProp_GetString(AMF_GetProp(&obj2, &av_level, -1), &level); ++ AMFProp_GetString(AMF_GetProp(&obj2, &av_description, -1), &description); + + RTMP_Log(RTMP_LOGDEBUG, "%s, onStatus: %s", __FUNCTION__, code.av_val); + if (AVMATCH(&code, &av_NetStream_Failed) +- || AVMATCH(&code, &av_NetStream_Play_Failed) +- || AVMATCH(&code, &av_NetStream_Play_StreamNotFound) +- || AVMATCH(&code, &av_NetConnection_Connect_InvalidApp)) ++ || AVMATCH(&code, &av_NetStream_Play_Failed) ++ || AVMATCH(&code, &av_NetStream_Play_StreamNotFound) ++ || AVMATCH(&code, &av_NetConnection_Connect_Rejected) ++ || AVMATCH(&code, &av_NetConnection_Connect_InvalidApp)) + { + r->m_stream_id = -1; + RTMP_Close(r); +@@ -3194,6 +3531,46 @@ + r->m_pausing = 3; + } + } ++ ++ else if (AVMATCH(&code, &av_NetConnection_confStream)) ++ { ++#ifdef CRYPTO ++ static const char hexdig[] = "0123456789abcdef"; ++ AVal auth; ++ SAVC(cf_stream); ++ int i; ++ char hash_hex[33] = {0}; ++ unsigned char hash[16]; ++ ++ param_count = strsplit(description.av_val, description.av_len, ':', ¶ms); ++ if (param_count >= 3) ++ { ++ char *buf = malloc(strlen(params[0]) + r->Link.playpath.av_len + 1); ++ strcpy(buf, params[0]); ++ strncat(buf, r->Link.playpath.av_val, r->Link.playpath.av_len); ++ md5_hash((unsigned char *) buf, strlen(buf), hash); ++ for (i = 0; i < 16; i++) ++ { ++ hash_hex[i * 2] = hexdig[0x0f & (hash[i] >> 4)]; ++ hash_hex[i * 2 + 1] = hexdig[0x0f & (hash[i])]; ++ } ++ auth.av_val = &hash_hex[atoi(params[1]) - 1]; ++ auth.av_len = atoi(params[2]); ++ RTMP_Log(RTMP_LOGDEBUG, "Khalsa: %.*s", auth.av_len, auth.av_val); ++ ++ enc = pbuf; ++ enc = AMF_EncodeString(enc, pend, &av_cf_stream); ++ enc = AMF_EncodeNumber(enc, pend, txn); ++ *enc++ = AMF_NULL; ++ enc = AMF_EncodeString(enc, pend, &auth); ++ av_Command.av_val = pbuf; ++ av_Command.av_len = enc - pbuf; ++ ++ SendInvoke(r, &av_Command, FALSE); ++ free(buf); ++ } ++#endif ++ } + } + else if (AVMATCH(&method, &av_playlist_ready)) + { +@@ -3207,6 +3584,85 @@ + } + } + } ++ else if (AVMATCH(&method, &av_verifyClient)) ++ { ++ double VerificationNumber = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3)); ++ RTMP_Log(RTMP_LOGDEBUG, "VerificationNumber: %.2f", VerificationNumber); ++ ++ enc = pbuf; ++ enc = AMF_EncodeString(enc, pend, &av__result); ++ enc = AMF_EncodeNumber(enc, pend, txn); ++ *enc++ = AMF_NULL; ++ enc = AMF_EncodeNumber(enc, pend, exp(atan(sqrt(VerificationNumber))) + 1); ++ av_Response.av_val = pbuf; ++ av_Response.av_len = enc - pbuf; ++ ++ AMF_Decode(&obj, av_Response.av_val, av_Response.av_len, FALSE); ++ AMF_Dump(&obj); ++ SendInvoke(r, &av_Response, FALSE); ++ } ++ else if (AVMATCH(&method, &av_sendStatus)) ++ { ++ if (r->Link.WeebToken.av_len) ++ { ++ AVal av_Authorized = AVC("User.hasAccess"); ++ AVal av_TransferLimit = AVC("User.noPremium.limited"); ++ AVal av_UserLimit = AVC("User.noPremium.tooManyUsers"); ++ AVal av_TimeLeft = AVC("timeLeft"); ++ AVal av_Status, av_ReconnectionTime; ++ ++ AMFObject Status; ++ AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &Status); ++ AMFProp_GetString(AMF_GetProp(&Status, &av_code, -1), &av_Status); ++ RTMP_Log(RTMP_LOGINFO, "%.*s", av_Status.av_len, av_Status.av_val); ++ if (AVMATCH(&av_Status, &av_Authorized)) ++ { ++ RTMP_Log(RTMP_LOGINFO, "Weeb.tv authentication successful"); ++ RTMP_SendCreateStream(r); ++ } ++ else if (AVMATCH(&av_Status, &av_UserLimit)) ++ { ++ RTMP_Log(RTMP_LOGINFO, "No free slots available"); ++ RTMP_Close(r); ++ } ++ else if (AVMATCH(&av_Status, &av_TransferLimit)) ++ { ++ AMFProp_GetString(AMF_GetProp(&Status, &av_TimeLeft, -1), &av_ReconnectionTime); ++ RTMP_Log(RTMP_LOGINFO, "Viewing limit exceeded. try again in %.*s minutes.", av_ReconnectionTime.av_len, av_ReconnectionTime.av_val); ++ RTMP_Close(r); ++ } ++ } ++ } ++ else if (AVMATCH(&method, &av_cps)) ++ { ++ int Status = AMFProp_GetBoolean(AMF_GetProp(&obj, NULL, 3)); ++ if (Status == FALSE) ++ { ++ AVal Message; ++ AMFProp_GetString(AMF_GetProp(&obj, NULL, 4), &Message); ++ RTMP_Log(RTMP_LOGINFO, "Model status is %.*s", Message.av_len, Message.av_val); ++ RTMP_Close(r); ++ } ++ else ++ { ++ AVal Playpath, Server; ++ AMFProp_GetString(AMF_GetProp(&obj, NULL, 5), &Playpath); ++ AMFProp_GetString(AMF_GetProp(&obj, NULL, 6), &Server); ++ if (strncasecmp(&Playpath.av_val[Playpath.av_len - 4], ".mp4", 4) != 0) ++ { ++ char *playpath = calloc(Server.av_len + Playpath.av_len + 25, sizeof (char)); ++ strcat(playpath, "rtmp://"); ++ strncat(playpath, Server.av_val, Server.av_len); ++ strcat(playpath, "/live-origin/"); ++ strncat(playpath, Playpath.av_val, Playpath.av_len); ++ strcat(playpath, ".mp4"); ++ Playpath.av_val = playpath; ++ Playpath.av_len = strlen(playpath); ++ } ++ RTMP_ParsePlaypath(&Playpath, &r->Link.playpath); ++ RTMP_SendCreateStream(r); ++ } ++ } + else + { + +@@ -3232,7 +3688,8 @@ + return TRUE; + } + +- if (prop->p_type == AMF_OBJECT || prop->p_type == AMF_ECMA_ARRAY) ++ if (prop->p_type == AMF_OBJECT || prop->p_type == AMF_ECMA_ARRAY ++ || prop->p_type == AMF_STRICT_ARRAY) + { + if (RTMP_FindFirstMatchingProperty(&prop->p_vu.p_object, name, p)) + return TRUE; +@@ -3258,7 +3715,8 @@ + return TRUE; + } + +- if (prop->p_type == AMF_OBJECT) ++ if (prop->p_type == AMF_OBJECT || prop->p_type == AMF_ECMA_ARRAY ++ || prop->p_type == AMF_STRICT_ARRAY) + { + if (RTMP_FindPrefixProperty(&prop->p_vu.p_object, name, p)) + return TRUE; +@@ -3292,6 +3750,7 @@ + snprintf(str, 255, "%s", + prop->p_vu.p_number != 0. ? "TRUE" : "FALSE"); + break; ++ case AMF_NULL: + case AMF_STRING: + len = snprintf(str, 255, "%.*s", prop->p_vu.p_aval.av_len, + prop->p_vu.p_aval.av_val); +@@ -3307,7 +3766,7 @@ + } + if (str[0] && prop->p_name.av_len) + { +- RTMP_Log(RTMP_LOGINFO, " %-22.*s%s", prop->p_name.av_len, ++ RTMP_Log(RTMP_LOGINFO, " %-24.*s%s", prop->p_name.av_len, + prop->p_name.av_val, str); + } + } +@@ -3389,7 +3848,7 @@ + unsigned int tmp; + if (packet->m_body && packet->m_nBodySize >= 2) + nType = AMF_DecodeInt16(packet->m_body); +- RTMP_Log(RTMP_LOGDEBUG, "%s, received ctrl. type: %d, len: %d", __FUNCTION__, nType, ++ RTMP_Log(RTMP_LOGDEBUG, "%s, received ctrl, type: %d, len: %d", __FUNCTION__, nType, + packet->m_nBodySize); + /*RTMP_LogHex(packet.m_body, packet.m_nBodySize); */ + +@@ -3498,15 +3957,15 @@ + RTMP_Log(RTMP_LOGDEBUG, "%s, SWFVerification ping received: ", __FUNCTION__); + if (packet->m_nBodySize > 2 && packet->m_body[2] > 0x01) + { +- RTMP_Log(RTMP_LOGERROR, +- "%s: SWFVerification Type %d request not supported! Patches welcome...", +- __FUNCTION__, packet->m_body[2]); ++ RTMP_Log(RTMP_LOGERROR, ++ "%s: SWFVerification Type %d request not supported, attempting to use SWFVerification Type 1! Patches welcome...", ++ __FUNCTION__, packet->m_body[2]); + } + #ifdef CRYPTO + /*RTMP_LogHex(packet.m_body, packet.m_nBodySize); */ + + /* respond with HMAC SHA256 of decompressed SWF, key is the 30byte player key, also the last 30 bytes of the server handshake are applied */ +- else if (r->Link.SWFSize) ++ if (r->Link.SWFSize) + { + RTMP_SendCtrl(r, 0x1B, 0, 0); + } +@@ -3811,8 +4270,18 @@ + serversig[4], serversig[5], serversig[6], serversig[7]); + + /* 2nd part of handshake */ +- if (!WriteN(r, serversig, RTMP_SIG_SIZE)) +- return FALSE; ++ if (r->Link.CombineConnectPacket) ++ { ++ char *HandshakeResponse = malloc(RTMP_SIG_SIZE); ++ memcpy(HandshakeResponse, (char *) serversig, RTMP_SIG_SIZE); ++ r->Link.HandshakeResponse.av_val = HandshakeResponse; ++ r->Link.HandshakeResponse.av_len = RTMP_SIG_SIZE; ++ } ++ else ++ { ++ if (!WriteN(r, (char *) serversig, RTMP_SIG_SIZE)) ++ return FALSE; ++ } + + if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE) + return FALSE; +@@ -4263,8 +4732,13 @@ + { + int nBytes; + +- if (!sb->sb_size) +- sb->sb_start = sb->sb_buf; ++ /* Copy unprocessed bytes to the start of buffer to make optimum use of ++ * available buffer */ ++ if (sb->sb_start != sb->sb_buf) ++ { ++ memcpy(sb->sb_buf, sb->sb_start, sb->sb_size); ++ sb->sb_start = sb->sb_buf; ++ } + + while (1) + { +@@ -4278,6 +4752,8 @@ + #endif + { + nBytes = recv(sb->sb_socket, sb->sb_start + sb->sb_size, nBytes, 0); ++ if (!nBytes) ++ RTMP_Log(RTMP_LOGDEBUG, "Socket closed by server, nBytes: %d", nBytes); + } + if (nBytes != -1) + { +@@ -4417,21 +4893,19 @@ + HTTP_Post(RTMP *r, RTMPTCmd cmd, const char *buf, int len) + { + char hbuf[512]; +- int hlen = snprintf(hbuf, sizeof(hbuf), "POST /%s%s/%d HTTP/1.1\r\n" +- "Host: %.*s:%d\r\n" +- "Accept: */*\r\n" +- "User-Agent: Shockwave Flash\r\n" +- "Connection: Keep-Alive\r\n" +- "Cache-Control: no-cache\r\n" +- "Content-type: application/x-fcs\r\n" +- "Content-length: %d\r\n\r\n", RTMPT_cmds[cmd], +- r->m_clientID.av_val ? r->m_clientID.av_val : "", +- r->m_msgCounter, r->Link.hostname.av_len, r->Link.hostname.av_val, +- r->Link.port, len); ++ int hlen = snprintf(hbuf, sizeof (hbuf), "POST /%s%s/%d HTTP/1.1\r\n" ++ "Content-Type: application/x-fcs\r\n" ++ "User-Agent: Shockwave Flash\r\n" ++ "Host: %.*s:%d\r\n" ++ "Content-Length: %d\r\n" ++ "Connection: Keep-Alive\r\n" ++ "Cache-Control: no-cache\r\n\r\n", RTMPT_cmds[cmd], ++ r->m_clientID.av_val ? r->m_clientID.av_val : "", ++ r->m_msgCounter, r->Link.hostname.av_len, r->Link.hostname.av_val, ++ r->Link.port, len); + RTMPSockBuf_Send(&r->m_sb, hbuf, hlen); + hlen = RTMPSockBuf_Send(&r->m_sb, buf, len); + r->m_msgCounter++; +- r->m_unackd++; + return hlen; + } + +@@ -4441,22 +4915,17 @@ + char *ptr; + int hlen; + +-restart: + if (fill) + RTMPSockBuf_Fill(&r->m_sb); +- if (r->m_sb.sb_size < 13) { +- if (fill) +- goto restart; ++ ++ /* Check if socket buffer is empty or HTTP header isn't completely received */ ++ memset(r->m_sb.sb_start + r->m_sb.sb_size, '\0', 1); ++ if ((!r->m_sb.sb_size) || (!strstr(r->m_sb.sb_start, "\r\n\r\n"))) + return -2; +- } ++ + if (strncmp(r->m_sb.sb_start, "HTTP/1.1 200 ", 13)) + return -1; + r->m_sb.sb_start[r->m_sb.sb_size] = '\0'; +- if (!strstr(r->m_sb.sb_start, "\r\n\r\n")) { +- if (fill) +- goto restart; +- return -2; +- } + + ptr = r->m_sb.sb_start + sizeof("HTTP/1.1 200"); + while ((ptr = strstr(ptr, "Content-"))) { +@@ -4464,21 +4933,31 @@ + ptr += 8; + } + if (!ptr) +- return -1; +- hlen = atoi(ptr+16); ++ { ++ ptr = r->m_sb.sb_start + sizeof ("HTTP/1.1 200"); ++ RTMP_Log(RTMP_LOGDEBUG, "No Content-Length header found, assuming continuous stream"); ++ hlen = 2147483648UL; // 2 GB ++ } ++ else ++ hlen = atoi(ptr + 16); + ptr = strstr(ptr+16, "\r\n\r\n"); + if (!ptr) + return -1; + ptr += 4; +- if (ptr + (r->m_clientID.av_val ? 1 : hlen) > r->m_sb.sb_start + r->m_sb.sb_size) +- { +- if (fill) +- goto restart; +- return -2; +- } + r->m_sb.sb_size -= ptr - r->m_sb.sb_start; + r->m_sb.sb_start = ptr; +- r->m_unackd--; ++ ++ /* Stop processing if content length is 0 */ ++ if (!hlen) ++ return -3; ++ ++ /* Refill buffer if no payload is received */ ++ if (hlen && (!r->m_sb.sb_size)) ++ { ++ RTMPSockBuf_Fill(&r->m_sb); ++ ptr = r->m_sb.sb_buf; ++ r->m_sb.sb_start = ptr; ++ } + + if (!r->m_clientID.av_val) + { +@@ -4498,10 +4977,17 @@ + r->m_sb.sb_start++; + r->m_sb.sb_size--; + } ++ ++ /* Following values shouldn't be negative in any case */ ++ if (r->m_resplen < 0) ++ r->m_resplen = 0; ++ if (r->m_sb.sb_size < 0) ++ r->m_sb.sb_size = 0; ++ + return 0; + } + +-#define MAX_IGNORED_FRAMES 50 ++#define MAX_IGNORED_FRAMES 100 + + /* Read from the stream until we get a media packet. + * Returns -3 if Play.Close/Stop, -2 if fatal error, -1 if no more media +@@ -4569,162 +5055,156 @@ + #endif + + if (r->m_read.flags & RTMP_READ_RESUME) +- { +- /* check the header if we get one */ +- if (packet.m_nTimeStamp == 0) +- { +- if (r->m_read.nMetaHeaderSize > 0 +- && packet.m_packetType == RTMP_PACKET_TYPE_INFO) +- { +- AMFObject metaObj; +- int nRes = +- AMF_Decode(&metaObj, packetBody, nPacketLen, FALSE); +- if (nRes >= 0) +- { +- AVal metastring; +- AMFProp_GetString(AMF_GetProp(&metaObj, NULL, 0), +- &metastring); +- +- if (AVMATCH(&metastring, &av_onMetaData)) +- { +- /* compare */ +- if ((r->m_read.nMetaHeaderSize != nPacketLen) || +- (memcmp +- (r->m_read.metaHeader, packetBody, +- r->m_read.nMetaHeaderSize) != 0)) +- { +- ret = RTMP_READ_ERROR; +- } +- } +- AMF_Reset(&metaObj); +- if (ret == RTMP_READ_ERROR) +- break; +- } +- } ++ { ++ RTMP_Log(RTMP_LOGDEBUG2, "Received timestamp: %d, type %d", ++ packet.m_nTimeStamp, packet.m_packetType); ++ if (packet.m_nTimeStamp > 0 && r->m_read.nResumeDriftTS > 0) ++ packet.m_nTimeStamp -= r->m_read.nResumeDriftTS; ++ RTMP_Log(RTMP_LOGDEBUG2, "Adjusted timestamp: %d", packet.m_nTimeStamp); ++ ++ /* check the header if we get one */ ++ if (r->m_read.nMetaHeaderSize > 0 ++ && packet.m_packetType == RTMP_PACKET_TYPE_INFO) ++ { ++ AMFObject metaObj; ++ int nRes = AMF_Decode(&metaObj, packetBody, nPacketLen, FALSE); ++ if (nRes >= 0) ++ { ++ AVal metastring; ++ AMFProp_GetString(AMF_GetProp(&metaObj, NULL, 0), &metastring); + +- /* check first keyframe to make sure we got the right position +- * in the stream! (the first non ignored frame) +- */ +- if (r->m_read.nInitialFrameSize > 0) +- { +- /* video or audio data */ +- if (packet.m_packetType == r->m_read.initialFrameType +- && r->m_read.nInitialFrameSize == nPacketLen) +- { +- /* we don't compare the sizes since the packet can +- * contain several FLV packets, just make sure the +- * first frame is our keyframe (which we are going +- * to rewrite) +- */ +- if (memcmp +- (r->m_read.initialFrame, packetBody, +- r->m_read.nInitialFrameSize) == 0) +- { +- RTMP_Log(RTMP_LOGDEBUG, "Checked keyframe successfully!"); +- r->m_read.flags |= RTMP_READ_GOTKF; +- /* ignore it! (what about audio data after it? it is +- * handled by ignoring all 0ms frames, see below) +- */ +- ret = RTMP_READ_IGNORE; +- break; +- } +- } ++ if (AVMATCH(&metastring, &av_onMetaData)) ++ { ++ /* compare */ ++ if ((r->m_read.nMetaHeaderSize != nPacketLen) || ++ (memcmp(r->m_read.metaHeader, packetBody, r->m_read.nMetaHeaderSize) != 0)) ++ { ++ ret = RTMP_READ_ERROR; ++ } ++ } ++ AMF_Reset(&metaObj); ++ if (ret == RTMP_READ_ERROR) ++ break; ++ } ++ } + +- /* hande FLV streams, even though the server resends the +- * keyframe as an extra video packet it is also included +- * in the first FLV stream chunk and we have to compare +- * it and filter it out !! +- */ +- if (packet.m_packetType == RTMP_PACKET_TYPE_FLASH_VIDEO) +- { +- /* basically we have to find the keyframe with the +- * correct TS being nResumeTS +- */ +- unsigned int pos = 0; +- uint32_t ts = 0; +- +- while (pos + 11 < nPacketLen) +- { +- /* size without header (11) and prevTagSize (4) */ +- uint32_t dataSize = +- AMF_DecodeInt24(packetBody + pos + 1); +- ts = AMF_DecodeInt24(packetBody + pos + 4); +- ts |= (packetBody[pos + 7] << 24); ++ /* check first keyframe to make sure we got the right position ++ * in the stream! (the first non ignored frame) ++ */ ++ RTMP_Log(RTMP_LOGDEBUG2, "Required packet length: %d, Packet length: %d", ++ r->m_read.nInitialFrameSize, nPacketLen); ++ if (r->m_read.nInitialFrameSize > 0) ++ { ++ /* video or audio data */ ++ if (packet.m_packetType == r->m_read.initialFrameType ++ && r->m_read.nInitialFrameSize == nPacketLen) ++ { ++ /* we don't compare the sizes since the packet can ++ * contain several FLV packets, just make sure the ++ * first frame is our keyframe (which we are going ++ * to rewrite) ++ */ ++ RTMP_Log(RTMP_LOGDEBUG2, "Comparing keyframe data"); ++ if (memcmp(r->m_read.initialFrame, packetBody, ++ r->m_read.nInitialFrameSize) == 0) ++ { ++ RTMP_Log(RTMP_LOGDEBUG, "Checked keyframe successfully!"); ++ r->m_read.flags |= RTMP_READ_GOTKF; ++ r->m_read.nResumeDriftTS = packet.m_nTimeStamp; ++ /* ignore it! (what about audio data after it? it is ++ * handled by ignoring all 0ms frames, see below) ++ */ ++ ret = RTMP_READ_IGNORE; ++ break; ++ } ++ } ++ ++ /* hande FLV streams, even though the server resends the ++ * keyframe as an extra video packet it is also included ++ * in the first FLV stream chunk and we have to compare ++ * it and filter it out !! ++ */ ++ if (packet.m_packetType == RTMP_PACKET_TYPE_FLASH_VIDEO) ++ { ++ /* basically we have to find the keyframe with the ++ * correct TS being nResumeTS ++ */ ++ unsigned int pos = 0; ++ uint32_t ts = 0; ++ ++ while (pos + 11 < nPacketLen) ++ { ++ /* size without header (11) and prevTagSize (4) */ ++ uint32_t dataSize = AMF_DecodeInt24(packetBody + pos + 1); ++ ts = AMF_DecodeInt24(packetBody + pos + 4); ++ ts |= (packetBody[pos + 7] << 24); + + #ifdef _DEBUG +- RTMP_Log(RTMP_LOGDEBUG, +- "keyframe search: FLV Packet: type %02X, dataSize: %d, timeStamp: %d ms", +- packetBody[pos], dataSize, ts); +-#endif +- /* ok, is it a keyframe?: +- * well doesn't work for audio! +- */ +- if (packetBody[pos /*6928, test 0 */ ] == +- r->m_read.initialFrameType +- /* && (packetBody[11]&0xf0) == 0x10 */ ) +- { +- if (ts == r->m_read.nResumeTS) +- { +- RTMP_Log(RTMP_LOGDEBUG, +- "Found keyframe with resume-keyframe timestamp!"); +- if (r->m_read.nInitialFrameSize != dataSize +- || memcmp(r->m_read.initialFrame, +- packetBody + pos + 11, +- r->m_read. +- nInitialFrameSize) != 0) +- { +- RTMP_Log(RTMP_LOGERROR, +- "FLV Stream: Keyframe doesn't match!"); +- ret = RTMP_READ_ERROR; +- break; +- } +- r->m_read.flags |= RTMP_READ_GOTFLVK; +- +- /* skip this packet? +- * check whether skippable: +- */ +- if (pos + 11 + dataSize + 4 > nPacketLen) +- { +- RTMP_Log(RTMP_LOGWARNING, +- "Non skipable packet since it doesn't end with chunk, stream corrupt!"); +- ret = RTMP_READ_ERROR; +- break; +- } +- packetBody += (pos + 11 + dataSize + 4); +- nPacketLen -= (pos + 11 + dataSize + 4); +- +- goto stopKeyframeSearch; +- +- } +- else if (r->m_read.nResumeTS < ts) +- { +- /* the timestamp ts will only increase with +- * further packets, wait for seek +- */ +- goto stopKeyframeSearch; +- } +- } +- pos += (11 + dataSize + 4); +- } +- if (ts < r->m_read.nResumeTS) +- { +- RTMP_Log(RTMP_LOGERROR, +- "First packet does not contain keyframe, all " +- "timestamps are smaller than the keyframe " +- "timestamp; probably the resume seek failed?"); +- } +- stopKeyframeSearch: +- ; +- if (!(r->m_read.flags & RTMP_READ_GOTFLVK)) +- { +- RTMP_Log(RTMP_LOGERROR, +- "Couldn't find the seeked keyframe in this chunk!"); +- ret = RTMP_READ_IGNORE; +- break; +- } +- } +- } +- } ++ RTMP_Log(RTMP_LOGDEBUG, ++ "keyframe search: FLV Packet: type %02X, dataSize: %d, timeStamp: %d ms", ++ packetBody[pos], dataSize, ts); ++#endif ++ /* ok, is it a keyframe?: ++ * well doesn't work for audio! ++ */ ++ if (packetBody[pos /*6928, test 0 */ ] == r->m_read.initialFrameType ++ /* && (packetBody[11]&0xf0) == 0x10 */) ++ { ++ if (ts == r->m_read.nResumeTS) ++ { ++ RTMP_Log(RTMP_LOGDEBUG, "Found keyframe with resume-keyframe timestamp!"); ++ if (r->m_read.nInitialFrameSize != dataSize || ++ memcmp(r->m_read.initialFrame, packetBody + pos + 11, ++ r->m_read.nInitialFrameSize) != 0) ++ { ++ RTMP_Log(RTMP_LOGERROR, "FLV Stream: Keyframe doesn't match!"); ++ ret = RTMP_READ_ERROR; ++ break; ++ } ++ r->m_read.flags |= RTMP_READ_GOTFLVK; ++ ++ /* skip this packet? ++ * check whether skippable: ++ */ ++ if (pos + 11 + dataSize + 4 > nPacketLen) ++ { ++ RTMP_Log(RTMP_LOGWARNING, "Non skipable packet since it doesn't " ++ "end with chunk, stream corrupt!"); ++ ret = RTMP_READ_ERROR; ++ break; ++ } ++ packetBody += (pos + 11 + dataSize + 4); ++ nPacketLen -= (pos + 11 + dataSize + 4); ++ ++ goto stopKeyframeSearch; ++ ++ } ++ else if (r->m_read.nResumeTS < ts) ++ { ++ /* the timestamp ts will only increase with ++ * further packets, wait for seek ++ */ ++ goto stopKeyframeSearch; ++ } ++ } ++ pos += (11 + dataSize + 4); ++ } ++ if (ts < r->m_read.nResumeTS) ++ { ++ RTMP_Log(RTMP_LOGERROR, ++ "First packet does not contain keyframe, all " ++ "timestamps are smaller than the keyframe " ++ "timestamp; probably the resume seek failed?"); ++ } ++ stopKeyframeSearch: ++ if (!(r->m_read.flags & RTMP_READ_GOTFLVK)) ++ { ++ RTMP_Log(RTMP_LOGERROR, "Couldn't find the seeked keyframe in this chunk!"); ++ ret = RTMP_READ_IGNORE; ++ break; ++ } ++ } ++ } + + if (packet.m_nTimeStamp > 0 + && (r->m_read.flags & (RTMP_READ_GOTKF|RTMP_READ_GOTFLVK))) +@@ -4984,7 +5464,7 @@ + 0x00, 0x00, 0x00, 0x00 + }; + +-#define HEADERBUF (128*1024) ++#define HEADERBUF (1024*1024) + int + RTMP_Read(RTMP *r, char *buf, int size) + { +@@ -5187,3 +5667,284 @@ + } + return size+s2; + } ++ ++AVal ++AVcopy(AVal src) ++{ ++ AVal dst; ++ if (src.av_len) ++ { ++ dst.av_val = malloc(src.av_len + 1); ++ memcpy(dst.av_val, src.av_val, src.av_len); ++ dst.av_val[src.av_len] = '\0'; ++ dst.av_len = src.av_len; ++ } ++ else ++ { ++ dst.av_val = NULL; ++ dst.av_len = 0; ++ } ++ return dst; ++} ++ ++static int ++ConnectSocket(RTMP *r) ++{ ++ int on = 1; ++ struct sockaddr_in service; ++ if (!r->Link.hostname.av_len) ++ return FALSE; ++ ++ memset(&service, 0, sizeof (struct sockaddr_in)); ++ service.sin_family = AF_INET; ++ ++ if (r->Link.socksport) ++ { ++ /* Connect via SOCKS */ ++ if (!add_addr_info(&service, &r->Link.sockshost, r->Link.socksport)) ++ return FALSE; ++ } ++ else ++ { ++ /* Connect directly */ ++ if (!add_addr_info(&service, &r->Link.hostname, r->Link.port)) ++ return FALSE; ++ } ++ ++ r->m_sb.sb_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); ++ if (r->m_sb.sb_socket != -1) ++ { ++ if (connect(r->m_sb.sb_socket, (struct sockaddr *) &service, sizeof (struct sockaddr)) < 0) ++ { ++ int err = GetSockError(); ++ RTMP_Log(RTMP_LOGERROR, "%s, failed to connect socket. %d (%s)", ++ __FUNCTION__, err, strerror(err)); ++ RTMP_Close(r); ++ return FALSE; ++ } ++ ++ if (r->Link.socksport) ++ { ++ RTMP_Log(RTMP_LOGDEBUG, "%s ... SOCKS negotiation", __FUNCTION__); ++ if (!SocksNegotiate(r)) ++ { ++ RTMP_Log(RTMP_LOGERROR, "%s, SOCKS negotiation failed.", __FUNCTION__); ++ RTMP_Close(r); ++ return FALSE; ++ } ++ } ++ } ++ else ++ { ++ RTMP_Log(RTMP_LOGERROR, "%s, failed to create socket. Error: %d", ++ __FUNCTION__, GetSockError()); ++ return FALSE; ++ } ++ ++ /* set timeout */ ++ SET_RCVTIMEO(tv, r->Link.timeout); ++ if (setsockopt(r->m_sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO, (char *) &tv, sizeof (tv))) ++ { ++ RTMP_Log(RTMP_LOGERROR, "%s, Setting socket timeout to %d failed!", ++ __FUNCTION__, r->Link.timeout); ++ } ++ ++ setsockopt(r->m_sb.sb_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &on, sizeof (on)); ++ if (r->Link.protocol & RTMP_FEATURE_HTTP) ++ setsockopt(r->m_sb.sb_socket, SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof (on)); ++ ++ return TRUE; ++} ++ ++static int ++SendCommand(RTMP *r, char *method, int queue) ++{ ++ char pbuf[256], *pend = pbuf + sizeof (pbuf), *enc; ++ AVal av_command, methodName; ++ ++ enc = pbuf; ++ methodName.av_val = method; ++ methodName.av_len = strlen(method); ++ enc = AMF_EncodeString(enc, pend, &methodName); ++ enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); ++ *enc++ = AMF_NULL; ++ av_command.av_val = pbuf; ++ av_command.av_len = enc - pbuf; ++ ++ return SendInvoke(r, &av_command, queue); ++} ++ ++static int ++SendGetStreamLength(RTMP *r) ++{ ++ char pbuf[256], *pend = pbuf + sizeof (pbuf), *enc; ++ AVal av_Command; ++ SAVC(getStreamLength); ++ ++ enc = pbuf; ++ enc = AMF_EncodeString(enc, pend, &av_getStreamLength); ++ enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); ++ *enc++ = AMF_NULL; ++ enc = AMF_EncodeString(enc, pend, &r->Link.playpath); ++ av_Command.av_val = pbuf; ++ av_Command.av_len = enc - pbuf; ++ ++ return SendInvoke(r, &av_Command, TRUE); ++} ++ ++static int ++SendInvoke(RTMP *r, AVal *command, int queue) ++{ ++ RTMPPacket packet; ++ char pbuf[512], *enc; ++ ++ packet.m_nChannel = 0x03; /* control channel (invoke) */ ++ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; ++ packet.m_packetType = RTMP_PACKET_TYPE_INVOKE; ++ packet.m_nTimeStamp = 0; ++ packet.m_nInfoField2 = 0; ++ packet.m_hasAbsTimestamp = 0; ++ packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; ++ ++ enc = packet.m_body; ++ if (command->av_len) ++ { ++ memcpy(enc, command->av_val, command->av_len); ++ enc += command->av_len; ++ } ++ else ++ return FALSE; ++ packet.m_nBodySize = enc - packet.m_body; ++ ++ return RTMP_SendPacket(r, &packet, queue); ++} ++ ++AVal ++StripParams(AVal *src) ++{ ++ AVal str; ++ if (src->av_val) ++ { ++ str.av_val = calloc(src->av_len + 1, sizeof (char)); ++ strncpy(str.av_val, src->av_val, src->av_len); ++ str.av_len = src->av_len; ++ char *start = str.av_val; ++ char *end = start + str.av_len; ++ char *ptr = start; ++ ++ while (ptr < end) ++ { ++ if (*ptr == '?') ++ { ++ str.av_len = ptr - start; ++ break; ++ } ++ ptr++; ++ } ++ memset(start + str.av_len, 0, 1); ++ ++ char *dynamic = strstr(start, "[[DYNAMIC]]"); ++ if (dynamic) ++ { ++ dynamic -= 1; ++ memset(dynamic, 0, 1); ++ str.av_len = dynamic - start; ++ end = start + str.av_len; ++ } ++ ++ char *import = strstr(start, "[[IMPORT]]"); ++ if (import) ++ { ++ str.av_val = import + 11; ++ strcpy(start, "http://"); ++ str.av_val = strcat(start, str.av_val); ++ str.av_len = strlen(str.av_val); ++ } ++ return str; ++ } ++ str = *src; ++ return str; ++} ++ ++char * ++strreplace(char *srcstr, int srclen, char *orig, char *repl, int didAlloc) ++{ ++ char *ptr = NULL, *sptr = srcstr; ++ int origlen = strlen(orig); ++ int repllen = strlen(repl); ++ if (!srclen) ++ srclen = strlen(srcstr); ++ char *srcend = srcstr + srclen; ++ int dstbuffer = srclen / origlen * repllen; ++ if (dstbuffer < srclen) ++ dstbuffer = srclen; ++ char *dststr = calloc(dstbuffer + 1, sizeof (char)); ++ char *dptr = dststr; ++ ++ if ((ptr = strstr(srcstr, orig))) ++ { ++ while (ptr < srcend && (ptr = strstr(sptr, orig))) ++ { ++ int len = ptr - sptr; ++ memcpy(dptr, sptr, len); ++ sptr += len + origlen; ++ dptr += len; ++ memcpy(dptr, repl, repllen); ++ dptr += repllen; ++ } ++ memcpy(dptr, sptr, srcend - sptr); ++ if (didAlloc) ++ free(srcstr); ++ return dststr; ++ } ++ ++ memcpy(dststr, srcstr, srclen); ++ if (didAlloc) ++ free(srcstr); ++ return dststr; ++} ++ ++int ++strsplit(char *src, int srclen, char delim, char ***params) ++{ ++ char *sptr, *srcbeg, *srcend, *dstr; ++ int count = 1, i = 0, len = 0; ++ ++ if (src == NULL) ++ return 0; ++ if (!srclen) ++ srclen = strlen(src); ++ srcbeg = src; ++ srcend = srcbeg + srclen; ++ sptr = srcbeg; ++ ++ /* count the delimiters */ ++ while (sptr < srcend) ++ { ++ if (*sptr++ == delim) ++ count++; ++ } ++ sptr = srcbeg; ++ *params = malloc(count * sizeof (size_t)); ++ char **param = *params; ++ ++ for (i = 0; i < (count - 1); i++) ++ { ++ dstr = strchr(sptr, delim); ++ len = dstr - sptr; ++ param[i] = malloc((len + 1) * sizeof (char)); ++ memcpy(param[i], sptr, len); ++ *(param[i] + len) = '\0'; ++ sptr += len + 1; ++ } ++ ++ /* copy the last string */ ++ if (sptr <= srcend) ++ { ++ len = srclen - (sptr - srcbeg); ++ param[i] = malloc((len + 1) * sizeof (char)); ++ memcpy(param[i], sptr, len); ++ *(param[i] + len) = '\0'; ++ } ++ return count; ++} +diff -uNr librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/librtmp/rtmp.h librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/librtmp/rtmp.h +--- librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/librtmp/rtmp.h 2014-03-02 19:20:23.000000000 +0100 ++++ librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/librtmp/rtmp.h 2014-05-04 17:55:17.517338389 +0200 +@@ -150,12 +150,14 @@ + AVal playpath; /* passed in explicitly */ + AVal tcUrl; + AVal swfUrl; ++ AVal swfHash; + AVal pageUrl; + AVal app; + AVal auth; + AVal flashVer; + AVal subscribepath; + AVal usherToken; ++ AVal WeebToken; + AVal token; + AVal pubUser; + AVal pubPasswd; +@@ -174,9 +176,15 @@ + int lFlags; + + int swfAge; ++ int swfSize; + + int protocol; ++ int ConnectPacket; ++ int CombineConnectPacket; ++ int redirected; + int timeout; /* connection timeout in seconds */ ++ AVal Extras; ++ AVal HandshakeResponse; + + #define RTMP_PUB_NAME 0x0001 /* send login to server */ + #define RTMP_PUB_RESP 0x0002 /* send salted password hash */ +@@ -224,6 +232,7 @@ + /* if bResume == TRUE */ + uint8_t initialFrameType; + uint32_t nResumeTS; ++ uint32_t nResumeDriftTS; + char *metaHeader; + char *initialFrame; + uint32_t nMetaHeaderSize; +@@ -310,6 +319,7 @@ + AVal *flashVer, + AVal *subscribepath, + AVal *usherToken, ++ AVal *WeebToken, + int dStart, + int dStop, int bLiveStream, long int timeout); + +@@ -375,6 +385,11 @@ + int RTMP_HashSWF(const char *url, unsigned int *size, unsigned char *hash, + int age); + ++ AVal AVcopy(AVal src); ++ AVal StripParams(AVal *src); ++ char *strreplace(char *srcstr, int srclen, char *orig, char *repl, int didAlloc); ++ int strsplit(char *src, int srclen, char delim, char ***params); ++ + #ifdef __cplusplus + }; + #endif +diff -uNr librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/librtmp/rtmp_sys.h librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/librtmp/rtmp_sys.h +--- librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/librtmp/rtmp_sys.h 2014-03-02 19:20:23.000000000 +0100 ++++ librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/librtmp/rtmp_sys.h 2014-05-04 17:55:17.517338389 +0200 +@@ -65,6 +65,7 @@ + #include + #include + #include ++#include + #if POLARSSL_VERSION_NUMBER < 0x01010000 + #define havege_random havege_rand + #endif +@@ -105,6 +106,7 @@ + #define TLS_write(s,b,l) ssl_write(s,(unsigned char *)b,l) + #define TLS_shutdown(s) ssl_close_notify(s) + #define TLS_close(s) ssl_free(s); free(s) ++#define md5_hash(i, ilen, o) md5(i, ilen, o) + + #elif defined(USE_GNUTLS) + #include +@@ -122,6 +124,8 @@ + #define TLS_write(s,b,l) gnutls_record_send(s,b,l) + #define TLS_shutdown(s) gnutls_bye(s, GNUTLS_SHUT_RDWR) + #define TLS_close(s) gnutls_deinit(s) ++#define md5_hash(i, ilen, o) gnutls_digest_algorithm_t algorithm = GNUTLS_DIG_MD5;\ ++ gnutls_hash_fast(algorithm, i, ilen, o); + + #else /* USE_OPENSSL */ + #define TLS_CTX SSL_CTX * +@@ -134,6 +138,7 @@ + #define TLS_write(s,b,l) SSL_write(s,b,l) + #define TLS_shutdown(s) SSL_shutdown(s) + #define TLS_close(s) SSL_free(s) ++#define md5_hash(i, ilen, o) MD5(i, ilen, o) + + #endif + #endif +diff -uNr librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/Makefile librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/Makefile +--- librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/Makefile 2014-03-02 19:20:23.000000000 +0100 ++++ librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/Makefile 2014-05-04 17:55:17.513338440 +0200 +@@ -32,7 +32,7 @@ + SBINDIR=$(DESTDIR)$(sbindir) + MANDIR=$(DESTDIR)$(mandir) + +-LIBS_posix= ++LIBS_posix=-lm + LIBS_darwin= + LIBS_mingw=-lws2_32 -lwinmm -lgdi32 + LIB_RTMP=-Llibrtmp -lrtmp +diff -uNr librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/rtmpdump.c librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/rtmpdump.c +--- librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/rtmpdump.c 2014-03-02 19:20:23.000000000 +0100 ++++ librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/rtmpdump.c 2014-05-04 17:55:17.517338389 +0200 +@@ -283,6 +283,7 @@ + uint8_t dataType; + int bAudioOnly; + off_t size; ++ char *syncbuf, *p; + + fseek(file, 0, SEEK_END); + size = ftello(file); +@@ -293,8 +294,8 @@ + + bAudioOnly = (dataType & 0x4) && !(dataType & 0x1); + +- RTMP_Log(RTMP_LOGDEBUG, "bAudioOnly: %d, size: %llu", bAudioOnly, +- (unsigned long long) size); ++ RTMP_Log(RTMP_LOGDEBUG, "bAudioOnly: %d, size: %lu", bAudioOnly, ++ (unsigned long) size); + + // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams) + +@@ -326,6 +327,51 @@ + prevTagSize = AMF_DecodeInt32(buffer); + //RTMP_Log(RTMP_LOGDEBUG, "Last packet: prevTagSize: %d", prevTagSize); + ++ if (prevTagSize <= 0 || prevTagSize > size - 4 - 13) ++ { ++ /* Last packet was not fully received - try to sync to last tag */ ++ prevTagSize = 0; ++ tsize = size > 0x100000 ? 0x100000 : size; /* 1MB should be enough for 3500K bitrates */ ++ if (tsize > 13 + 15) ++ { ++ tsize -= 13; // do not read header ++ syncbuf = (char *) malloc(tsize); ++ if (syncbuf) ++ { ++ fseeko(file, size - tsize, SEEK_SET); ++ if (fread(syncbuf, 1, tsize, file) == tsize) ++ { ++ p = syncbuf + tsize; ++ while (p >= syncbuf + 15) ++ { ++ /* Check for StreamID */ ++ if (AMF_DecodeInt24(p - 7) == 0) ++ { ++ /* Check for Audio/Video/Script */ ++ dataType = p[-15] & 0x1F; ++ if (dataType == 8 || dataType == 9 || dataType == 18) ++ { ++ prevTagSize = AMF_DecodeInt24(p - 14); ++ if ((prevTagSize < tsize) && (p + prevTagSize + 11 <= syncbuf + tsize - 4) ++ && (AMF_DecodeInt32(p - 4 + prevTagSize) == prevTagSize + 11)) ++ { ++ prevTagSize = syncbuf + tsize - p + 15; ++ RTMP_Log(RTMP_LOGDEBUG, "Sync success - found last tag at 0x%x", (uint32_t) (size - prevTagSize)); ++ prevTagSize -= 4; ++ tsize = 0; ++ break; ++ } ++ else ++ prevTagSize = 0; ++ } ++ } ++ --p; ++ } ++ } ++ free(syncbuf); ++ } ++ } ++ } + if (prevTagSize == 0) + { + RTMP_Log(RTMP_LOGERROR, "Couldn't find keyframe to resume from!"); +@@ -705,6 +751,8 @@ + RTMP_LogPrintf + ("--jtv|-j JSON Authentication token for Justin.tv legacy servers\n"); + RTMP_LogPrintf ++ ("--weeb|-J string Authentication token for weeb.tv servers\n"); ++ RTMP_LogPrintf + ("--hashes|-# Display progress with hashes, not with the byte counter\n"); + RTMP_LogPrintf + ("--buffer|-b Buffer time in milliseconds (default: %u)\n", +@@ -751,7 +799,8 @@ + AVal hostname = { 0, 0 }; + AVal playpath = { 0, 0 }; + AVal subscribepath = { 0, 0 }; +- AVal usherToken = { 0, 0 }; //Justin.tv auth token ++ AVal usherToken = { 0, 0 }; // Justin.tv auth token ++ AVal WeebToken = { 0, 0 }; // Weeb.tv auth token + int port = -1; + int protocol = RTMP_PROTOCOL_UNDEFINED; + int retries = 0; +@@ -858,12 +907,13 @@ + {"quiet", 0, NULL, 'q'}, + {"verbose", 0, NULL, 'V'}, + {"jtv", 1, NULL, 'j'}, ++ {"weeb", 1, NULL, 'J'}, + {0, 0, 0, 0} + }; + + while ((opt = + getopt_long(argc, argv, +- "hVveqzRr:s:t:i:p:a:b:f:o:u:C:n:c:l:y:Ym:k:d:A:B:T:w:x:W:X:S:#j:", ++ "hVveqzRr:s:t:i:p:a:b:f:o:u:C:n:c:l:y:Ym:k:d:A:B:T:w:x:W:X:S:#j:J:", + longopts, NULL)) != -1) + { + switch (opt) +@@ -995,7 +1045,7 @@ + port = parsedPort; + if (playpath.av_len == 0 && parsedPlaypath.av_len) + { +- playpath = parsedPlaypath; ++ playpath = AVcopy(parsedPlaypath); + } + if (protocol == RTMP_PROTOCOL_UNDEFINED) + protocol = parsedProtocol; +@@ -1079,6 +1129,9 @@ + case 'j': + STR2AVAL(usherToken, optarg); + break; ++ case 'J': ++ STR2AVAL(WeebToken, optarg); ++ break; + default: + RTMP_LogPrintf("unknown option: %c\n", opt); + usage(argv[0]); +@@ -1170,14 +1223,14 @@ + + if (tcUrl.av_len == 0) + { +- tcUrl.av_len = strlen(RTMPProtocolStringsLower[protocol]) + +- hostname.av_len + app.av_len + sizeof("://:65535/"); ++ tcUrl.av_len = strlen(RTMPProtocolStringsLower[protocol]) + ++ hostname.av_len + app.av_len + sizeof ("://:65535/"); + tcUrl.av_val = (char *) malloc(tcUrl.av_len); +- if (!tcUrl.av_val) +- return RD_FAILED; ++ if (!tcUrl.av_val) ++ return RD_FAILED; + tcUrl.av_len = snprintf(tcUrl.av_val, tcUrl.av_len, "%s://%.*s:%d/%.*s", +- RTMPProtocolStringsLower[protocol], hostname.av_len, +- hostname.av_val, port, app.av_len, app.av_val); ++ RTMPProtocolStringsLower[protocol], hostname.av_len, ++ hostname.av_val, port, app.av_len, app.av_val); + } + + int first = 1; +@@ -1197,8 +1250,8 @@ + if (!fullUrl.av_len) + { + RTMP_SetupStream(&rtmp, protocol, &hostname, port, &sockshost, &playpath, +- &tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize, +- &flashVer, &subscribepath, &usherToken, dSeek, dStopOffset, bLiveStream, timeout); ++ &tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize, ++ &flashVer, &subscribepath, &usherToken, &WeebToken, dSeek, dStopOffset, bLiveStream, timeout); + } + else + { +diff -uNr librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/rtmpgw.c librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/rtmpgw.c +--- librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/rtmpgw.c 2014-03-02 19:20:23.000000000 +0100 ++++ librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/rtmpgw.c 2014-05-04 17:55:17.525338289 +0200 +@@ -96,7 +96,8 @@ + AVal flashVer; + AVal token; + AVal subscribepath; +- AVal usherToken; //Justin.tv auth token ++ AVal usherToken; // Justin.tv auth token ++ AVal WeebToken; // Weeb.tv auth token + AVal sockshost; + AMFObject extras; + int edepth; +@@ -556,8 +557,8 @@ + if (!req.fullUrl.av_len) + { + RTMP_SetupStream(&rtmp, req.protocol, &req.hostname, req.rtmpport, &req.sockshost, +- &req.playpath, &req.tcUrl, &req.swfUrl, &req.pageUrl, &req.app, &req.auth, &req.swfHash, req.swfSize, &req.flashVer, &req.subscribepath, &req.usherToken, dSeek, req.dStopOffset, +- req.bLiveStream, req.timeout); ++ &req.playpath, &req.tcUrl, &req.swfUrl, &req.pageUrl, &req.app, &req.auth, &req.swfHash, req.swfSize, &req.flashVer, &req.subscribepath, &req.usherToken, &req.WeebToken, dSeek, req.dStopOffset, ++ req.bLiveStream, req.timeout); + } + else + { +@@ -972,6 +973,9 @@ + case 'j': + STR2AVAL(req->usherToken, arg); + break; ++ case 'J': ++ STR2AVAL(req->WeebToken, arg); ++ break; + default: + RTMP_LogPrintf("unknown option: %c, arg: %s\n", opt, arg); + return FALSE; +@@ -1044,6 +1048,7 @@ + {"quiet", 0, NULL, 'q'}, + {"verbose", 0, NULL, 'V'}, + {"jtv", 1, NULL, 'j'}, ++ {"weeb", 1, NULL, 'J'}, + {0, 0, 0, 0} + }; + +@@ -1056,7 +1061,7 @@ + + while ((opt = + getopt_long(argc, argv, +- "hvqVzr:s:t:i:p:a:f:u:n:c:l:y:m:d:D:A:B:T:g:w:x:W:X:S:j:", longopts, ++ "hvqVzr:s:t:i:p:a:f:u:n:c:l:y:m:d:D:A:B:T:g:w:x:W:X:S:j:J:", longopts, + NULL)) != -1) + { + switch (opt) +@@ -1121,6 +1126,8 @@ + RTMP_LogPrintf + ("--jtv|-j JSON Authentication token for Justin.tv legacy servers\n"); + RTMP_LogPrintf ++ ("--weeb|-J string Authentication token for weeb.tv servers\n"); ++ RTMP_LogPrintf + ("--buffer|-b Buffer time in milliseconds (default: %u)\n\n", + defaultRTMPRequest.bufferTime); + +diff -uNr librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/rtmpsrv.c librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/rtmpsrv.c +--- librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/rtmpsrv.c 2014-03-02 19:20:23.000000000 +0100 ++++ librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/rtmpsrv.c 2014-05-04 17:55:17.525338289 +0200 +@@ -25,9 +25,13 @@ + */ + + #include ++#ifdef __MINGW_H ++#include ++#endif + #include + #include + #include ++#include + + #include + #include +@@ -94,12 +98,19 @@ + STREAMING_SERVER *rtmpServer = 0; // server structure pointer + void *sslCtx = NULL; + ++int file_exists(const char *fname); + STREAMING_SERVER *startStreaming(const char *address, int port); + void stopStreaming(STREAMING_SERVER * server); + void AVreplace(AVal *src, const AVal *orig, const AVal *repl); + + static const AVal av_dquote = AVC("\""); + static const AVal av_escdquote = AVC("\\\""); ++#ifdef WIN32 ++static const AVal av_caret = AVC("^"); ++static const AVal av_esccaret = AVC("^^"); ++static const AVal av_pipe = AVC("|"); ++static const AVal av_escpipe = AVC("^|"); ++#endif + + typedef struct + { +@@ -168,6 +179,12 @@ + SAVC(code); + SAVC(description); + SAVC(secureToken); ++SAVC(_checkbw); ++SAVC(_onbwdone); ++SAVC(checkBandwidth); ++SAVC(onBWDone); ++SAVC(FCSubscribe); ++SAVC(onFCSubscribe); + + static int + SendConnectResult(RTMP *r, double txn) +@@ -191,7 +208,7 @@ + enc = AMF_EncodeNumber(enc, pend, txn); + *enc++ = AMF_OBJECT; + +- STR2AVAL(av, "FMS/3,5,1,525"); ++ STR2AVAL(av, "FMS/3,5,7,7009"); + enc = AMF_EncodeNamedString(enc, pend, &av_fmsVer, &av); + enc = AMF_EncodeNamedNumber(enc, pend, &av_capabilities, 31.0); + enc = AMF_EncodeNamedNumber(enc, pend, &av_mode, 1.0); +@@ -213,7 +230,7 @@ + enc = AMF_EncodeNamedString(enc, pend, &av_secureToken, &av); + #endif + STR2AVAL(p.p_name, "version"); +- STR2AVAL(p.p_vu.p_aval, "3,5,1,525"); ++ STR2AVAL(p.p_vu.p_aval, "3,5,7,7009"); + p.p_type = AMF_STRING; + obj.o_num = 1; + obj.o_props = &p; +@@ -234,7 +251,7 @@ + SendResultNumber(RTMP *r, double txn, double ID) + { + RTMPPacket packet; +- char pbuf[256], *pend = pbuf+sizeof(pbuf); ++ char pbuf[1024], *pend = pbuf + sizeof (pbuf); + + packet.m_nChannel = 0x03; // control channel (invoke) + packet.m_headerType = 1; /* RTMP_PACKET_SIZE_MEDIUM; */ +@@ -264,12 +281,13 @@ + SAVC(details); + SAVC(clientid); + static const AVal av_NetStream_Authenticate_UsherToken = AVC("NetStream.Authenticate.UsherToken"); ++static const AVal av_FCSubscribe_message = AVC("FCSubscribe to stream"); + + static int + SendPlayStart(RTMP *r) + { + RTMPPacket packet; +- char pbuf[512], *pend = pbuf+sizeof(pbuf); ++ char pbuf[1024], *pend = pbuf + sizeof (pbuf); + + packet.m_nChannel = 0x03; // control channel (invoke) + packet.m_headerType = 1; /* RTMP_PACKET_SIZE_MEDIUM; */ +@@ -301,7 +319,7 @@ + SendPlayStop(RTMP *r) + { + RTMPPacket packet; +- char pbuf[512], *pend = pbuf+sizeof(pbuf); ++ char pbuf[1024], *pend = pbuf + sizeof (pbuf); + + packet.m_nChannel = 0x03; // control channel (invoke) + packet.m_headerType = 1; /* RTMP_PACKET_SIZE_MEDIUM; */ +@@ -329,6 +347,83 @@ + return RTMP_SendPacket(r, &packet, FALSE); + } + ++static int ++SendCheckBWResponse(RTMP *r, int oldMethodType, int onBWDoneInit) ++{ ++ RTMPPacket packet; ++ char pbuf[1024], *pend = pbuf + sizeof (pbuf); ++ char *enc; ++ ++ packet.m_nChannel = 0x03; /* control channel (invoke) */ ++ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; ++ packet.m_packetType = RTMP_PACKET_TYPE_INVOKE; ++ packet.m_nTimeStamp = 0; ++ packet.m_nInfoField2 = 0; ++ packet.m_hasAbsTimestamp = 0; ++ packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; ++ ++ enc = packet.m_body; ++ if (oldMethodType) ++ { ++ enc = AMF_EncodeString(enc, pend, &av__onbwdone); ++ enc = AMF_EncodeNumber(enc, pend, 0); ++ *enc++ = AMF_NULL; ++ enc = AMF_EncodeNumber(enc, pend, 10240); ++ enc = AMF_EncodeNumber(enc, pend, 0); ++ } ++ else ++ { ++ enc = AMF_EncodeString(enc, pend, &av_onBWDone); ++ enc = AMF_EncodeNumber(enc, pend, 0); ++ *enc++ = AMF_NULL; ++ if (!onBWDoneInit) ++ { ++ enc = AMF_EncodeNumber(enc, pend, 10240); ++ enc = AMF_EncodeNumber(enc, pend, 0); ++ enc = AMF_EncodeNumber(enc, pend, 0); ++ enc = AMF_EncodeNumber(enc, pend, 20); ++ } ++ } ++ ++ packet.m_nBodySize = enc - packet.m_body; ++ ++ return RTMP_SendPacket(r, &packet, FALSE); ++} ++ ++static int ++SendOnFCSubscribe(RTMP *r) ++{ ++ RTMPPacket packet; ++ char pbuf[1024], *pend = pbuf + sizeof (pbuf); ++ char *enc; ++ ++ packet.m_nChannel = 0x03; /* control channel (invoke) */ ++ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; ++ packet.m_packetType = RTMP_PACKET_TYPE_INVOKE; ++ packet.m_nTimeStamp = 0; ++ packet.m_nInfoField2 = 0; ++ packet.m_hasAbsTimestamp = 0; ++ packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; ++ ++ enc = packet.m_body; ++ enc = AMF_EncodeString(enc, pend, &av_onFCSubscribe); ++ enc = AMF_EncodeNumber(enc, pend, 0); ++ *enc++ = AMF_NULL; ++ ++ *enc++ = AMF_OBJECT; ++ enc = AMF_EncodeNamedString(enc, pend, &av_level, &av_status); ++ enc = AMF_EncodeNamedString(enc, pend, &av_code, &av_NetStream_Play_Start); ++ enc = AMF_EncodeNamedString(enc, pend, &av_description, &av_FCSubscribe_message); ++ enc = AMF_EncodeNamedNumber(enc, pend, &av_clientid, 0); ++ *enc++ = 0; ++ *enc++ = 0; ++ *enc++ = AMF_OBJECT_END; ++ ++ packet.m_nBodySize = enc - packet.m_body; ++ ++ return RTMP_SendPacket(r, &packet, FALSE); ++} ++ + static void + spawn_dumper(int argc, AVal *av, char *cmd) + { +@@ -389,6 +484,8 @@ + len += 40; + break; + case AMF_OBJECT: ++ case AMF_ECMA_ARRAY: ++ case AMF_STRICT_ARRAY: + len += 9; + len += countAMF(&p->p_vu.p_object, argc); + (*argc) += 2; +@@ -404,12 +501,14 @@ + static char * + dumpAMF(AMFObject *obj, char *ptr, AVal *argv, int *argc) + { +- int i, len, ac = *argc; ++ int i, ac = *argc; + const char opt[] = "NBSO Z"; + +- for (i=0, len=0; i < obj->o_num; i++) ++ for (i = 0; i < obj->o_num; i++) + { + AMFObjectProperty *p = &obj->o_props[i]; ++ if ((p->p_type == AMF_ECMA_ARRAY) || (p->p_type == AMF_STRICT_ARRAY)) ++ p->p_type = AMF_OBJECT; + argv[ac].av_val = ptr+1; + argv[ac++].av_len = 2; + ptr += sprintf(ptr, " -C "); +@@ -569,6 +668,7 @@ + server->arglen += countAMF(&r->Link.extras, &server->argc); + } + SendConnectResult(r, txn); ++ SendCheckBWResponse(r, FALSE, TRUE); + } + else if (AVMATCH(&method, &av_createStream)) + { +@@ -583,10 +683,26 @@ + AVal usherToken; + AMFProp_GetString(AMF_GetProp(&obj, NULL, 3), &usherToken); + AVreplace(&usherToken, &av_dquote, &av_escdquote); ++#ifdef WIN32 ++ AVreplace(&usherToken, &av_caret, &av_esccaret); ++ AVreplace(&usherToken, &av_pipe, &av_escpipe); ++#endif + server->arglen += 6 + usherToken.av_len; + server->argc += 2; + r->Link.usherToken = usherToken; + } ++ else if (AVMATCH(&method, &av__checkbw)) ++ { ++ SendCheckBWResponse(r, TRUE, FALSE); ++ } ++ else if (AVMATCH(&method, &av_checkBandwidth)) ++ { ++ SendCheckBWResponse(r, FALSE, FALSE); ++ } ++ else if (AVMATCH(&method, &av_FCSubscribe)) ++ { ++ SendOnFCSubscribe(r); ++ } + else if (AVMATCH(&method, &av_play)) + { + char *file, *p, *q, *cmd, *ptr; +@@ -600,6 +716,17 @@ + if (obj.o_num > 5) + r->Link.length = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 5)); + */ ++ double StartFlag = 0; ++ AMFObjectProperty *Start = AMF_GetProp(&obj, NULL, 4); ++ if (!(Start->p_type == AMF_INVALID)) ++ StartFlag = AMFProp_GetNumber(Start); ++ r->Link.app = AVcopy(r->Link.app); ++ if (StartFlag == -1000 || (r->Link.app.av_val && strstr(r->Link.app.av_val, "live"))) ++ { ++ StartFlag = -1000; ++ server->arglen += 7; ++ server->argc += 1; ++ } + if (r->Link.tcUrl.av_len) + { + len = server->arglen + r->Link.playpath.av_len + 4 + +@@ -617,6 +744,7 @@ + argv[argc].av_val = ptr + 1; + argv[argc++].av_len = 2; + argv[argc].av_val = ptr + 5; ++ r->Link.tcUrl = StripParams(&r->Link.tcUrl); + ptr += sprintf(ptr," -r \"%s\"", r->Link.tcUrl.av_val); + argv[argc++].av_len = r->Link.tcUrl.av_len; + +@@ -641,6 +769,7 @@ + argv[argc].av_val = ptr + 1; + argv[argc++].av_len = 2; + argv[argc].av_val = ptr + 5; ++ r->Link.swfUrl = StripParams(&r->Link.swfUrl); + ptr += sprintf(ptr, " -W \"%s\"", r->Link.swfUrl.av_val); + argv[argc++].av_len = r->Link.swfUrl.av_len; + } +@@ -663,10 +792,17 @@ + r->Link.usherToken.av_val = NULL; + r->Link.usherToken.av_len = 0; + } +- if (r->Link.extras.o_num) { +- ptr = dumpAMF(&r->Link.extras, ptr, argv, &argc); +- AMF_Reset(&r->Link.extras); +- } ++ if (StartFlag == -1000) ++ { ++ argv[argc].av_val = ptr + 1; ++ argv[argc++].av_len = 6; ++ ptr += sprintf(ptr, " --live"); ++ } ++ if (r->Link.extras.o_num) ++ { ++ ptr = dumpAMF(&r->Link.extras, ptr, argv, &argc); ++ AMF_Reset(&r->Link.extras); ++ } + argv[argc].av_val = ptr + 1; + argv[argc++].av_len = 2; + argv[argc].av_val = ptr + 5; +@@ -674,7 +810,13 @@ + r->Link.playpath.av_len, r->Link.playpath.av_val); + argv[argc++].av_len = r->Link.playpath.av_len; + +- av = r->Link.playpath; ++ if (r->Link.playpath.av_len) ++ av = r->Link.playpath; ++ else ++ { ++ av.av_val = "file"; ++ av.av_len = 4; ++ } + /* strip trailing URL parameters */ + q = memchr(av.av_val, '?', av.av_len); + if (q) +@@ -708,25 +850,82 @@ + + memcpy(file, av.av_val, av.av_len); + file[av.av_len] = '\0'; +- for (p=file; *p; p++) +- if (*p == ':') +- *p = '_'; + +- /* Add extension if none present */ +- if (file[av.av_len - 4] != '.') +- { +- av.av_len += 4; +- } +- /* Always use flv extension, regardless of original */ +- if (strcmp(file+av.av_len-4, ".flv")) +- { +- strcpy(file+av.av_len-4, ".flv"); +- } ++ if (strlen(file) < 128) ++ { ++ /* Add extension if none present */ ++ if (file[av.av_len - 4] != '.') ++ { ++ av.av_len += 4; ++ } ++ ++ /* Always use flv extension, regardless of original */ ++ if (strcmp(file + av.av_len - 4, ".flv")) ++ { ++ strcpy(file + av.av_len - 4, ".flv"); ++ } ++ ++ /* Remove invalid characters from filename */ ++ file = strreplace(file, 0, ":", "_", TRUE); ++ file = strreplace(file, 0, "&", "_", TRUE); ++ file = strreplace(file, 0, "^", "_", TRUE); ++ file = strreplace(file, 0, "|", "_", TRUE); ++ } ++ else ++ { ++ /* Filename too long - generate unique name */ ++ strcpy(file, "vXXXXXX"); ++ mktemp(file); ++ strcat(file, ".flv"); ++ } ++ ++ /* Add timestamp to the filename */ ++ char *filename, *pfilename, timestamp[21]; ++ int filename_len, timestamp_len; ++ time_t current_time; ++ ++ time(¤t_time); ++ timestamp_len = strftime(×tamp[0], sizeof (timestamp), "%Y-%m-%d_%I-%M-%S_", localtime(¤t_time)); ++ timestamp[timestamp_len] = '\0'; ++ filename_len = strlen(file); ++ filename = malloc(timestamp_len + filename_len + 1); ++ pfilename = filename; ++ memcpy(pfilename, timestamp, timestamp_len); ++ pfilename += timestamp_len; ++ memcpy(pfilename, file, filename_len); ++ pfilename += filename_len; ++ *pfilename++ = '\0'; ++ file = filename; ++ + argv[argc].av_val = ptr + 1; + argv[argc++].av_len = 2; + argv[argc].av_val = file; + argv[argc].av_len = av.av_len; +- ptr += sprintf(ptr, " -o %s", file); ++#ifdef VLC ++ char *vlc; ++ int didAlloc = FALSE; ++ ++ if (getenv("VLC")) ++ vlc = getenv("VLC"); ++ else if (getenv("ProgramFiles")) ++ { ++ vlc = malloc(512 * sizeof (char)); ++ didAlloc = TRUE; ++ char *ProgramFiles = getenv("ProgramFiles"); ++ sprintf(vlc, "\"%s%s", ProgramFiles, " (x86)\\VideoLAN\\VLC\\vlc.exe"); ++ if (!file_exists(vlc + 1)) ++ sprintf(vlc + 1, "%s%s", ProgramFiles, "\\VideoLAN\\VLC\\vlc.exe"); ++ strcpy(vlc + strlen(vlc), "\" -"); ++ } ++ else ++ vlc = "vlc -"; ++ ++ ptr += sprintf(ptr, " | %s", vlc); ++ if (didAlloc) ++ free(vlc); ++#else ++ ptr += sprintf(ptr, " -o \"%s\"", file); ++#endif + now = RTMP_GetTime(); + if (now - server->filetime < DUPTIME && AVMATCH(&argv[argc], &server->filename)) + { +@@ -740,7 +939,21 @@ + server->filetime = now; + free(server->filename.av_val); + server->filename = argv[argc++]; +- spawn_dumper(argc, argv, cmd); ++#ifdef VLC ++ FILE *vlc_cmdfile = fopen("VLC.bat", "w"); ++ char *vlc_batchcmd = strreplace(cmd, 0, "%", "%%", FALSE); ++ fprintf(vlc_cmdfile, "%s\n", vlc_batchcmd); ++ fclose(vlc_cmdfile); ++ free(vlc_batchcmd); ++ spawn_dumper(argc, argv, "VLC.bat"); ++#else ++ spawn_dumper(argc, argv, cmd); ++#endif ++ ++ /* Save command to text file */ ++ FILE *cmdfile = fopen("Command.txt", "a"); ++ fprintf(cmdfile, "%s\n", cmd); ++ fclose(cmdfile); + } + + free(cmd); +@@ -859,12 +1072,18 @@ + { + case 'q': + RTMP_LogPrintf("Exiting\n"); +- stopStreaming(rtmpServer); +- exit(0); ++ if (rtmpServer) ++ stopStreaming(rtmpServer); + break; + default: + RTMP_LogPrintf("Unknown command \'%c\', ignoring\n", ich); + } ++ sleep(1); ++ if (rtmpServer && (rtmpServer->state == STREAMING_STOPPED)) ++ { ++ RTMP_Log(RTMP_LOGDEBUG, "Exiting text UI thread"); ++ break; ++ } + } + TFRET(); + } +@@ -1052,7 +1271,6 @@ + } + } + +- + void + sigIntHandler(int sig) + { +@@ -1189,3 +1407,15 @@ + src->av_val = dest; + src->av_len = dptr - dest; + } ++ ++int ++file_exists(const char *fname) ++{ ++ FILE *file; ++ if ((file = fopen(fname, "r"))) ++ { ++ fclose(file); ++ return TRUE; ++ } ++ return FALSE; ++} +diff -uNr librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/rtmpsuck.c librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/rtmpsuck.c +--- librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/rtmpsuck.c 2014-03-02 19:20:23.000000000 +0100 ++++ librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/rtmpsuck.c 2014-05-04 17:55:17.525338289 +0200 +@@ -25,10 +25,13 @@ + */ + + #include ++#ifdef __MINGW_H ++#include ++#endif + #include + #include + #include +- ++#include + #include + #include + +@@ -141,18 +144,21 @@ + SAVC(secureToken); + SAVC(onStatus); + SAVC(close); ++SAVC(play2); + static const AVal av_NetStream_Failed = AVC("NetStream.Failed"); + static const AVal av_NetStream_Play_Failed = AVC("NetStream.Play.Failed"); +-static const AVal av_NetStream_Play_StreamNotFound = +-AVC("NetStream.Play.StreamNotFound"); +-static const AVal av_NetConnection_Connect_InvalidApp = +-AVC("NetConnection.Connect.InvalidApp"); ++static const AVal av_NetStream_Play_StreamNotFound = AVC("NetStream.Play.StreamNotFound"); ++static const AVal av_NetConnection_Connect_InvalidApp = AVC("NetConnection.Connect.InvalidApp"); ++static const AVal av_NetConnection_Connect_Rejected = AVC("NetConnection.Connect.Rejected"); + static const AVal av_NetStream_Play_Start = AVC("NetStream.Play.Start"); + static const AVal av_NetStream_Play_Complete = AVC("NetStream.Play.Complete"); + static const AVal av_NetStream_Play_Stop = AVC("NetStream.Play.Stop"); ++static const AVal av_NetStream_Authenticate_UsherToken = AVC("NetStream.Authenticate.UsherToken"); + + static const char *cst[] = { "client", "server" }; + ++char *dumpAMF(AMFObject *obj, char *ptr); ++ + // Returns 0 for OK/Failed/error, 1 for 'Stop or Complete' + int + ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *body) +@@ -198,26 +204,28 @@ + if (cobj.o_props[i].p_type == AMF_STRING) + { + pval = cobj.o_props[i].p_vu.p_aval; +- RTMP_LogPrintf("%.*s: %.*s\n", pname.av_len, pname.av_val, pval.av_len, pval.av_val); ++ RTMP_LogPrintf("%10.*s : %.*s\n", pname.av_len, pname.av_val, pval.av_len, pval.av_val); + } + if (AVMATCH(&pname, &av_app)) + { +- server->rc.Link.app = pval; ++ server->rc.Link.app = AVcopy(pval); + pval.av_val = NULL; + } + else if (AVMATCH(&pname, &av_flashVer)) + { +- server->rc.Link.flashVer = pval; ++ server->rc.Link.flashVer = AVcopy(pval); + pval.av_val = NULL; + } + else if (AVMATCH(&pname, &av_swfUrl)) + { + #ifdef CRYPTO + if (pval.av_val) +- RTMP_HashSWF(pval.av_val, &server->rc.Link.SWFSize, +- (unsigned char *)server->rc.Link.SWFHash, 30); ++ { ++ AVal swfUrl = StripParams(&pval); ++ RTMP_HashSWF(swfUrl.av_val, &server->rc.Link.SWFSize, (unsigned char *) server->rc.Link.SWFHash, 30); ++ } + #endif +- server->rc.Link.swfUrl = pval; ++ server->rc.Link.swfUrl = AVcopy(pval); + pval.av_val = NULL; + } + else if (AVMATCH(&pname, &av_tcUrl)) +@@ -225,7 +233,7 @@ + char *r1 = NULL, *r2; + int len; + +- server->rc.Link.tcUrl = pval; ++ server->rc.Link.tcUrl = AVcopy(pval); + if ((pval.av_val[0] | 0x40) == 'r' && + (pval.av_val[1] | 0x40) == 't' && + (pval.av_val[2] | 0x40) == 'm' && +@@ -267,7 +275,7 @@ + } + else if (AVMATCH(&pname, &av_pageUrl)) + { +- server->rc.Link.pageUrl = pval; ++ server->rc.Link.pageUrl = AVcopy(pval); + pval.av_val = NULL; + } + else if (AVMATCH(&pname, &av_audioCodecs)) +@@ -287,14 +295,21 @@ + if (pval.av_val) + free(pval.av_val); + } ++ + if (obj.o_num > 3) + { +- if (AMFProp_GetBoolean(&obj.o_props[3])) +- server->rc.Link.lFlags |= RTMP_LF_AUTH; +- if (obj.o_num > 4) +- { +- AMFProp_GetString(&obj.o_props[4], &server->rc.Link.auth); +- } ++ int i = obj.o_num - 3; ++ server->rc.Link.extras.o_num = i; ++ server->rc.Link.extras.o_props = malloc(i * sizeof (AMFObjectProperty)); ++ memcpy(server->rc.Link.extras.o_props, obj.o_props + 3, i * sizeof (AMFObjectProperty)); ++ obj.o_num = 3; ++ } ++ ++ if (server->rc.Link.extras.o_num) ++ { ++ server->rc.Link.Extras.av_val = calloc(2048, sizeof (char)); ++ dumpAMF(&server->rc.Link.extras, server->rc.Link.Extras.av_val); ++ server->rc.Link.Extras.av_len = strlen(server->rc.Link.Extras.av_val); + } + + if (!RTMP_Connect(&server->rc, pack)) +@@ -303,6 +318,37 @@ + return 1; + } + server->rc.m_bSendCounter = FALSE; ++ ++ if (server->rc.Link.extras.o_props) ++ { ++ AMF_Reset(&server->rc.Link.extras); ++ } ++ } ++ else if (AVMATCH(&method, &av_NetStream_Authenticate_UsherToken)) ++ { ++ AVal usherToken = {0}; ++ AMFProp_GetString(AMF_GetProp(&obj, NULL, 3), &usherToken); ++ server->rc.Link.usherToken = AVcopy(usherToken); ++ RTMP_LogPrintf("%10s : %.*s\n", "usherToken", server->rc.Link.usherToken.av_len, server->rc.Link.usherToken.av_val); ++ } ++ else if (AVMATCH(&method, &av_play2)) ++ { ++ RTMP_Log(RTMP_LOGDEBUG, "%s: Detected play2 request\n", __FUNCTION__); ++ if (body && nBodySize > 0) ++ { ++ char* pCmd = (char*) body; ++ char* pEnd = pCmd + nBodySize - 4; ++ while (pCmd < pEnd) ++ { ++ if (pCmd[0] == 'p' && pCmd[1] == 'l' && pCmd[2] == 'a' && pCmd[3] == 'y' && pCmd[4] == '2') ++ { ++ /* Disable bitrate transition by sending invalid command */ ++ pCmd[4] = 'z'; ++ break; ++ } ++ ++pCmd; ++ } ++ } + } + else if (AVMATCH(&method, &av_play)) + { +@@ -323,6 +369,14 @@ + if (!av.av_val) + goto out; + ++ double StartFlag = 0; ++ AMFObjectProperty *Start = AMF_GetProp(&obj, NULL, 4); ++ if (!(Start->p_type == AMF_INVALID)) ++ StartFlag = AMFProp_GetNumber(Start); ++ if (StartFlag == -1000 || (server->rc.Link.app.av_val && strstr(server->rc.Link.app.av_val, "live"))) ++ StartFlag = -1000; ++ RTMP_LogPrintf("%10s : %s\n", "live", (StartFlag == -1000) ? "yes" : "no"); ++ + /* check for duplicates */ + for (fl = server->f_head; fl; fl=fl->f_next) + { +@@ -362,19 +416,104 @@ + /* hope there aren't more than 255 dups */ + if (count) + flen += 2; +- file = malloc(flen+1); ++ file = malloc(flen + 5); + + memcpy(file, av.av_val, av.av_len); + if (count) + sprintf(file+av.av_len, "%02x", count); + else + file[av.av_len] = '\0'; +- for (p=file; *p; p++) +- if (*p == ':') +- *p = '_'; +- RTMP_LogPrintf("Playpath: %.*s\nSaving as: %s\n", +- server->rc.Link.playpath.av_len, server->rc.Link.playpath.av_val, +- file); ++ ++ if (strlen(file) < 128) ++ { ++ /* Add extension if none present */ ++ if (file[av.av_len - 4] != '.') ++ { ++ av.av_len += 4; ++ } ++ ++ /* Always use flv extension, regardless of original */ ++ if (strcmp(file + av.av_len - 4, ".flv")) ++ { ++ strcpy(file + av.av_len - 4, ".flv"); ++ } ++ ++ /* Remove invalid characters from filename */ ++ file = strreplace(file, 0, ":", "_", TRUE); ++ file = strreplace(file, 0, "&", "_", TRUE); ++ file = strreplace(file, 0, "^", "_", TRUE); ++ file = strreplace(file, 0, "|", "_", TRUE); ++ } ++ else ++ { ++ /* Filename too long - generate unique name */ ++ strcpy(file, "vXXXXXX"); ++ mktemp(file); ++ strcat(file, ".flv"); ++ } ++ ++ /* Add timestamp to the filename */ ++ char *filename, *pfilename, timestamp[21]; ++ int filename_len, timestamp_len; ++ time_t current_time; ++ ++ time(¤t_time); ++ timestamp_len = strftime(×tamp[0], sizeof (timestamp), "%Y-%m-%d_%I-%M-%S_", localtime(¤t_time)); ++ timestamp[timestamp_len] = '\0'; ++ filename_len = strlen(file); ++ filename = malloc(timestamp_len + filename_len + 1); ++ pfilename = filename; ++ memcpy(pfilename, timestamp, timestamp_len); ++ pfilename += timestamp_len; ++ memcpy(pfilename, file, filename_len); ++ pfilename += filename_len; ++ *pfilename++ = '\0'; ++ file = filename; ++ ++ RTMP_LogPrintf("%10s : %.*s\n%10s : %s\n", "Playpath", server->rc.Link.playpath.av_len, ++ server->rc.Link.playpath.av_val, "Saving as", file); ++ ++ /* Save command to text file */ ++ char *cmd = NULL, *ptr = NULL; ++ AVal swfUrl, tcUrl; ++ ++ cmd = calloc(4096, sizeof (char)); ++ ptr = cmd; ++ tcUrl = StripParams(&server->rc.Link.tcUrl); ++ swfUrl = StripParams(&server->rc.Link.swfUrl); ++ ptr += sprintf(ptr, "rtmpdump -r \"%.*s\" -a \"%.*s\" -f \"%.*s\" -W \"%.*s\" -p \"%.*s\"", ++ tcUrl.av_len, tcUrl.av_val, ++ server->rc.Link.app.av_len, server->rc.Link.app.av_val, ++ server->rc.Link.flashVer.av_len, server->rc.Link.flashVer.av_val, ++ swfUrl.av_len, swfUrl.av_val, ++ server->rc.Link.pageUrl.av_len, server->rc.Link.pageUrl.av_val); ++ ++ if (server->rc.Link.usherToken.av_val) ++ { ++ char *usherToken = strreplace(server->rc.Link.usherToken.av_val, server->rc.Link.usherToken.av_len, "\"", "\\\"", TRUE); ++#ifdef WIN32 ++ usherToken = strreplace(usherToken, 0, "^", "^^", TRUE); ++ usherToken = strreplace(usherToken, 0, "|", "^|", TRUE); ++#endif ++ ptr += sprintf(ptr, " --jtv \"%s\"", usherToken); ++ free(usherToken); ++ } ++ ++ if (server->rc.Link.Extras.av_len) ++ { ++ ptr += sprintf(ptr, "%.*s", server->rc.Link.Extras.av_len, server->rc.Link.Extras.av_val); ++ } ++ ++ if (StartFlag == -1000) ++ ptr += sprintf(ptr, "%s", " --live"); ++ ptr += sprintf(ptr, " -y \"%.*s\"", server->rc.Link.playpath.av_len, server->rc.Link.playpath.av_val); ++ ptr += sprintf(ptr, " -o \"%s\"\n", file); ++ ++ FILE *cmdfile = fopen("Command.txt", "a"); ++ fprintf(cmdfile, "%s", cmd); ++ fclose(cmdfile); ++ free(cmd); ++ + out = fopen(file, "wb"); + free(file); + if (!out) +@@ -407,9 +546,10 @@ + + RTMP_Log(RTMP_LOGDEBUG, "%s, onStatus: %s", __FUNCTION__, code.av_val); + if (AVMATCH(&code, &av_NetStream_Failed) +- || AVMATCH(&code, &av_NetStream_Play_Failed) +- || AVMATCH(&code, &av_NetStream_Play_StreamNotFound) +- || AVMATCH(&code, &av_NetConnection_Connect_InvalidApp)) ++ || AVMATCH(&code, &av_NetStream_Play_Failed) ++ || AVMATCH(&code, &av_NetStream_Play_StreamNotFound) ++ || AVMATCH(&code, &av_NetConnection_Connect_Rejected) ++ || AVMATCH(&code, &av_NetConnection_Connect_InvalidApp)) + { + ret = 1; + } +@@ -719,13 +859,18 @@ + { + case 'q': + RTMP_LogPrintf("Exiting\n"); +- stopStreaming(rtmpServer); +- free(rtmpServer); +- exit(0); ++ if (rtmpServer) ++ stopStreaming(rtmpServer); + break; + default: + RTMP_LogPrintf("Unknown command \'%c\', ignoring\n", ich); + } ++ sleep(1); ++ if (rtmpServer && (rtmpServer->state == STREAMING_STOPPED)) ++ { ++ RTMP_Log(RTMP_LOGDEBUG, "Exiting text UI thread"); ++ break; ++ } + } + TFRET(); + } +@@ -1123,7 +1268,6 @@ + } + } + +- + void + sigIntHandler(int sig) + { +@@ -1196,3 +1340,48 @@ + #endif + return nStatus; + } ++ ++char * ++dumpAMF(AMFObject *obj, char *ptr) ++{ ++ int i; ++ const char opt[] = "NBSO Z"; ++ ++ for (i = 0; i < obj->o_num; i++) ++ { ++ AMFObjectProperty *p = &obj->o_props[i]; ++ if ((p->p_type == AMF_ECMA_ARRAY) || (p->p_type == AMF_STRICT_ARRAY)) ++ p->p_type = AMF_OBJECT; ++ if (p->p_type > 5) ++ continue; ++ ptr += sprintf(ptr, " -C "); ++ if (p->p_name.av_val) ++ *ptr++ = 'N'; ++ *ptr++ = opt[p->p_type]; ++ *ptr++ = ':'; ++ if (p->p_name.av_val) ++ ptr += sprintf(ptr, "%.*s:", p->p_name.av_len, p->p_name.av_val); ++ switch (p->p_type) ++ { ++ case AMF_BOOLEAN: ++ *ptr++ = p->p_vu.p_number != 0 ? '1' : '0'; ++ break; ++ case AMF_STRING: ++ memcpy(ptr, p->p_vu.p_aval.av_val, p->p_vu.p_aval.av_len); ++ ptr += p->p_vu.p_aval.av_len; ++ break; ++ case AMF_NUMBER: ++ ptr += sprintf(ptr, "%f", p->p_vu.p_number); ++ break; ++ case AMF_OBJECT: ++ *ptr++ = '1'; ++ ptr = dumpAMF(&p->p_vu.p_object, ptr); ++ ptr += sprintf(ptr, " -C O:0"); ++ break; ++ case AMF_NULL: ++ default: ++ break; ++ } ++ } ++ return ptr; ++} +diff -uNr librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/thread.c librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/thread.c +--- librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497.org/thread.c 2014-03-02 19:20:23.000000000 +0100 ++++ librtmp-79459a2b43f41ac44a2ec001139bcb7b1b8f7497/thread.c 2014-05-04 17:55:17.525338289 +0200 +@@ -32,7 +32,7 @@ + HANDLE thd; + + thd = (HANDLE) _beginthread(routine, 0, args); +- if (thd == -1L) ++ if (thd == INVALID_HANDLE_VALUE) + RTMP_LogPrintf("%s, _beginthread failed with %d\n", __FUNCTION__, errno); + + return thd;