Created
January 29, 2016 04:08
-
-
Save phracker/1a3856343adf467ec286 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git Makefile Makefile | |
index a1595a8..9fe7584 100644 | |
--- Makefile | |
+++ Makefile | |
@@ -32,7 +32,7 @@ BINDIR=$(DESTDIR)$(bindir) | |
SBINDIR=$(DESTDIR)$(sbindir) | |
MANDIR=$(DESTDIR)$(mandir) | |
-LIBS_posix= | |
+LIBS_posix=-lm | |
LIBS_darwin= | |
LIBS_mingw=-lws2_32 -lwinmm -lgdi32 | |
LIB_RTMP=-Llibrtmp -lrtmp | |
diff --git librtmp/Makefile librtmp/Makefile | |
index 2c1c790..e367535 100644 | |
--- librtmp/Makefile | |
+++ librtmp/Makefile | |
@@ -26,7 +26,7 @@ REQ_GNUTLS=gnutls,hogweed,nettle | |
REQ_OPENSSL=libssl,libcrypto | |
PUB_GNUTLS=-lgmp | |
LIBZ=-lz | |
-LIBS_posix= | |
+LIBS_posix=-lm | |
LIBS_darwin= | |
LIBS_mingw=-lws2_32 -lwinmm -lgdi32 | |
LIB_GNUTLS=-lgnutls -lhogweed -lnettle -lgmp $(LIBZ) | |
diff --git librtmp/amf.c librtmp/amf.c | |
index 1c5f99f..1310cbe 100644 | |
--- librtmp/amf.c | |
+++ librtmp/amf.c | |
@@ -319,6 +319,13 @@ AMFProp_SetName(AMFObjectProperty *prop, AVal *name) | |
prop->p_name = *name; | |
} | |
+void | |
+AMFProp_SetString(AMFObjectProperty *prop, AVal *str) | |
+{ | |
+ prop->p_type = AMF_STRING; | |
+ prop->p_vu.p_aval = *str; | |
+} | |
+ | |
AMFDataType | |
AMFProp_GetType(AMFObjectProperty *prop) | |
{ | |
@@ -503,6 +510,9 @@ AMF3Prop_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize, | |
return -1; | |
} | |
+ if (*pBuffer == AMF3_NULL) | |
+ bDecodeName = FALSE; | |
+ | |
/* decode name */ | |
if (bDecodeName) | |
{ | |
@@ -586,7 +596,7 @@ AMF3Prop_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize, | |
} | |
case AMF3_OBJECT: | |
{ | |
- int nRes = AMF3_Decode(&prop->p_vu.p_object, pBuffer, nSize, TRUE); | |
+ int nRes = AMF3_Decode(&prop->p_vu.p_object, pBuffer, nSize, FALSE); | |
if (nRes == -1) | |
return -1; | |
nSize -= nRes; | |
@@ -620,6 +630,9 @@ AMFProp_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize, | |
return -1; | |
} | |
+ if (*pBuffer == AMF_NULL) | |
+ bDecodeName = FALSE; | |
+ | |
if (bDecodeName && nSize < 4) | |
{ /* at least name (length + at least 1 byte) and 1 byte of data */ | |
RTMP_Log(RTMP_LOGDEBUG, | |
@@ -649,9 +662,8 @@ AMFProp_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize, | |
return -1; | |
} | |
- nSize--; | |
- | |
prop->p_type = *pBuffer++; | |
+ nSize--; | |
switch (prop->p_type) | |
{ | |
case AMF_NUMBER: | |
@@ -697,9 +709,13 @@ AMFProp_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize, | |
break; | |
case AMF_REFERENCE: | |
{ | |
- RTMP_Log(RTMP_LOGERROR, "AMF_REFERENCE not supported!"); | |
- return -1; | |
- break; | |
+ RTMP_Log(RTMP_LOGDEBUG, "AMF_REFERENCE is not fully supported!"); | |
+ if (nSize < 2) | |
+ return -1; | |
+ prop->p_type = AMF_NUMBER; | |
+ prop->p_vu.p_number = AMF_DecodeInt16(pBuffer); | |
+ nSize -= 2; | |
+ break; | |
} | |
case AMF_ECMA_ARRAY: | |
{ | |
@@ -731,13 +747,13 @@ AMFProp_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize, | |
} | |
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; | |
@@ -809,8 +825,8 @@ AMFProp_Dump(AMFObjectProperty *prop) | |
} | |
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; | |
@@ -1021,11 +1037,18 @@ AMF3_Decode(AMFObject *obj, const char *pBuffer, int nSize, int bAMFData) | |
obj->o_props = NULL; | |
if (bAMFData) | |
{ | |
- if (*pBuffer != AMF3_OBJECT) | |
- RTMP_Log(RTMP_LOGERROR, | |
- "AMF3 Object encapsulated in AMF stream does not start with AMF3_OBJECT!"); | |
- pBuffer++; | |
- nSize--; | |
+ // Decode only if it's an AMF3 object | |
+ if (*pBuffer == AMF3_OBJECT) | |
+ { | |
+ pBuffer++; | |
+ nSize--; | |
+ } | |
+ else | |
+ { | |
+ RTMP_Log(RTMP_LOGERROR, "AMF3 Object encapsulated in AMF stream does not start with AMF3_OBJECT!"); | |
+ pBuffer += nOriginalSize; | |
+ return nOriginalSize; | |
+ } | |
} | |
ref = 0; | |
@@ -1043,8 +1066,12 @@ AMF3_Decode(AMFObject *obj, const char *pBuffer, int nSize, int bAMFData) | |
{ | |
int32_t classRef = (ref >> 1); | |
- AMF3ClassDef cd = { {0, 0} | |
- }; | |
+ AMF3ClassDef cd; | |
+ cd.cd_name.av_len = 0; | |
+ cd.cd_name.av_val = 0; | |
+ cd.cd_externalizable = FALSE; | |
+ cd.cd_dynamic = TRUE; | |
+ cd.cd_num = 0; | |
AMFObjectProperty prop; | |
if ((classRef & 0x1) == 0) | |
@@ -1061,6 +1088,7 @@ AMF3_Decode(AMFObject *obj, const char *pBuffer, int nSize, int bAMFData) | |
cd.cd_dynamic = ((classExtRef >> 1) & 0x1) == 1; | |
cdnum = classExtRef >> 2; | |
+ cd.cd_num = cdnum; | |
/* class name */ | |
@@ -1070,24 +1098,25 @@ AMF3_Decode(AMFObject *obj, const char *pBuffer, int nSize, int bAMFData) | |
/*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 < cdnum; i++) | |
- { | |
- AVal memberName; | |
- if (nSize <=0) | |
+ { | |
+ AVal memberName = {NULL, 0}; | |
+ if (nSize <= 0) | |
{ | |
invalid: | |
RTMP_Log(RTMP_LOGDEBUG, "%s, invalid class encoding!", | |
__FUNCTION__); | |
return nOriginalSize; | |
- } | |
- len = AMF3ReadString(pBuffer, &memberName); | |
- RTMP_Log(RTMP_LOGDEBUG, "Member: %s", memberName.av_val); | |
- AMF3CD_AddProp(&cd, &memberName); | |
+ } | |
+ len = AMF3ReadString(pBuffer, &memberName); | |
+ if (memberName.av_val) | |
+ { | |
+ RTMP_Log(RTMP_LOGDEBUG, "Member: %.*s", memberName.av_len, memberName.av_val); | |
+ AMF3CD_AddProp(&cd, &memberName); | |
+ } | |
nSize -= len; | |
pBuffer += len; | |
} | |
@@ -1118,10 +1147,10 @@ invalid: | |
else | |
{ | |
int nRes, i; | |
- for (i = 0; i < cd.cd_num; i++) /* non-dynamic */ | |
- { | |
- if (nSize <=0) | |
- goto invalid; | |
+ for (i = 0; i < cd.cd_num; i++) /* non-dynamic */ | |
+ { | |
+ if (nSize <= 0) | |
+ goto invalid; | |
nRes = AMF3Prop_Decode(&prop, pBuffer, nSize, FALSE); | |
if (nRes == -1) | |
RTMP_Log(RTMP_LOGDEBUG, "%s, failed to decode AMF3 property!", | |
@@ -1138,9 +1167,9 @@ invalid: | |
int len = 0; | |
do | |
- { | |
- if (nSize <=0) | |
- goto invalid; | |
+ { | |
+ if (nSize <= 0) | |
+ goto invalid; | |
nRes = AMF3Prop_Decode(&prop, pBuffer, nSize, TRUE); | |
AMF_AddProp(obj, &prop); | |
@@ -1154,7 +1183,15 @@ invalid: | |
} | |
RTMP_Log(RTMP_LOGDEBUG, "class object!"); | |
} | |
- return nOriginalSize - nSize; | |
+ | |
+ /** | |
+ * In case of switch to AMF3 serialization consume rest of the unprocessed | |
+ * packet data to make sure it's not later processed as AMF0 data. | |
+ */ | |
+ if (bAMFData) | |
+ return nOriginalSize; | |
+ else | |
+ return nOriginalSize - nSize; | |
} | |
int | |
@@ -1272,7 +1309,8 @@ AMF3CD_AddProp(AMF3ClassDef *cd, AVal *prop) | |
{ | |
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 --git librtmp/handshake.h librtmp/handshake.h | |
index 0438486..104af28 100644 | |
--- librtmp/handshake.h | |
+++ librtmp/handshake.h | |
@@ -707,7 +707,7 @@ HandShake(RTMP * r, int FP9HandShake) | |
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 @@ HandShake(RTMP * r, int FP9HandShake) | |
#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 @@ HandShake(RTMP * r, int FP9HandShake) | |
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 @@ HandShake(RTMP * r, int FP9HandShake) | |
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 @@ HandShake(RTMP * r, int FP9HandShake) | |
#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 @@ HandShake(RTMP * r, int FP9HandShake) | |
__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 @@ HandShake(RTMP * r, int FP9HandShake) | |
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 @@ HandShake(RTMP * r, int FP9HandShake) | |
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 @@ HandShake(RTMP * r, int FP9HandShake) | |
} | |
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 @@ SHandShake(RTMP * r) | |
{ | |
encrypted = FALSE; | |
} | |
- else if (type == 6 || type == 8) | |
+ else if (type == 6 || type == 8 || type == 9) | |
{ | |
offalg = 1; | |
encrypted = TRUE; | |
@@ -1148,7 +1156,7 @@ SHandShake(RTMP * r) | |
#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 --git librtmp/hashswf.c librtmp/hashswf.c | |
index 9f4e2c0..01b97e2 100644 | |
--- librtmp/hashswf.c | |
+++ librtmp/hashswf.c | |
@@ -70,7 +70,7 @@ extern TLS_CTX RTMP_TLS_ctx; | |
#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 @@ HTTP_get(struct HTTP_ctx *http, const char *url, HTTP_read_callback *cb) | |
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 @@ HTTP_get(struct HTTP_ctx *http, const char *url, HTTP_read_callback *cb) | |
} | |
p1 = strchr(sb.sb_buf, ' '); | |
- rc = atoi(p1 + 1); | |
+ rc = p1 ? atoi(p1 + 1) : 400; | |
http->status = rc; | |
if (rc >= 300) | |
@@ -379,13 +381,13 @@ make_unix_time(char *s) | |
if (fmt) | |
{ | |
/* Day, DD-MMM-YYYY HH:MM:SS GMT */ | |
- time.tm_mday = strtol(n + 1, &n, 0); | |
+ time.tm_mday = strtol(n + 1, &n, 10); | |
month = n + 1; | |
n = strchr(month, ' '); | |
- time.tm_year = strtol(n + 1, &n, 0); | |
- time.tm_hour = strtol(n + 1, &n, 0); | |
- time.tm_min = strtol(n + 1, &n, 0); | |
- time.tm_sec = strtol(n + 1, NULL, 0); | |
+ time.tm_year = strtol(n + 1, &n, 10); | |
+ time.tm_hour = strtol(n + 1, &n, 10); | |
+ time.tm_min = strtol(n + 1, &n, 10); | |
+ time.tm_sec = strtol(n + 1, NULL, 10); | |
} | |
else | |
{ | |
@@ -395,11 +397,11 @@ make_unix_time(char *s) | |
n = strchr(month, ' '); | |
while (isspace(*n)) | |
n++; | |
- time.tm_mday = strtol(n, &n, 0); | |
- time.tm_hour = strtol(n + 1, &n, 0); | |
- time.tm_min = strtol(n + 1, &n, 0); | |
- time.tm_sec = strtol(n + 1, &n, 0); | |
- time.tm_year = strtol(n + 1, NULL, 0); | |
+ time.tm_mday = strtol(n, &n, 10); | |
+ time.tm_hour = strtol(n + 1, &n, 10); | |
+ time.tm_min = strtol(n + 1, &n, 10); | |
+ time.tm_sec = strtol(n + 1, &n, 10); | |
+ time.tm_year = strtol(n + 1, NULL, 10); | |
} | |
if (time.tm_year > 100) | |
time.tm_year -= ysub; | |
@@ -528,9 +530,11 @@ RTMP_HashSWF(const char *url, unsigned int *size, unsigned char *hash, | |
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 @@ RTMP_HashSWF(const char *url, unsigned int *size, unsigned char *hash, | |
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 --git librtmp/log.c librtmp/log.c | |
index 1b52000..7564a15 100644 | |
--- librtmp/log.c | |
+++ librtmp/log.c | |
@@ -52,8 +52,8 @@ static void rtmp_log_default(int level, const char *format, va_list vl) | |
vsnprintf(str, MAX_PRINT_LEN-1, format, vl); | |
/* Filter out 'no-name' */ | |
- if ( RTMP_debuglevel<RTMP_LOGALL && strstr(str, "no-name" ) != NULL ) | |
- return; | |
+ if (RTMP_debuglevel < RTMP_LOGDEBUG && strstr(str, "no-name") != NULL) | |
+ return; | |
if ( !fmsg ) fmsg = stderr; | |
diff --git librtmp/parseurl.c librtmp/parseurl.c | |
index 646c70c..a0a83e6 100644 | |
--- librtmp/parseurl.c | |
+++ librtmp/parseurl.c | |
@@ -34,6 +34,7 @@ int RTMP_ParseURL(const char *url, int *protocol, AVal *host, unsigned int *port | |
AVal *playpath, AVal *app) | |
{ | |
char *p, *end, *col, *ques, *slash; | |
+ int doubleSlash = FALSE; | |
RTMP_Log(RTMP_LOGDEBUG, "Parsing..."); | |
@@ -140,11 +141,19 @@ parsehost: | |
char *slash2, *slash3 = NULL, *slash4 = NULL; | |
int applen, appnamelen; | |
- slash2 = strchr(p, '/'); | |
- if(slash2) | |
- slash3 = strchr(slash2+1, '/'); | |
- if(slash3) | |
- slash4 = strchr(slash3+1, '/'); | |
+ if ((slash2 = strstr(p, "//"))) | |
+ { | |
+ doubleSlash = TRUE; | |
+ slash2 += 1; | |
+ } | |
+ else | |
+ { | |
+ slash2 = strchr(p, '/'); | |
+ if (slash2) | |
+ slash3 = strchr(slash2 + 1, '/'); | |
+ if (slash3) | |
+ slash4 = strchr(slash3 + 1, '/'); | |
+ } | |
applen = end-p; /* ondemand, pass all parameters as app */ | |
appnamelen = applen; /* ondemand length */ | |
@@ -168,6 +177,8 @@ parsehost: | |
applen = appnamelen; | |
} | |
+ if (doubleSlash) | |
+ applen -= 1; | |
app->av_val = p; | |
app->av_len = applen; | |
RTMP_Log(RTMP_LOGDEBUG, "Parsed app : %.*s", applen, p); | |
diff --git librtmp/rtmp.c librtmp/rtmp.c | |
index ca7db6a..c652cff 100644 | |
--- librtmp/rtmp.c | |
+++ librtmp/rtmp.c | |
@@ -28,6 +28,7 @@ | |
#include <string.h> | |
#include <assert.h> | |
#include <time.h> | |
+#include <math.h> | |
#include "rtmp_sys.h" | |
#include "log.h" | |
@@ -68,6 +69,7 @@ TLS_CTX RTMP_TLS_ctx; | |
#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,18 +110,25 @@ typedef enum { | |
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); | |
+static void TransformRot13(AMFObject *obj, AVal *rindex, AVal *r); | |
+static void __TeaCrypt(uint32_t *block, uint32_t len, uint32_t *key); | |
+static AVal TeaEncrypt(AVal *srcData, AVal *srcKey); | |
#if 0 /* unused */ | |
static int SendBGHasStream(RTMP *r, double dId, AVal *playpath); | |
@@ -338,10 +347,15 @@ RTMP_Init(RTMP *r) | |
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; | |
+ r->Link.publishId = 0; | |
+ r->Link.dynamicPublish = FALSE; | |
} | |
void | |
@@ -359,6 +373,8 @@ RTMP_GetDuration(RTMP *r) | |
int | |
RTMP_IsConnected(RTMP *r) | |
{ | |
+ if (r->m_sb.sb_size > 0) | |
+ return TRUE; | |
return r->m_sb.sb_socket != -1; | |
} | |
@@ -445,6 +461,8 @@ RTMP_SetupStream(RTMP *r, | |
AVal *flashVer, | |
AVal *subscribepath, | |
AVal *usherToken, | |
+ AVal *WeebToken, | |
+ AVal *ccomm, | |
int dStart, | |
int dStop, int bLiveStream, long int timeout) | |
{ | |
@@ -467,6 +485,8 @@ RTMP_SetupStream(RTMP *r, | |
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) | |
@@ -515,6 +535,10 @@ RTMP_SetupStream(RTMP *r, | |
r->Link.subscribepath = *subscribepath; | |
if (usherToken && usherToken->av_len) | |
r->Link.usherToken = *usherToken; | |
+ if (WeebToken && WeebToken->av_len) | |
+ r->Link.WeebToken = *WeebToken; | |
+ if (ccomm && ccomm->av_len) | |
+ r->Link.ccomm = *ccomm; | |
r->Link.seekTime = dStart; | |
r->Link.stopTime = dStop; | |
if (bLiveStream) | |
@@ -572,14 +596,24 @@ static struct urlopt { | |
"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("ccommand"), OFF(Link.ccomm), OPT_STR, 0, | |
+ "Send custom command before play" }, | |
{ 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, | |
@@ -685,6 +719,9 @@ parseAMF(AMFObject *obj, AVal *av, int *depth) | |
case 'O': | |
prop.p_type = AMF_OBJECT; | |
break; | |
+ case 'Z': | |
+ prop.p_type = AMF_NULL; | |
+ break; | |
default: | |
return -1; | |
} | |
@@ -722,7 +759,7 @@ int RTMP_SetOpt(RTMP *r, const AVal *opt, AVal *arg) | |
*aptr = *arg; } | |
break; | |
case OPT_INT: { | |
- long l = strtol(arg->av_val, NULL, 0); | |
+ long l = strtol(arg->av_val, NULL, 10); | |
*(int *)v = l; } | |
break; | |
case OPT_BOOL: { | |
@@ -767,7 +804,7 @@ int RTMP_SetupURL(RTMP *r, char *url) | |
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'; | |
@@ -844,9 +881,16 @@ int RTMP_SetupURL(RTMP *r, char *url) | |
} | |
#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); | |
@@ -949,6 +993,8 @@ RTMP_Connect0(RTMP *r, struct sockaddr * service) | |
} | |
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; | |
} | |
@@ -1399,41 +1445,96 @@ ReadN(RTMP *r, char *buffer, int n) | |
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; | |
@@ -1460,10 +1561,11 @@ ReadN(RTMP *r, char *buffer, int n) | |
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_nBytesIn > 0xF0000000) | |
+ r->m_nBytesIn -= 0xF0000000; | |
+ 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 | |
@@ -1474,7 +1576,8 @@ ReadN(RTMP *r, char *buffer, int n) | |
{ | |
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; | |
} | |
@@ -1499,6 +1602,7 @@ static int | |
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]; | |
@@ -1514,6 +1618,15 @@ WriteN(RTMP *r, const char *buffer, int n) | |
} | |
#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; | |
@@ -1550,6 +1663,14 @@ WriteN(RTMP *r, const char *buffer, int n) | |
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; | |
} | |
@@ -1579,6 +1700,9 @@ SendConnectPacket(RTMP *r, RTMPPacket *cp) | |
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); | |
@@ -1627,7 +1751,7 @@ SendConnectPacket(RTMP *r, RTMPPacket *cp) | |
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); | |
@@ -1791,7 +1915,7 @@ SendUsherToken(RTMP *r, AVal *usherToken) | |
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); | |
@@ -1934,6 +2058,26 @@ SendPublish(RTMP *r) | |
return RTMP_SendPacket(r, &packet, TRUE); | |
} | |
+static int | |
+SendDynamicPublish(RTMP *r, double publishId) | |
+{ | |
+ char pbuf[1024], *pend = pbuf + sizeof (pbuf), *enc; | |
+ AVal av_command, av_publishId; | |
+ | |
+ enc = pbuf; | |
+ enc = AMF_EncodeString(enc, pend, &av_publish); | |
+ enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); | |
+ *enc++ = AMF_NULL; | |
+ av_publishId.av_val = malloc(128 * sizeof (char)); | |
+ av_publishId.av_len = sprintf(av_publishId.av_val, "%.0f", publishId); | |
+ enc = AMF_EncodeString(enc, pend, &av_publishId); | |
+ enc = AMF_EncodeString(enc, pend, &av_live); | |
+ av_command.av_val = pbuf; | |
+ av_command.av_len = enc - pbuf; | |
+ | |
+ return SendInvoke(r, &av_command, FALSE); | |
+} | |
+ | |
SAVC(deleteStream); | |
static int | |
@@ -2097,6 +2241,7 @@ SendBytesReceived(RTMP *r) | |
} | |
SAVC(_checkbw); | |
+SAVC(checkBandwidth); | |
static int | |
SendCheckBW(RTMP *r) | |
@@ -2114,7 +2259,7 @@ SendCheckBW(RTMP *r) | |
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; | |
@@ -2221,10 +2366,8 @@ SendPlay(RTMP *r) | |
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; | |
@@ -2340,7 +2483,7 @@ RTMP_SendCtrl(RTMP *r, short nType, unsigned int nObject, unsigned int nTime) | |
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; | |
@@ -2372,8 +2515,8 @@ RTMP_SendCtrl(RTMP *r, short nType, unsigned int nObject, unsigned int nTime) | |
} | |
else if (nType == 0x1A) | |
{ | |
- *buf = nObject & 0xff; | |
- } | |
+ *buf = nObject & 0xff; | |
+ } | |
else | |
{ | |
if (nSize > 2) | |
@@ -2873,6 +3016,7 @@ PublisherAuth(RTMP *r, AVal *description) | |
#endif | |
+SAVC(onBWCheck); | |
SAVC(onBWDone); | |
SAVC(onFCSubscribe); | |
SAVC(onFCUnsubscribe); | |
@@ -2885,24 +3029,25 @@ SAVC(level); | |
SAVC(description); | |
SAVC(onStatus); | |
SAVC(playlist_ready); | |
+SAVC(cps); | |
+SAVC(disneyToken); | |
+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 | |
@@ -2912,6 +3057,11 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) | |
AVal method; | |
double txn; | |
int ret = 0, nRes; | |
+ char pbuf[512], *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", | |
@@ -2952,7 +3102,14 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) | |
RTMP_Log(RTMP_LOGDEBUG, "%s, received result for method call <%s>", __FUNCTION__, | |
methodInvoked.av_val); | |
- if (AVMATCH(&methodInvoked, &av_connect)) | |
+ if ((r->Link.dynamicPublish == TRUE) && AVMATCH(&methodInvoked, &r->Link.dynamicCommand)) | |
+ { | |
+ r->Link.dynamicPublish = FALSE; | |
+ r->Link.publishId = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3)); | |
+ RTMP_Log(RTMP_LOGDEBUG, "server returned dynamic publish id: %.0f", r->Link.publishId); | |
+ RTMP_SendCreateStream(r); | |
+ } | |
+ else if (AVMATCH(&methodInvoked, &av_connect)) | |
{ | |
if (r->Link.token.av_len) | |
{ | |
@@ -2973,46 +3130,360 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) | |
RTMP_SendServerBW(r); | |
RTMP_SendCtrl(r, 3, 0, 300); | |
} | |
- RTMP_SendCreateStream(r); | |
+ if (r->Link.ccomm.av_len) | |
+ { | |
+ param_count = strsplit(r->Link.ccomm.av_val, FALSE, ';', ¶ms); | |
+ if ((param_count > 1) && (strcasecmp(params[1], "TRUE") == 0)) | |
+ SendCommand(r, params[0], TRUE); | |
+ else | |
+ SendCommand(r, params[0], FALSE); | |
+ if ((param_count > 2) && (strcasecmp(params[2], "TRUE") == 0)) | |
+ { | |
+ RTMP_Log(RTMP_LOGDEBUG, "overriding inbuilt dynamic publish command with -K (ccommand) switch"); | |
+ r->Link.dynamicPublish = TRUE; | |
+ r->Link.dynamicCommand.av_val = params[0]; | |
+ r->Link.dynamicCommand.av_len = strlen(params[0]); | |
+ } | |
+ else | |
+ { | |
+ RTMP_Log(RTMP_LOGDEBUG, "overriding inbuilt site specific authentication with -K (ccommand) switch"); | |
+ r->Link.dynamicPublish = FALSE; | |
+ RTMP_SendCreateStream(r); | |
+ } | |
+ } | |
+ else if (strstr(host, "3dbuzz.com") || strstr(pageUrl, "3dbuzz.com")) | |
+ { | |
+ AVal r1, r3; | |
+ AVal av_r1 = AVC("r1"); | |
+ AVal av_r3 = AVC("r3"); | |
+ AVal r1_key = AVC("4V?c6k7Y`(6~rMjp6S6!xT04]8m$g2"); | |
+ AVal r3_key = AVC("aB`d^+8?9;36]Lw2#rg?PDMcX?lCw2"); | |
+ TransformRot13(&obj, &av_r1, &r1); | |
+ TransformRot13(&obj, &av_r3, &r3); | |
+ if (r1.av_val && r3.av_val) | |
+ { | |
+ AVal av_qq = AVC("qq"); | |
+ AVal av_tos = AVC("http://www.3dbuzz.com/home/tos"); | |
+ AVal av_warning = AVC("Stream capturing is a violation of our terms, and may result in immediate cancellation of your account without refund"); | |
+ AVal r1_response; | |
+ | |
+ RTMP_Log(RTMP_LOGDEBUG, "3DBuzz SecureToken r1 request - %.*s", r1.av_len, r1.av_val); | |
+ RTMP_Log(RTMP_LOGDEBUG, "3DBuzz SecureToken r3 request - %.*s", r3.av_len, r3.av_val); | |
+ DecodeTEA(&r1_key, &r1); | |
+ DecodeTEA(&r3_key, &r3); | |
+ r1_response = TeaEncrypt(&av_tos, &r1); | |
+ RTMP_Log(RTMP_LOGDEBUG, "3DBuzz SecureToken r1 response - %.*s", r1_response.av_len, r1_response.av_val); | |
+ RTMP_Log(RTMP_LOGDEBUG, "3DBuzz SecureToken r3 response - %.*s", r3.av_len, r3.av_val); | |
+ | |
+ enc = pbuf; | |
+ enc = AMF_EncodeString(enc, pend, &av_qq); | |
+ enc = AMF_EncodeNumber(enc, pend, 0); | |
+ *enc++ = AMF_NULL; | |
+ enc = AMF_EncodeString(enc, pend, &r3); | |
+ enc = AMF_EncodeString(enc, pend, &av_tos); | |
+ enc = AMF_EncodeString(enc, pend, &r1_response); | |
+ enc = AMF_EncodeString(enc, pend, &av_warning); | |
+ av_Command.av_val = pbuf; | |
+ av_Command.av_len = enc - pbuf; | |
+ SendInvoke(r, &av_Command, FALSE); | |
+ } | |
- 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); | |
- } | |
- } | |
+ RTMP_SendCreateStream(r); | |
+ } | |
+ else if (strstr(pageUrl, "cam4")) | |
+ { | |
+ AMFObject obj2, response; | |
+ AMFObjectProperty p; | |
+ AVal Host, ID, IP, av_ChallengeResponse; | |
+ AVal av_receiveRTMPResponse = AVC("receiveRTMPResponse"); | |
+ AVal av_client = AVC("client"); | |
+ AVal av_result = AVC("result"); | |
+ char ChallengeResponse[16] = {0}; | |
+ SAVC(application); | |
+ SAVC(Host); | |
+ SAVC(ID); | |
+ SAVC(IP); | |
+ | |
+ AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &obj2); | |
+ if (RTMP_FindFirstMatchingProperty(&obj2, &av_application, &p)) | |
+ { | |
+ RTMP_Log(RTMP_LOGDEBUG, "sending cam4 authentication"); | |
+ AMFProp_GetObject(&p, &obj2); | |
+ RTMP_FindFirstMatchingProperty(&obj2, &av_Host, &p); | |
+ AMFProp_GetString(&p, &Host); | |
+ RTMP_FindFirstMatchingProperty(&obj2, &av_ID, &p); | |
+ AMFProp_GetString(&p, &ID); | |
+ RTMP_FindFirstMatchingProperty(&obj2, &av_IP, &p); | |
+ AMFProp_GetString(&p, &IP); | |
+ RTMP_Log(RTMP_LOGDEBUG, "Cam4 Host: %.*s", Host.av_len, Host.av_val); | |
+ RTMP_Log(RTMP_LOGDEBUG, "Cam4 ID : %.*s", ID.av_len, ID.av_val); | |
+ RTMP_Log(RTMP_LOGDEBUG, "Cam4 IP : %.*s", IP.av_len, IP.av_val); | |
+ snprintf(ChallengeResponse, 15, "%d", Host.av_len + ID.av_len + IP.av_len); | |
+ av_ChallengeResponse.av_val = ChallengeResponse; | |
+ av_ChallengeResponse.av_len = strlen(av_ChallengeResponse.av_val); | |
+ AMFProp_SetName(&p, &av_client); | |
+ AMFProp_SetString(&p, &ID); | |
+ AMF_AddProp(&response, &p); | |
+ AMFProp_SetName(&p, &av_result); | |
+ AMFProp_SetString(&p, &av_ChallengeResponse); | |
+ AMF_AddProp(&response, &p); | |
+ | |
+ enc = pbuf; | |
+ enc = AMF_EncodeString(enc, pend, &av_receiveRTMPResponse); | |
+ enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); | |
+ *enc++ = AMF_NULL; | |
+ enc = AMF_Encode(&response, enc, pend); | |
+ enc = AMF_EncodeBoolean(enc, pend, TRUE); | |
+ 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, 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); | |
+ } | |
+ } | |
+ else if (strstr(host, "featve.com") || strstr(pageUrl, "featve.com")) | |
+ { | |
+ AVal av_auth = AVC("yes"); | |
+ SAVC(youCannotPlayMe); | |
+ | |
+ 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, "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 (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 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, "pc3oot.us.to")) | |
+ { | |
+ SendCommand(r, "UIUIUINASOWAS", TRUE); | |
+ SendGetStreamLength(r); | |
+ 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(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, "iUsteJaSakamCarevataKerka", TRUE); | |
+ RTMP_SendCreateStream(r); | |
+ } | |
+ else if (strstr(pageUrl, "janjua.tv")) | |
+ { | |
+ SendCommand(r, "soLagaDaSeStoriAga", TRUE); | |
+ RTMP_SendCreateStream(r); | |
+ } | |
+ else if (strstr(pageUrl, "liveflash.tv")) | |
+ { | |
+ char *command = "kaskatijaEkonomista"; | |
+ r->Link.dynamicPublish = TRUE; | |
+ r->Link.dynamicCommand.av_val = command; | |
+ r->Link.dynamicCommand.av_len = strlen(command); | |
+ SendCommand(r, command, TRUE); | |
+ } | |
+ else if (strstr(pageUrl, "mips.tv") || strstr(pageUrl, "mipsplayer.com")) | |
+ { | |
+ char *command = "gaolVanusPobeleVoKosata"; | |
+ r->Link.dynamicPublish = TRUE; | |
+ r->Link.dynamicCommand.av_val = command; | |
+ r->Link.dynamicCommand.av_len = strlen(command); | |
+ SendCommand(r, command, TRUE); | |
+ } | |
+ else if (strstr(pageUrl, "streamify.tv")) | |
+ { | |
+ SendCommand(r, "keGoVidishStambolSoseBardovci", TRUE); | |
+ RTMP_SendCreateStream(r); | |
+ } | |
+ else if (strstr(pageUrl, "ucaster.eu")) | |
+ { | |
+ SendCommand(r, "vujkoMiLazarBarakovOdMonospitovo", 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(pageUrl, "zenex.tv")) | |
+ { | |
+ SendCommand(r, "goVideStambolSoseBardovci", TRUE); | |
+ RTMP_SendCreateStream(r); | |
+ } | |
+ 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); | |
+ if (r->Link.publishId > 0) | |
+ { | |
+ RTMP_Log(RTMP_LOGDEBUG, "sending dynamic publish id: %.0f", r->Link.publishId); | |
+ SendDynamicPublish(r, r->Link.publishId); | |
+ } | |
+ /* 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)) | |
@@ -3036,21 +3507,22 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) | |
{ | |
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; i<r->m_numCalls; i++) | |
+ for (i = 0; i < r->m_numCalls; i++) | |
{ | |
if (r->m_methodCalls[i].num == txn) | |
{ | |
@@ -3062,12 +3534,12 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) | |
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)) | |
{ | |
@@ -3086,34 +3558,96 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) | |
goto leave; | |
} | |
} | |
- } | |
- 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; | |
+ if (r->Link.lFlags & RTMP_LF_FTCU) | |
+ r->Link.lFlags ^= RTMP_LF_FTCU; | |
+ 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"); | |
+ if (r->Link.port == 0) | |
+ { | |
+ if (r->Link.protocol & RTMP_FEATURE_SSL) | |
+ r->Link.port = 443; | |
+ else if (r->Link.protocol & RTMP_FEATURE_HTTP) | |
+ r->Link.port = 80; | |
+ else | |
+ r->Link.port = 1935; | |
+ } | |
+ RTMP_Connect(r, NULL); | |
+ } | |
+ else | |
+ { | |
+ | |
+ RTMP_Log(RTMP_LOGERROR, "rtmp server requested close"); | |
+ if (r->m_bPlaying && (strstr(pageUrl, "streamlive.to") || strstr(pageUrl, "uk-iptv.co.uk"))) | |
+ RTMP_Log(RTMP_LOGINFO, "ignoring close request"); | |
+ else | |
+ RTMP_Close(r); | |
+ } | |
} | |
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); | |
@@ -3171,6 +3705,46 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) | |
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)) | |
{ | |
@@ -3184,6 +3758,109 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) | |
} | |
} | |
} | |
+ else if (AVMATCH(&method, &av_cps)) | |
+ { | |
+ if (obj.o_num >= 4) | |
+ { | |
+ 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 | |
+ { | |
+ if (obj.o_num >= 7) | |
+ { | |
+ 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 if (AVMATCH(&method, &av_disneyToken)) | |
+ { | |
+ double FirstNumber = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3)); | |
+ double SecondNumber = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 4)); | |
+ RTMP_Log(RTMP_LOGDEBUG, "FirstNumber: %.2f, SecondNumber: %.2f", FirstNumber, SecondNumber); | |
+ | |
+ enc = pbuf; | |
+ enc = AMF_EncodeString(enc, pend, &av__result); | |
+ enc = AMF_EncodeNumber(enc, pend, txn); | |
+ *enc++ = AMF_NULL; | |
+ enc = AMF_EncodeNumber(enc, pend, FirstNumber * SecondNumber); | |
+ 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_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 | |
{ | |
@@ -3209,7 +3886,8 @@ RTMP_FindFirstMatchingProperty(AMFObject *obj, const AVal *name, | |
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; | |
@@ -3235,7 +3913,8 @@ RTMP_FindPrefixProperty(AMFObject *obj, const AVal *name, | |
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; | |
@@ -3269,6 +3948,7 @@ DumpMetaData(AMFObject *obj) | |
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); | |
@@ -3284,7 +3964,7 @@ DumpMetaData(AMFObject *obj) | |
} | |
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); | |
} | |
} | |
@@ -3366,7 +4046,7 @@ HandleCtrl(RTMP *r, const RTMPPacket *packet) | |
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); */ | |
@@ -3475,15 +4155,15 @@ HandleCtrl(RTMP *r, const RTMPPacket *packet) | |
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); | |
} | |
@@ -3788,8 +4468,18 @@ HandShake(RTMP *r, int FP9HandShake) | |
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; | |
@@ -3942,7 +4632,7 @@ RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue) | |
nSize = packetSize[packet->m_headerType]; | |
hSize = nSize; cSize = 0; | |
- t = packet->m_nTimeStamp - last; | |
+ t = packet->m_nTimeStamp ? packet->m_nTimeStamp - last : 0; | |
if (packet->m_body) | |
{ | |
@@ -4251,8 +4941,13 @@ RTMPSockBuf_Fill(RTMPSockBuf *sb) | |
{ | |
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) | |
{ | |
@@ -4266,8 +4961,10 @@ RTMPSockBuf_Fill(RTMPSockBuf *sb) | |
#endif | |
{ | |
nBytes = recv(sb->sb_socket, sb->sb_start + sb->sb_size, nBytes, 0); | |
- } | |
- if (nBytes != -1) | |
+ if (!nBytes) | |
+ RTMP_Log(RTMP_LOGDEBUG, "Socket closed by server, nBytes: %d", nBytes); | |
+ } | |
+ if (nBytes >= 0) | |
{ | |
sb->sb_size += nBytes; | |
} | |
@@ -4405,21 +5102,19 @@ static int | |
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; | |
} | |
@@ -4429,22 +5124,17 @@ HTTP_read(RTMP *r, int fill) | |
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-"))) { | |
@@ -4452,21 +5142,31 @@ restart: | |
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) | |
{ | |
@@ -4486,10 +5186,17 @@ restart: | |
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 | |
@@ -4557,162 +5264,156 @@ Read_1_Packet(RTMP *r, char *buf, unsigned int buflen) | |
#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); | |
+ | |
+ 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; | |
+ } | |
+ } | |
- /* 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; | |
- } | |
- } | |
+ /* 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); | |
+ /* 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); | |
+ 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; | |
- } | |
- } | |
- } | |
- } | |
+ /* 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))) | |
@@ -4972,7 +5673,7 @@ static const char flvHeader[] = { 'F', 'L', 'V', 0x01, | |
0x00, 0x00, 0x00, 0x00 | |
}; | |
-#define HEADERBUF (128*1024) | |
+#define HEADERBUF (1024*1024) | |
int | |
RTMP_Read(RTMP *r, char *buf, int size) | |
{ | |
@@ -5175,3 +5876,395 @@ RTMP_Write(RTMP *r, const char *buf, int size) | |
} | |
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; | |
+} | |
+ | |
+void | |
+TransformRot13(AMFObject *obj, AVal *rindex, AVal *r) | |
+{ | |
+ char *chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMabcdefghijklmnopqrstuvwxyzabcdefghijklm"; | |
+ int i = 0, pos = 0; | |
+ AMFObject obj2; | |
+ | |
+ AMFProp_GetObject(AMF_GetProp(obj, NULL, 3), &obj2); | |
+ AMFProp_GetString(AMF_GetProp(&obj2, rindex, -1), r); | |
+ | |
+ for (i = 0; i < r->av_len; i++) | |
+ { | |
+ char *chr = &r->av_val[i]; | |
+ chr = strchr(chars, *chr); | |
+ pos = chr ? chr - chars : -1; | |
+ if (pos > -1) | |
+ r->av_val[i] = chars[pos + 13]; | |
+ } | |
+} | |
+ | |
+void | |
+__TeaCrypt(uint32_t *block, uint32_t len, uint32_t *key) | |
+{ | |
+ uint32_t z = block[len - 1], y = block[0], sum = 0, e, DELTA = 0x9e3779b9; | |
+ int32_t p, q; | |
+ | |
+ q = 6 + 52 / len; | |
+ while (q-- > 0) | |
+ { | |
+ sum += DELTA; | |
+ e = (sum >> 2) & 3; | |
+ for (p = 0; p < len - 1; p++) | |
+ { | |
+ y = block[p + 1]; | |
+ block[p] += ((z >> 5^y << 2) + (y >> 3^z << 4)) ^ ((sum^y) + (key[(p & 3)^e] ^ z)); | |
+ z = block[p]; | |
+ } | |
+ y = block[0]; | |
+ block[len - 1] += ((z >> 5^y << 2) + (y >> 3^z << 4)) ^ ((sum^y) + (key[(p & 3)^e] ^ z)); | |
+ z = block[len - 1]; | |
+ } | |
+} | |
+ | |
+AVal | |
+TeaEncrypt(AVal *srcData, AVal *srcKey) | |
+{ | |
+ int i, reqPadding, longKeyBlocks, longDataBlocks; | |
+ unsigned char *key, *data; | |
+ | |
+ // Prepare key | |
+ int srcKeyLen = srcKey->av_len; | |
+ int reqKeyLen = 16; | |
+ reqPadding = reqKeyLen - srcKeyLen; | |
+ if (reqPadding < 0) | |
+ { | |
+ reqPadding = 0; | |
+ srcKeyLen = reqKeyLen; | |
+ } | |
+ key = calloc((srcKeyLen + reqPadding + 1), sizeof (char)); | |
+ memcpy(key, srcKey->av_val, srcKeyLen); | |
+ longKeyBlocks = reqKeyLen / 4; | |
+ uint32_t *longKeyBuf = (uint32_t *) malloc(longKeyBlocks * sizeof (uint32_t)); | |
+ for (i = 0; i < longKeyBlocks; i++) | |
+ { | |
+ longKeyBuf[i] = 0; | |
+ longKeyBuf[i] |= (key[i * 4 + 0]) | (key[i * 4 + 1] << 8) | (key[i * 4 + 2] << 16) | (key[i * 4 + 3] << 24); | |
+ } | |
+ | |
+ // Prepare data | |
+ int srcDataLen = srcData->av_len; | |
+ reqPadding = ((int) ((srcDataLen + 3) / 4))*4 - srcDataLen; | |
+ if ((srcDataLen + reqPadding) < 8) | |
+ reqPadding = 8 - srcDataLen; | |
+ data = calloc((srcDataLen + reqPadding + 1), sizeof (char)); | |
+ memcpy(data, srcData->av_val, srcDataLen); | |
+ longDataBlocks = (srcDataLen + reqPadding) / 4; | |
+ uint32_t *longDataBuf = malloc(longDataBlocks * sizeof (uint32_t)); | |
+ for (i = 0; i < longDataBlocks; i++) | |
+ { | |
+ longDataBuf[i] = 0; | |
+ longDataBuf[i] |= (data[i * 4 + 0]) | (data[i * 4 + 1] << 8) | (data[i * 4 + 2] << 16) | (data[i * 4 + 3] << 24); | |
+ } | |
+ | |
+ // Encrypt data | |
+ __TeaCrypt(longDataBuf, longDataBlocks, longKeyBuf); | |
+ | |
+ // Convert data back to char array | |
+ for (i = 0; i < longDataBlocks; i++) | |
+ { | |
+ data[i * 4 + 0] = longDataBuf[i] & 0xFF; | |
+ data[i * 4 + 1] = (longDataBuf[i] >> 8) & 0xFF; | |
+ data[i * 4 + 2] = (longDataBuf[i] >> 16) & 0xFF; | |
+ data[i * 4 + 3] = (longDataBuf[i] >> 24) & 0xFF; | |
+ } | |
+ | |
+ // Convert to hex string | |
+ AVal hexData; | |
+ hexData.av_val = calloc((longDataBlocks * 4 * 2) + 1, sizeof (char)); | |
+ for (i = 0; i < (longDataBlocks * 4); i++) | |
+ sprintf(&hexData.av_val[i * 2], "%.2X", data[i]); | |
+ hexData.av_len = strlen(hexData.av_val); | |
+ | |
+ // Free allocated resources | |
+ free(key); | |
+ free(longKeyBuf); | |
+ free(data); | |
+ free(longDataBuf); | |
+ | |
+ return hexData; | |
+} | |
diff --git librtmp/rtmp.h librtmp/rtmp.h | |
index 0248913..3e573da 100644 | |
--- librtmp/rtmp.h | |
+++ librtmp/rtmp.h | |
@@ -150,12 +150,15 @@ extern "C" | |
AVal playpath; /* passed in explicitly */ | |
AVal tcUrl; | |
AVal swfUrl; | |
+ AVal swfHash; | |
AVal pageUrl; | |
AVal app; | |
AVal auth; | |
AVal flashVer; | |
AVal subscribepath; | |
+ AVal ccomm; | |
AVal usherToken; | |
+ AVal WeebToken; | |
AVal token; | |
AVal pubUser; | |
AVal pubPasswd; | |
@@ -175,9 +178,18 @@ extern "C" | |
int lFlags; | |
int swfAge; | |
+ int swfSize; | |
int protocol; | |
+ int ConnectPacket; | |
+ int CombineConnectPacket; | |
+ int redirected; | |
int timeout; /* connection timeout in seconds */ | |
+ int dynamicPublish; | |
+ AVal dynamicCommand; | |
+ AVal Extras; | |
+ AVal HandshakeResponse; | |
+ double publishId; | |
int pFlags; /* unused, but kept to avoid breaking ABI */ | |
@@ -220,6 +232,7 @@ extern "C" | |
/* if bResume == TRUE */ | |
uint8_t initialFrameType; | |
uint32_t nResumeTS; | |
+ uint32_t nResumeDriftTS; | |
char *metaHeader; | |
char *initialFrame; | |
uint32_t nMetaHeaderSize; | |
@@ -306,6 +319,8 @@ extern "C" | |
AVal *flashVer, | |
AVal *subscribepath, | |
AVal *usherToken, | |
+ AVal *WeebToken, | |
+ AVal *ccomm, | |
int dStart, | |
int dStop, int bLiveStream, long int timeout); | |
@@ -371,6 +386,11 @@ extern "C" | |
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 --git librtmp/rtmp_sys.h librtmp/rtmp_sys.h | |
index 85d7e53..b2a3438 100644 | |
--- librtmp/rtmp_sys.h | |
+++ librtmp/rtmp_sys.h | |
@@ -65,6 +65,7 @@ | |
#include <polarssl/net.h> | |
#include <polarssl/ssl.h> | |
#include <polarssl/havege.h> | |
+#include <polarssl/md5.h> | |
#if POLARSSL_VERSION_NUMBER < 0x01010000 | |
#define havege_random havege_rand | |
#endif | |
@@ -105,6 +106,7 @@ typedef struct tls_server_ctx { | |
#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 <gnutls/gnutls.h> | |
@@ -122,6 +124,8 @@ typedef struct tls_ctx { | |
#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 @@ typedef struct tls_ctx { | |
#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 --git rtmpdump.c rtmpdump.c | |
index 13741a7..b3ae33f 100644 | |
--- rtmpdump.c | |
+++ rtmpdump.c | |
@@ -36,6 +36,9 @@ | |
#ifdef WIN32 | |
#define fseeko fseeko64 | |
#define ftello ftello64 | |
+#ifdef __MINGW32__ | |
+#define off_t off64_t | |
+#endif | |
#include <io.h> | |
#include <fcntl.h> | |
#define SET_BINMODE(f) setmode(fileno(f), O_BINARY) | |
@@ -67,7 +70,7 @@ InitSockets() | |
#endif | |
} | |
-inline void | |
+static inline void | |
CleanupSockets() | |
{ | |
#ifdef WIN32 | |
@@ -148,9 +151,9 @@ OpenResumeFile(const char *flvFile, // file name [in] | |
if (!*file) | |
return RD_SUCCESS; // RD_SUCCESS, because we go to fresh file mode instead of quiting | |
- fseek(*file, 0, SEEK_END); | |
+ fseeko(*file, 0, SEEK_END); | |
*size = ftello(*file); | |
- fseek(*file, 0, SEEK_SET); | |
+ fseeko(*file, 0, SEEK_SET); | |
if (*size > 0) | |
{ | |
@@ -178,7 +181,7 @@ OpenResumeFile(const char *flvFile, // file name [in] | |
} | |
uint32_t dataOffset = AMF_DecodeInt32(hbuf + 5); | |
- fseek(*file, dataOffset, SEEK_SET); | |
+ fseeko(*file, dataOffset, SEEK_SET); | |
if (fread(hbuf, 1, 4, *file) != 4) | |
{ | |
@@ -283,18 +286,24 @@ GetLastKeyframe(FILE * file, // output file [in] | |
uint8_t dataType; | |
int bAudioOnly; | |
off_t size; | |
+ char *syncbuf, *p; | |
- fseek(file, 0, SEEK_END); | |
+ fseeko(file, 0, SEEK_END); | |
size = ftello(file); | |
+ if (size <= 0) | |
+ { | |
+ dSeek = 0; | |
+ return RD_SUCCESS; | |
+ } | |
- fseek(file, 4, SEEK_SET); | |
+ fseeko(file, 4, SEEK_SET); | |
if (fread(&dataType, sizeof(uint8_t), 1, file) != 1) | |
return RD_FAILED; | |
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 +335,51 @@ GetLastKeyframe(FILE * file, // output file [in] | |
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!"); | |
@@ -703,8 +757,12 @@ void usage(char *prog) | |
RTMP_LogPrintf | |
("--token|-T key Key for SecureToken response\n"); | |
RTMP_LogPrintf | |
+ ("--ccommand|-K key Send custom command before play\n"); | |
+ 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 +809,9 @@ main(int argc, char **argv) | |
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 | |
+ AVal ccomm = { 0, 0 }; | |
int port = -1; | |
int protocol = RTMP_PROTOCOL_UNDEFINED; | |
int retries = 0; | |
@@ -853,17 +913,19 @@ main(int argc, char **argv) | |
{"start", 1, NULL, 'A'}, | |
{"stop", 1, NULL, 'B'}, | |
{"token", 1, NULL, 'T'}, | |
+ {"ccommand", 1, NULL, 'K'}, | |
{"hashes", 0, NULL, '#'}, | |
{"debug", 0, NULL, 'z'}, | |
{"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:K:w:x:W:X:S:#j:J:", | |
longopts, NULL)) != -1) | |
{ | |
switch (opt) | |
@@ -995,7 +1057,7 @@ main(int argc, char **argv) | |
port = parsedPort; | |
if (playpath.av_len == 0 && parsedPlaypath.av_len) | |
{ | |
- playpath = parsedPlaypath; | |
+ playpath = AVcopy(parsedPlaypath); | |
} | |
if (protocol == RTMP_PROTOCOL_UNDEFINED) | |
protocol = parsedProtocol; | |
@@ -1061,6 +1123,9 @@ main(int argc, char **argv) | |
RTMP_SetOpt(&rtmp, &av_token, &token); | |
} | |
break; | |
+ case 'K': | |
+ STR2AVAL(ccomm, optarg); | |
+ break; | |
case '#': | |
bHashes = TRUE; | |
break; | |
@@ -1079,6 +1144,9 @@ main(int argc, char **argv) | |
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 +1238,14 @@ main(int argc, char **argv) | |
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 +1265,9 @@ main(int argc, char **argv) | |
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, &ccomm, | |
+ dSeek, dStopOffset, bLiveStream, timeout); | |
} | |
else | |
{ | |
diff --git rtmpgw.c rtmpgw.c | |
index 3e47602..e56b855 100644 | |
--- rtmpgw.c | |
+++ rtmpgw.c | |
@@ -96,7 +96,9 @@ typedef struct | |
AVal flashVer; | |
AVal token; | |
AVal subscribepath; | |
- AVal usherToken; //Justin.tv auth token | |
+ AVal ccomm; | |
+ AVal usherToken; // Justin.tv auth token | |
+ AVal WeebToken; // Weeb.tv auth token | |
AVal sockshost; | |
AMFObject extras; | |
int edepth; | |
@@ -556,8 +558,8 @@ void processTCPrequest(STREAMING_SERVER * server, // server socket and state (ou | |
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, &req.ccomm, dSeek, req.dStopOffset, req.bLiveStream, req.timeout); | |
} | |
else | |
{ | |
@@ -972,6 +974,12 @@ ParseOption(char opt, char *arg, RTMP_REQUEST * req) | |
case 'j': | |
STR2AVAL(req->usherToken, arg); | |
break; | |
+ case 'J': | |
+ STR2AVAL(req->WeebToken, arg); | |
+ break; | |
+ case 'K': | |
+ STR2AVAL(req->ccomm, arg); | |
+ break; | |
default: | |
RTMP_LogPrintf("unknown option: %c, arg: %s\n", opt, arg); | |
return FALSE; | |
@@ -1044,6 +1052,8 @@ main(int argc, char **argv) | |
{"quiet", 0, NULL, 'q'}, | |
{"verbose", 0, NULL, 'V'}, | |
{"jtv", 1, NULL, 'j'}, | |
+ {"weeb", 1, NULL, 'J'}, | |
+ {"ccommand", 1, NULL, 'K'}, | |
{0, 0, 0, 0} | |
}; | |
@@ -1056,7 +1066,7 @@ main(int argc, char **argv) | |
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) | |
@@ -1119,8 +1129,12 @@ main(int argc, char **argv) | |
RTMP_LogPrintf | |
("--token|-T key Key for SecureToken response\n"); | |
RTMP_LogPrintf | |
+ ("--ccommand|-K key Send custom command before play\n"); | |
+ 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 --git rtmpsrv.c rtmpsrv.c | |
index 5df4d3a..eccaa9c 100644 | |
--- rtmpsrv.c | |
+++ rtmpsrv.c | |
@@ -25,9 +25,13 @@ | |
*/ | |
#include <stdlib.h> | |
+#ifdef __MINGW_H | |
+#include <unistd.h> | |
+#endif | |
#include <string.h> | |
#include <math.h> | |
#include <limits.h> | |
+#include <time.h> | |
#include <signal.h> | |
#include <getopt.h> | |
@@ -94,12 +98,19 @@ typedef struct | |
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(level); | |
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 @@ SendConnectResult(RTMP *r, double txn) | |
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 @@ SendConnectResult(RTMP *r, double txn) | |
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 @@ static int | |
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 @@ static const AVal av_Stopped_playing = AVC("Stopped playing"); | |
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 @@ static int | |
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 @@ SendPlayStop(RTMP *r) | |
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 @@ countAMF(AMFObject *obj, int *argc) | |
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; | |
@@ -407,9 +504,11 @@ dumpAMF(AMFObject *obj, char *ptr, AVal *argv, int *argc) | |
int i, ac = *argc; | |
const char opt[] = "NBSO Z"; | |
- for (i=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 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int | |
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 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int | |
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; | |
@@ -602,6 +718,17 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int | |
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 + | |
@@ -619,6 +746,7 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int | |
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; | |
@@ -643,6 +771,7 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int | |
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; | |
} | |
@@ -665,10 +794,17 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int | |
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; | |
@@ -676,7 +812,13 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int | |
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) | |
@@ -710,25 +852,82 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int | |
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"); | |
+ mkstemp(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)) | |
{ | |
@@ -742,7 +941,21 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int | |
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); | |
@@ -861,12 +1074,18 @@ controlServerThread(void *unused) | |
{ | |
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(); | |
} | |
@@ -1054,7 +1273,6 @@ stopStreaming(STREAMING_SERVER * server) | |
} | |
} | |
- | |
void | |
sigIntHandler(int sig) | |
{ | |
@@ -1191,3 +1409,15 @@ AVreplace(AVal *src, const AVal *orig, const AVal *repl) | |
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 --git rtmpsuck.c rtmpsuck.c | |
index e886179..0abdba4 100644 | |
--- rtmpsuck.c | |
+++ rtmpsuck.c | |
@@ -25,10 +25,13 @@ | |
*/ | |
#include <stdlib.h> | |
+#ifdef __MINGW_H | |
+#include <unistd.h> | |
+#endif | |
#include <string.h> | |
#include <math.h> | |
#include <limits.h> | |
- | |
+#include <time.h> | |
#include <signal.h> | |
#include <getopt.h> | |
@@ -141,18 +144,21 @@ SAVC(code); | |
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 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b | |
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 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b | |
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 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b | |
} | |
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 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b | |
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 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b | |
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 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b | |
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 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b | |
/* 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"); | |
+ mkstemp(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 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b | |
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 @@ controlServerThread(void *unused) | |
{ | |
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(); | |
} | |
@@ -815,7 +960,7 @@ TFTYPE doServe(void *arg) // server socket and state (our listening socket) | |
if (select(n + 1, &rfds, NULL, NULL, &tv) <= 0) | |
{ | |
- if (server->f_cur && server->rc.m_mediaChannel && !paused) | |
+ if (server->f_cur && server->rc.m_mediaChannel && !paused && server->rc.m_channelTimestamp) | |
{ | |
server->rc.m_pauseStamp = server->rc.m_channelTimestamp[server->rc.m_mediaChannel]; | |
if (RTMP_ToggleStream(&server->rc)) | |
@@ -1123,7 +1268,6 @@ stopStreaming(STREAMING_SERVER * server) | |
} | |
} | |
- | |
void | |
sigIntHandler(int sig) | |
{ | |
@@ -1196,3 +1340,48 @@ main(int argc, char **argv) | |
#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 --git thread.c thread.c | |
index 0913c98..13d624a 100644 | |
--- thread.c | |
+++ thread.c | |
@@ -32,7 +32,7 @@ ThreadCreate(thrfunc *routine, void *args) | |
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; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment