diff --git a/lib/http_parser.cpp b/lib/http_parser.cpp index 165d5761..e1704330 100644 --- a/lib/http_parser.cpp +++ b/lib/http_parser.cpp @@ -4,6 +4,82 @@ #include "http_parser.h" #include "encode.h" #include "timing.h" +#include "defines.h" + +/// Helper function to check if the given c-string is numeric or not +static bool is_numeric(const char * str){ + while (str != 0){ + if (str[0] < 48 || str[0] > 57){return false;} + ++str; + } + return true; +} + +///Constructor that does the actual parsing +HTTP::URL::URL(const std::string & url){ + //first detect protocol at the start, if any + size_t proto_sep = url.find("://"); + if (proto_sep != std::string::npos){ + protocol = url.substr(0, proto_sep); + proto_sep += 3; + }else{ + proto_sep = 0; + } + //proto_sep now points to the start of the host, guaranteed + //continue by finding the path, if any + size_t first_slash = url.find('/', proto_sep); + if (first_slash != std::string::npos){ + path = url.substr(first_slash+1); + } + //host and port are now definitely between proto_sep and first_slash + //we check for [ at the start because we may have an IPv6 address as host + if (url[proto_sep] == '['){ + //IPv6 address - find matching brace + size_t closing_brace = url.find(']', proto_sep); + //check if it exists at all + if (closing_brace == std::string::npos || closing_brace > first_slash){ + //assume host ends at first slash if there is no closing brace before it + closing_brace = first_slash; + } + host = url.substr(proto_sep+1, closing_brace-(proto_sep+1)); + //continue by finding port, if any + size_t colon = url.rfind(':', first_slash); + if (colon == std::string::npos || colon <= closing_brace){ + //no port. Assume 80 + port = "80"; + }else{ + //we have a port number, read it + port = url.substr(colon+1, first_slash-(colon+1)); + } + }else{ + //"normal" host - first find port, if any + size_t colon = url.rfind(':', first_slash); + if (colon == std::string::npos || colon < proto_sep){ + //no port. Assume 80 + port = "80"; + host = url.substr(proto_sep, first_slash-proto_sep); + }else{ + //we have a port number, read it + port = url.substr(colon+1, first_slash-(colon+1)); + host = url.substr(proto_sep, colon-proto_sep); + } + } + //if the host is numeric, assume it is a port, instead + if (is_numeric(host.c_str())){ + port = host; + host = ""; + } + EXTREME_MSG("URL host: %s", host.c_str()); + EXTREME_MSG("URL protocol: %s", protocol.c_str()); + EXTREME_MSG("URL port: %s", port.c_str()); + EXTREME_MSG("URL path: %s", path.c_str()); +} + +///Returns the port in numeric format +uint32_t HTTP::URL::getPort() const{ + if (!port.size()){return 80;} + return atoi(port.c_str()); +} /// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing. /// All this constructor does is call HTTP::Parser::Clean(). @@ -447,7 +523,7 @@ bool HTTP::Parser::parse(std::string & HTTPbuffer) { tmpA.erase(0, f + 1); method = tmpA; if (url.find('?') != std::string::npos) { - parseVars(url.substr(url.find('?') + 1)); //parse GET variables + parseVars(url.substr(url.find('?') + 1), vars); //parse GET variables url.erase(url.find('?')); } url = Encodings::URL::decode(url); @@ -463,7 +539,7 @@ bool HTTP::Parser::parse(std::string & HTTPbuffer) { tmpA.erase(0, f + 1); protocol = tmpA; if (url.find('?') != std::string::npos) { - parseVars(url.substr(url.find('?') + 1)); //parse GET variables + parseVars(url.substr(url.find('?') + 1), vars); //parse GET variables url.erase(url.find('?')); } url = Encodings::URL::decode(url); @@ -508,7 +584,7 @@ bool HTTP::Parser::parse(std::string & HTTPbuffer) { HTTPbuffer.erase(0, toappend); } if (length == body.length()) { - parseVars(body); //parse POST variables + parseVars(body, vars); //parse POST variables return true; } else { return false; @@ -560,12 +636,12 @@ bool HTTP::Parser::parse(std::string & HTTPbuffer) { return false; //empty input } //HTTPReader::parse -/// Parses GET or POST-style variable data. -/// Saves to internal variable structure using HTTP::Parser::SetVar. -void HTTP::Parser::parseVars(std::string data) { +///HTTP variable parser to std::map structure. +///Reads variables from data, decodes and stores them to storage. +void HTTP::parseVars(const std::string & data, std::map & storage) { std::string varname; std::string varval; - // position where a part start (e.g. after &) + // position where a part starts (e.g. after &) size_t pos = 0; while (pos < data.length()) { size_t nextpos = data.find('&', pos); @@ -582,7 +658,9 @@ void HTTP::Parser::parseVars(std::string data) { varname = data.substr(pos, nextpos - pos); varval.clear(); } - SetVar(Encodings::URL::decode(varname), Encodings::URL::decode(varval)); + if (varname.size()){ + storage[Encodings::URL::decode(varname)] = Encodings::URL::decode(varval); + } if (nextpos == std::string::npos) { // in case the string is gigantic break; diff --git a/lib/http_parser.h b/lib/http_parser.h index 5a716af6..f186f4f3 100644 --- a/lib/http_parser.h +++ b/lib/http_parser.h @@ -10,6 +10,12 @@ /// Holds all HTTP processing related code. namespace HTTP { + + ///HTTP variable parser to std::map structure. + ///Reads variables from data, decodes and stores them to storage. + void parseVars(const std::string & data, std::map & storage); + + /// Simple class for reading and writing HTTP 1.0 and 1.1. class Parser { public: @@ -56,13 +62,22 @@ namespace HTTP { bool getChunks; unsigned int doingChunk; bool parse(std::string & HTTPbuffer); - void parseVars(std::string data); std::string builder; std::string read_buffer; std::map headers; std::map vars; void Trim(std::string & s); }; -//HTTP::Parser class + + ///URL parsing class. Parses full URL into its subcomponents + class URL { + public: + URL(const std::string & url); + uint32_t getPort() const; + std::string host;///< Hostname or IP address of URL + std::string protocol;///wait(); diff --git a/lib/shared_memory.h b/lib/shared_memory.h index bc0fe13c..21d0ed39 100644 --- a/lib/shared_memory.h +++ b/lib/shared_memory.h @@ -40,6 +40,7 @@ namespace IPC { char getSync(); void setSync(char s); unsigned int crc(); + uint32_t getPID(); private: ///\brief The payload for the stat exchange /// - 8 byte - now (timestamp of last statistics) diff --git a/lib/socket.cpp b/lib/socket.cpp index 2a22d0cb..bcc70be2 100644 --- a/lib/socket.cpp +++ b/lib/socket.cpp @@ -377,10 +377,6 @@ Socket::Connection::Connection(std::string host, int port, bool nonblock) { hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_ADDRCONFIG; - hints.ai_protocol = 0; - hints.ai_canonname = NULL; - hints.ai_addr = NULL; - hints.ai_next = NULL; int s = getaddrinfo(host.c_str(), ss.str().c_str(), &hints, &result); if (s != 0) { DEBUG_MSG(DLVL_FAIL, "Could not connect to %s:%i! Error: %s", host.c_str(), port, gai_strerror(s)); @@ -975,14 +971,14 @@ int Socket::Server::getSocket() { Socket::UDPConnection::UDPConnection(bool nonblock) { #ifdef __CYGWIN__ #warning UDP over IPv6 is currently disabled on windows - isIPv6 = false; + family = AF_INET; sock = socket(AF_INET, SOCK_DGRAM, 0); #else - isIPv6 = true; + family = AF_INET6; sock = socket(AF_INET6, SOCK_DGRAM, 0); if (sock == -1) { sock = socket(AF_INET, SOCK_DGRAM, 0); - isIPv6 = false; + family = AF_INET; } #endif if (sock == -1) { @@ -1005,14 +1001,14 @@ Socket::UDPConnection::UDPConnection(bool nonblock) { Socket::UDPConnection::UDPConnection(const UDPConnection & o) { #ifdef __CYGWIN__ #warning UDP over IPv6 is currently disabled on windows - isIPv6 = false; + family = AF_INET; sock = socket(AF_INET, SOCK_DGRAM, 0); #else - isIPv6 = true; + family = AF_INET6; sock = socket(AF_INET6, SOCK_DGRAM, 0); if (sock == -1) { sock = socket(AF_INET, SOCK_DGRAM, 0); - isIPv6 = false; + family = AF_INET; } #endif if (sock == -1) { @@ -1038,14 +1034,19 @@ Socket::UDPConnection::UDPConnection(const UDPConnection & o) { data_len = 0; } -/// Closes the UDP socket, cleans up any memory allocated by the socket. -Socket::UDPConnection::~UDPConnection() { +/// Close the UDP socket +void Socket::UDPConnection::close(){ if (sock != -1) { errno = EINTR; while (::close(sock) != 0 && errno == EINTR) { } sock = -1; } +} + +/// Closes the UDP socket, cleans up any memory allocated by the socket. +Socket::UDPConnection::~UDPConnection() { + close(); if (destAddr) { free(destAddr); destAddr = 0; @@ -1064,21 +1065,38 @@ void Socket::UDPConnection::SetDestination(std::string destIp, uint32_t port) { destAddr = 0; } destAddr = malloc(sizeof(struct sockaddr_in6)); - if (destAddr) { - destAddr_size = sizeof(struct sockaddr_in6); - memset(destAddr, 0, destAddr_size); - ((struct sockaddr_in6 *)destAddr)->sin6_family = AF_INET6; - ((struct sockaddr_in6 *)destAddr)->sin6_port = htons(port); - if (inet_pton(AF_INET6, destIp.c_str(), &(((struct sockaddr_in6 *)destAddr)->sin6_addr)) == 1) { - return; - } - memset(destAddr, 0, destAddr_size); - ((struct sockaddr_in *)destAddr)->sin_family = AF_INET; - ((struct sockaddr_in *)destAddr)->sin_port = htons(port); - if (inet_pton(AF_INET, destIp.c_str(), &(((struct sockaddr_in *)destAddr)->sin_addr)) == 1) { - return; - } + if (!destAddr) { + return; } + destAddr_size = sizeof(struct sockaddr_in6); + memset(destAddr, 0, destAddr_size); + + struct addrinfo * result, *rp, hints; + std::stringstream ss; + ss << port; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_ADDRCONFIG; + hints.ai_protocol = 0; + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + int s = getaddrinfo(destIp.c_str(), ss.str().c_str(), &hints, &result); + if (s != 0) { + DEBUG_MSG(DLVL_FAIL, "Could not connect UDP socket to %s:%i! Error: %s", destIp.c_str(), port, gai_strerror(s)); + return; + } + + for (rp = result; rp != NULL; rp = rp->ai_next) { + //assume success + memcpy(destAddr, rp->ai_addr, rp->ai_addrlen); + freeaddrinfo(result); + return; + //\todo Possibly detect and handle failure + } + freeaddrinfo(result); free(destAddr); destAddr = 0; DEBUG_MSG(DLVL_FAIL, "Could not set destination for UDP socket: %s:%d", destIp.c_str(), port); @@ -1164,64 +1182,163 @@ void Socket::UDPConnection::SendNow(const char * sdata, size_t len) { } /// Bind to a port number, returning the bound port. -/// Attempts to bind over IPv6 first. -/// If it fails, attempts to bind over IPv4. -/// If that fails too, gives up and returns zero. -/// Prints a debug message at DLVL_FAIL level if binding failed. +/// If that fails, returns zero. +/// \arg port Port to bind to, required. +/// \arg iface Interface address to listen for packets on (may be multicast address) +/// \arg multicastInterfaces Comma-separated list of interfaces to listen on for multicast packets. Optional, left out means automatically chosen by kernel. /// \return Actually bound port number, or zero on error. int Socket::UDPConnection::bind(int port, std::string iface, const std::string & multicastInterfaces) { + close();//we open a new socket for each attempt int result = 0; - if (isIPv6) { - struct sockaddr_in6 s6; - memset(&s6, 0, sizeof(s6)); - s6.sin6_family = AF_INET6; - if (iface == "0.0.0.0" || iface.length() == 0) { - s6.sin6_addr = in6addr_any; - } else { - inet_pton(AF_INET6, iface.c_str(), &s6.sin6_addr); + int addr_ret; + bool multicast = false; + struct addrinfo hints, *addr_result, *rp; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG | AI_PASSIVE; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + + std::stringstream ss; + ss << port; + + if (iface == "0.0.0.0" || iface.length() == 0) { + if ((addr_ret = getaddrinfo(0, ss.str().c_str(), &hints, &addr_result)) != 0){ + FAIL_MSG("Could not resolve %s for UDP: %s", iface.c_str(), gai_strerror(addr_ret)); + return 0; } - s6.sin6_port = htons(port); - int r = ::bind(sock, (sockaddr *)&s6, sizeof(s6)); - if (r == 0) { - result = ntohs(s6.sin6_port); - } - } else { - struct sockaddr_in s4; - memset(&s4, 0, sizeof(s4)); - s4.sin_family = AF_INET; - if (iface == "0.0.0.0" || iface.length() == 0) { - s4.sin_addr.s_addr = htonl(INADDR_ANY); - } else { - inet_pton(AF_INET, iface.c_str(), &s4.sin_addr); - } - s4.sin_port = htons(port); - int r = ::bind(sock, (sockaddr *)&s4, sizeof(s4)); - if (r == 0) { - result = ntohs(s4.sin_port); + }else{ + if ((addr_ret = getaddrinfo(iface.c_str(), ss.str().c_str(), &hints, &addr_result)) != 0){ + FAIL_MSG("Could not resolve %s for UDP: %s", iface.c_str(), gai_strerror(addr_ret)); + return 0; } } - if (!result){ - DEBUG_MSG(DLVL_FAIL, "Could not bind %s UDP socket to port %d: %s", isIPv6 ? "IPv6" : "IPv4", port, strerror(errno)); + + std::string err_str; + for (rp = addr_result; rp != NULL; rp = rp->ai_next) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sock == -1) { + continue; + } + char human_addr[INET6_ADDRSTRLEN]; + getnameinfo(rp->ai_addr, rp->ai_addrlen, human_addr, INET6_ADDRSTRLEN, 0, 0, NI_NUMERICHOST); + MEDIUM_MSG("Attempting bind to %s (%s)", human_addr, rp->ai_family == AF_INET6 ? "IPv6" : "IPv4"); + if (::bind(sock, rp->ai_addr, rp->ai_addrlen) == 0) { + family = rp->ai_family; + hints.ai_family = family; + break; + } + if (err_str.size()){err_str += ", ";} + err_str += human_addr; + err_str += ":"; + err_str += strerror(errno); + close();//we open a new socket for each attempt + } + if (sock == -1){ + FAIL_MSG("Could not open %s for UDP: %s", iface.c_str(), err_str.c_str()); + freeaddrinfo(addr_result); return 0; } - //Detect multicast - if (iface.length() && ((atoi(iface.c_str()) & 0xE0) == 0xE0)){ - if (!multicastInterfaces.length()){ - WARN_MSG("Multicast IP given without any defined interfaces"); + //socket is bound! Let's collect some more data... + if (family == AF_INET6){ + sockaddr_in6 * addr6 = (sockaddr_in6*)(rp->ai_addr); + result = ntohs(addr6->sin6_port); + if (memcmp((char*)&(addr6->sin6_addr), "\000\000\000\000\000\000\000\000\000\000\377\377", 12) == 0){ + //IPv6-mapped IPv4 address - 13th byte ([12]) holds the first IPv4 byte + multicast = (((char*)&(addr6->sin6_addr))[12] & 0xF0) == 0xE0; }else{ - struct ip_mreq group; - inet_pton(AF_INET, iface.c_str(), &group.imr_multiaddr.s_addr); - size_t loc = 0; - while (loc != std::string::npos){ - size_t nxtPos = multicastInterfaces.find(',', loc); - std::string curIface = multicastInterfaces.substr(loc, (nxtPos == std::string::npos ? nxtPos : nxtPos - loc)); - inet_pton(AF_INET, curIface.c_str(), &group.imr_interface.s_addr); - if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&group, sizeof(group)) < 0) { - WARN_MSG("Unable to register for multicast on interface %s: %s", curIface.c_str() , strerror(errno)); - } - loc = (nxtPos == std::string::npos ? nxtPos : nxtPos + 1); - } + //"normal" IPv6 address - prefix ff00::/8 + multicast = (((char*)&(addr6->sin6_addr))[0] == 0xFF); } + }else{ + sockaddr_in * addr4 = (sockaddr_in*)(rp->ai_addr); + result = ntohs(addr4->sin_port); + //multicast has a "1110" bit prefix + multicast = (((char*)&(addr4->sin_addr))[0] & 0xF0) == 0xE0; + } + freeaddrinfo(addr_result); + + //handle multicast membership(s) + if (multicast){ + struct ipv6_mreq mreq6; + struct ip_mreq mreq4; + memset(&mreq4, 0, sizeof(mreq4)); + memset(&mreq6, 0, sizeof(mreq6)); + struct addrinfo *reslocal, *resmulti; + if ((addr_ret = getaddrinfo(iface.c_str(), 0, &hints, &resmulti)) != 0){ + WARN_MSG("Unable to parse multicast address: %s", gai_strerror(addr_ret)); + close(); + result = -1; + return result; + } + + if (!multicastInterfaces.length()){ + if (family == AF_INET6){ + memcpy(&mreq6.ipv6mr_multiaddr, &((sockaddr_in6*)resmulti->ai_addr)->sin6_addr, sizeof(mreq6.ipv6mr_multiaddr)); + if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&mreq6, sizeof(mreq6)) != 0) { + FAIL_MSG("Unable to register for IPv6 multicast on all interfaces: %s", strerror(errno)); + close(); + result = -1; + } + }else{ + mreq4.imr_multiaddr = ((sockaddr_in*)resmulti->ai_addr)->sin_addr; + mreq4.imr_interface.s_addr = INADDR_ANY; + if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq4, sizeof(mreq4)) != 0) { + FAIL_MSG("Unable to register for IPv4 multicast on all interfaces: %s", strerror(errno)); + close(); + result = -1; + } + } + }else{ + size_t nxtPos = std::string::npos; + bool atLeastOne = false; + for (size_t loc = 0; loc != std::string::npos; loc = (nxtPos == std::string::npos ? nxtPos : nxtPos + 1)){ + nxtPos = multicastInterfaces.find(',', loc); + std::string curIface = multicastInterfaces.substr(loc, (nxtPos == std::string::npos ? nxtPos : nxtPos - loc)); + //do a bit of filtering for IPv6, removing the []-braces, if any + if (curIface[0] == '['){ + if (curIface[curIface.size() - 1] == ']'){ + curIface = curIface.substr(1, curIface.size()-2); + }else{ + curIface = curIface.substr(1, curIface.size()-1); + } + } + if (family == AF_INET6){ + INFO_MSG("Registering for IPv6 multicast on interface %s", curIface.c_str()); + if ((addr_ret = getaddrinfo(curIface.c_str(), 0, &hints, &reslocal)) != 0){ + WARN_MSG("Unable to resolve IPv6 interface address %s: %s", curIface.c_str(), gai_strerror(addr_ret)); + continue; + } + memcpy(&mreq6.ipv6mr_multiaddr, &((sockaddr_in6*)resmulti->ai_addr)->sin6_addr, sizeof(mreq6.ipv6mr_multiaddr)); + mreq6.ipv6mr_interface = ((sockaddr_in6*)reslocal->ai_addr)->sin6_scope_id; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (char *)&mreq6, sizeof(mreq6)) != 0) { + FAIL_MSG("Unable to register for IPv6 multicast on interface %s (%u): %s", curIface.c_str(), ((sockaddr_in6*)reslocal->ai_addr)->sin6_scope_id, strerror(errno)); + }else{ + atLeastOne = true; + } + }else{ + INFO_MSG("Registering for IPv4 multicast on interface %s", curIface.c_str()); + if ((addr_ret = getaddrinfo(curIface.c_str(), 0, &hints, &reslocal)) != 0){ + WARN_MSG("Unable to resolve IPv4 interface address %s: %s", curIface.c_str(), gai_strerror(addr_ret)); + continue; + } + mreq4.imr_multiaddr = ((sockaddr_in*)resmulti->ai_addr)->sin_addr; + mreq4.imr_interface = ((sockaddr_in*)reslocal->ai_addr)->sin_addr; + if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq4, sizeof(mreq4)) != 0) { + FAIL_MSG("Unable to register for IPv4 multicast on interface %s: %s", curIface.c_str() , strerror(errno)); + }else{ + atLeastOne = true; + } + } + if (!atLeastOne){ + close(); + result = -1; + } + freeaddrinfo(reslocal);//free resolved interface addr + }//loop over all interfaces + } + freeaddrinfo(resmulti);//free resolved multicast addr + } return result; } diff --git a/lib/socket.h b/lib/socket.h index fb776972..a0e580aa 100644 --- a/lib/socket.h +++ b/lib/socket.h @@ -130,13 +130,14 @@ namespace Socket { unsigned int up;///< Amount of bytes transferred up. unsigned int down;///< Amount of bytes transferred down. unsigned int data_size;///< The size in bytes of the allocated space in the data pointer. - bool isIPv6;//<<< True if IPv6 socket, false otherwise. + int family;///");d.append(UI.buildUI([{label:"Protocol",type:"select",select:x,"function":function(){V.html(F($(this).getval()))}}])).append(V)}break;case "Streams":if(!("capabilities"in mist.data)){d.html("Loading..");mist.send(function(){UI.navto(a)},{capabilities:!0});break}h=$("").append($("").append("").attr("colspan",6).text("Loading.."));e=$("").html($("").html($("").html($("").data("index",d).html($("").data("index",d).html($("
").text("Stream name").attr("data-sort-type", "string").addClass("sorting-asc")).append($("").text("Source").attr("data-sort-type","string")).append($("").text("Status").attr("data-sort-type","int")).append($("").css("text-align","right").text("Connections").attr("data-sort-type","int")).append($("")).append($("")))).append(h);d.append(UI.buildUI([{type:"help",help:"Here you can create, edit or delete new and existing streams. Immidiately go to the stream preview or view the information available about the stream with the info button."}])).append($("").css("text-align","right").html($("").addClass("description").text("Loading..")),t=0;if(typeof mist.data.totals!="undefined"&&typeof mist.data.totals[d]!="undefined"){var g=mist.data.totals[d].all_protocols.clients,t=0;if(g.length){for(a in g)t=t+g[a][1];t=Math.round(t/g.length)}}f.html(UI.format.number(t));if(t==0&&e.online==1)e.online=2;t=$("").css("text-align","right").css("white-space","nowrap");(!("ischild"in e)|| -!e.ischild)&&t.html($("
").html(g).attr("title",d).addClass("overflow_ellipsis")).append($("").text(e.source).attr("title",e.source).addClass("description").addClass("overflow_ellipsis").css("max-width","20em")).append($("").data("sort-value",e.online).html(i)).append(f).append($("").html(j)).append(t)); +c[b],e;e=d in mist.data.streams?mist.data.streams[d]:z[d];var f=$("").css("text-align","right").html($("").addClass("description").text("Loading..")),g=0;if(typeof mist.data.totals!="undefined"&&typeof mist.data.totals[d]!="undefined"){var t=mist.data.totals[d].all_protocols.clients,g=0;if(t.length){for(a in t)g=g+t[a][1];g=Math.round(g/t.length)}}f.html(UI.format.number(g));if(g==0&&e.online==1)e.online=2;g=$("").css("text-align","right").css("white-space","nowrap");(!("ischild"in e)|| +!e.ischild)&&g.html($("
").html(t).attr("title",d).addClass("overflow_ellipsis")).append($("").text(e.source).attr("title",e.source).addClass("description").addClass("overflow_ellipsis").css("max-width","20em")).append($("").data("sort-value",e.online).html(i)).append(f).append($("").html(j)).append(g)); a++}},{totals:a,active_streams:true})},z=$.extend(!0,{},mist.data.streams),W=function(a,c){var b=$.extend({},c);delete b.meta;delete b.error;b.online=2;b.name=a;b.ischild=true;return b};if(mist.data.LTS){var C=0,G=0;for(l in mist.data.streams)r=mist.data.capabilities.inputs.Folder||mist.data.capabilities.inputs["Folder.exe"],mist.inputMatch(r.source_match,mist.data.streams[l].source)&&(z[l].source+="*",mist.send(function(a,c){var b=c.stream,d;for(d in a.browse.files)for(var e in mist.data.capabilities.inputs)if(!(e.indexOf("Buffer")>= 0||e.indexOf("Folder")>=0)&&mist.inputMatch(mist.data.capabilities.inputs[e].source_match,"/"+a.browse.files[d])){var f=b+"+"+a.browse.files[d];z[f]=W(f,mist.data.streams[b]);z[f].source=mist.data.streams[b].source+a.browse.files[d]}"files"in a.browse&&a.browse.files.length?z[b].filesfound=true:mist.data.streams[b].filesfound=false;G++;if(C==G){mist.send(function(){E()},{active_streams:true});UI.interval.set(function(){E()},1E4)}},{browse:mist.data.streams[l].source},{stream:l}),C++);0==C&&(mist.send(function(){E()}, {active_streams:!0}),UI.interval.set(function(){E()},3E4))}else mist.send(function(){E()},{active_streams:!0}),UI.interval.set(function(){E()},1E4);break;case "Edit Stream":if("undefined"==typeof mist.data.capabilities){mist.send(function(){UI.navto(a,c)},{capabilities:!0});d.append("Loading..");break}j=!1;""!=c&&(j=!0);j?(l=c,o=mist.data.streams[l],d.find("h2").append(' "'+l+'"')):(d.html($("

").text("New Stream")),o={});l=[];for(q in mist.data.capabilities.inputs)l.push(mist.data.capabilities.inputs[q].source_match); var J=$("
");d.append(UI.buildUI([{label:"Stream name",type:"str",validate:["required","streamname"],pointer:{main:o,index:"name"},help:"Set the name this stream will be recognised by for players and/or stream pushing."},{label:"Source",type:"browse",validate:["required"],filetypes:l,pointer:{main:o,index:"source"},help:'Set the stream source.
VoD:You can browse to the file or folder as a source or simply enter the path to the file.
Live:You\'ll need to enter "push://IP" with the IP of the machine pushing towards MistServer.
You can use "push://" to accept any source.
(Pro only)Use "push://(IP)@password" to set a password protection for pushes.
If you\'re unsure how to set the source properly, please view our Live pushing guide at the tools section.', -"function":function(){var a=$(this).val();if(a!=""){var c=null,b;for(b in mist.data.capabilities.inputs)if(typeof mist.data.capabilities.inputs[b].source_match!="undefined"&&mist.inputMatch(mist.data.capabilities.inputs[b].source_match,a)){c=b;break}if(c===null)J.html($("

").text("Unrecognized input").addClass("red")).append($("").text("Please edit the stream source.").addClass("red"));else{a=mist.data.capabilities.inputs[c];J.html($("

").text(a.name+" Input options"));a=mist.convertBuildOptions(a, -o);J.append(UI.buildUI(a))}}}},$("
"),{type:"custom",custom:J},$("
"),$("

").text("Encryption"),{type:"help",help:"To enable encryption, the licence acquisition url must be entered, as well as either the content key or the key ID and seed.
Unsure how you should fill in your encryption or missing your preferred encryption? Please contact us."},{label:"License acquisition url",type:"str",LTSonly:!0,pointer:{main:o,index:"la_url"}},$("
"),{label:"Content key",type:"str",LTSonly:!0,pointer:{main:o, -index:"contentkey"}},{type:"text",text:" - or - "},{label:"Key ID",type:"str",LTSonly:!0,pointer:{main:o,index:"keyid"}},{label:"Key seed",type:"str",LTSonly:!0,pointer:{main:o,index:"keyseed"}},{type:"buttons",buttons:[{type:"cancel",label:"Cancel","function":function(){UI.navto("Streams")}},{type:"save",label:"Save","function":function(){if(!mist.data.streams)mist.data.streams={};mist.data.streams[o.name]=o;c!=o.name&&delete mist.data.streams[c];var a={};if(mist.data.LTS){a.addstream={};a.addstream[o.name]= -o;if(c!=o.name)a.deletestream=[c]}else a.streams=mist.data.streams;mist.send(function(){delete mist.data.streams[o.name].online;delete mist.data.streams[o.name].error;UI.navto("Streams")},a)}}]}]));break;case "Preview":if(""==c){d.append("Loading..");var N=function(c){var b={};c.sort();d.html($("

").text(a)).append(UI.buildUI([{label:"Select a stream",type:"select",select:c,pointer:{main:b,index:"stream"}},{type:"buttons",buttons:[{type:"save",label:"Go","function":function(){UI.navto(a,b.stream)}}]}])); +"function":function(){var a=$(this).val();if(a!=""){var c=null,b;for(b in mist.data.capabilities.inputs)if(typeof mist.data.capabilities.inputs[b].source_match!="undefined"&&mist.inputMatch(mist.data.capabilities.inputs[b].source_match,a)){c=b;break}if(c===null)J.html($("

").text("Unrecognized input").addClass("red")).append($("").text("Please edit the stream source.").addClass("red"));else{c=mist.data.capabilities.inputs[c];J.html($("

").text(c.name+" Input options"));c=mist.convertBuildOptions(c, +o);"always_match"in mist.data.capabilities.inputs[b]&&mist.inputMatch(mist.data.capabilities.inputs[b].always_match,a)&&c.push({label:"Always on",type:"checkbox",help:"Keep this input available at all times, even when there are no active viewers.",pointer:{main:o,index:"always_on"}});J.append(UI.buildUI(c))}}}},{label:"Stop sessions",type:"checkbox",help:"When saving these stream settings, kill this stream's current connections.",pointer:{main:o,index:"stop_sessions"}},$("
"),{type:"custom",custom:J}, +$("
"),$("

").text("Encryption"),{type:"help",help:"To enable encryption, the licence acquisition url must be entered, as well as either the content key or the key ID and seed.
Unsure how you should fill in your encryption or missing your preferred encryption? Please contact us."},{label:"License acquisition url",type:"str",LTSonly:!0,pointer:{main:o,index:"la_url"}},$("
"),{label:"Content key",type:"str",LTSonly:!0,pointer:{main:o,index:"contentkey"}},{type:"text",text:" - or - "},{label:"Key ID", +type:"str",LTSonly:!0,pointer:{main:o,index:"keyid"}},{label:"Key seed",type:"str",LTSonly:!0,pointer:{main:o,index:"keyseed"}},{type:"buttons",buttons:[{type:"cancel",label:"Cancel","function":function(){UI.navto("Streams")}},{type:"save",label:"Save","function":function(){if(!mist.data.streams)mist.data.streams={};mist.data.streams[o.name]=o;c!=o.name&&delete mist.data.streams[c];var a={};if(mist.data.LTS){a.addstream={};a.addstream[o.name]=o;if(c!=o.name)a.deletestream=[c]}else a.streams=mist.data.streams; +if(o.stop_sessions&&c!=""){a.stop_sessions=c;delete o.stop_sessions}mist.send(function(){delete mist.data.streams[o.name].online;delete mist.data.streams[o.name].error;UI.navto("Streams")},a)}}]}]));break;case "Preview":if(""==c){d.append("Loading..");var N=function(c){var b={};c.sort();d.html($("

").text(a)).append(UI.buildUI([{label:"Select a stream",type:"select",select:c,pointer:{main:b,index:"stream"}},{type:"buttons",buttons:[{type:"save",label:"Go","function":function(){UI.navto(a,b.stream)}}]}])); UI.elements.secondary_menu.html("").append($("").addClass("button").addClass("active").text("Choose stream").click(function(){UI.navto("Preview")}));var e=$("
").addClass("preview_icons");d.append($("").addClass("description").text("Or, click a stream from the list below.")).append(e);for(var f in c){var g=c[f],h="";if(g.indexOf("+")>-1){h=g.split("+");h=mist.data.streams[h[0]].source+h[1]}else h=mist.data.streams[g].source;e.append($("