#include #include "output_http_internal.h" #include #include #include #include "flashPlayer.h" #include "oldFlashPlayer.h" #include namespace Mist { /// Helper function to find the protocol entry for a given port number std::string getProtocolForPort(uint16_t portNo){ std::string ret; Util::DTSCShmReader rCapa(SHM_CAPA); DTSC::Scan conns = rCapa.getMember("connectors"); Util::DTSCShmReader rProto(SHM_PROTO); DTSC::Scan prtcls = rProto.getScan(); unsigned int pro_cnt = prtcls.getSize(); for (unsigned int i = 0; i < pro_cnt; ++i){ DTSC::Scan capa = conns.getMember(prtcls.getIndice(i).getMember("connector").asString()); uint16_t port = prtcls.getIndice(i).getMember("port").asInt(); //get the default port if none is set if (!port){ port = capa.getMember("optional").getMember("port").getMember("default").asInt(); } if (port == portNo){ ret = capa.getMember("protocol").asString(); break; } } if (ret.find(':') != std::string::npos){ ret.erase(ret.find(':')); } return ret; } OutHTTP::OutHTTP(Socket::Connection & conn) : HTTPOutput(conn){ stayConnected = false; if (myConn.getPureSocket() >= 0){ std::string host = getConnectedHost(); dup2(myConn.getSocket(), STDIN_FILENO); dup2(myConn.getSocket(), STDOUT_FILENO); myConn.drop(); myConn = Socket::Connection(fileno(stdout),fileno(stdin) ); myConn.setHost(host); } if (config->getString("nostreamtext").size()){ setenv("MIST_HTTP_nostreamtext", config->getString("nostreamtext").c_str(), 1); } if (config->getString("pubaddr").size()){ setenv("MIST_HTTP_pubaddr", config->getString("pubaddr").c_str(), 1); } if (config->getOption("wrappers",true).size() == 0 || config->getString("wrappers") == ""){ JSON::Value & wrappers = config->getOption("wrappers",true); wrappers.shrink(0); jsonForEach(capa["optional"]["wrappers"]["allowed"],it){ wrappers.append(*it); } } } OutHTTP::~OutHTTP() {} bool OutHTTP::listenMode(){ return !(config->getString("ip").size()); } void OutHTTP::onFail(const std::string & msg, bool critical){ std::string method = H.method; // send logo icon if (H.url.length() > 4 && H.url.substr(H.url.length() - 4, 4) == ".ico"){ sendIcon(); return; } if (H.url.length() > 6 && H.url.substr(H.url.length() - 5, 5) == ".html"){ HTMLResponse(); return; } if (H.url.size() >= 3 && H.url.substr(H.url.size() - 3) == ".js"){ if (websocketHandler()){return;} JSON::Value json_resp; json_resp["error"] = "Could not retrieve stream. Sorry."; json_resp["error_guru"] = msg; if (config->getString("nostreamtext") != ""){ json_resp["on_error"] = config->getString("nostreamtext"); } if (H.url.size() >= 5 && H.url.substr(0, 5) == "/json"){ H.Clean(); H.SetBody(json_resp.toString()); }else{ H.Clean(); H.SetBody("if (!mistvideo){var mistvideo = {};}\nmistvideo['" + streamName + "'] = "+json_resp.toString()+";\n"); } H.setCORSHeaders(); if(method == "OPTIONS" || method == "HEAD"){ H.SendResponse("200", "OK", myConn); H.Clean(); return; } H.SendResponse("200", "Stream not found", myConn); H.Clean(); return; } HTTPOutput::onFail(msg, critical); } void OutHTTP::init(Util::Config * cfg){ HTTPOutput::init(cfg); capa.removeMember("deps"); capa["name"] = "HTTP"; capa["desc"] = "Generic HTTP handler, required for all other HTTP-based outputs."; capa["provides"] = "HTTP"; capa["protocol"] = "http://"; capa["codecs"][0u][0u].append("*"); capa["url_rel"] = "/$.html"; capa["url_match"].append("/crossdomain.xml"); capa["url_match"].append("/clientaccesspolicy.xml"); capa["url_match"].append("/$.html"); capa["url_match"].append("/favicon.ico"); capa["url_match"].append("/$.smil"); capa["url_match"].append("/info_$.js"); capa["url_match"].append("/json_$.js"); capa["url_match"].append("/player.js"); capa["url_match"].append("/player.css"); capa["url_match"].append("/videojs.js"); capa["url_match"].append("/dashjs.js"); capa["url_match"].append("/embed_$.js"); capa["url_match"].append("/flashplayer.swf"); capa["url_match"].append("/oldflashplayer.swf"); capa["optional"]["wrappers"]["name"] = "Active players"; capa["optional"]["wrappers"]["help"] = "Which players are attempted and in what order."; capa["optional"]["wrappers"]["default"] = ""; capa["optional"]["wrappers"]["type"] = "ord_multi_sel"; /*capa["optional"]["wrappers"]["allowed"].append("theoplayer"); capa["optional"]["wrappers"]["allowed"].append("jwplayer");*/ capa["optional"]["wrappers"]["allowed"].append("html5"); capa["optional"]["wrappers"]["allowed"].append("videojs"); capa["optional"]["wrappers"]["allowed"].append("dashjs"); //capa["optional"]["wrappers"]["allowed"].append("polytrope"); //currently borked capa["optional"]["wrappers"]["allowed"].append("flash_strobe"); capa["optional"]["wrappers"]["allowed"].append("silverlight"); capa["optional"]["wrappers"]["allowed"].append("img"); capa["optional"]["wrappers"]["option"] = "--wrappers"; capa["optional"]["wrappers"]["short"] = "w"; cfg->addConnectorOptions(8080, capa); /*LTS-START*/ cfg->addOption("nostreamtext", JSON::fromString("{\"arg\":\"string\", \"default\":\"\", \"short\":\"t\",\"long\":\"nostreamtext\",\"help\":\"Text or HTML to display when streams are unavailable.\"}")); capa["optional"]["nostreamtext"]["name"] = "Stream unavailable text"; capa["optional"]["nostreamtext"]["help"] = "Text or HTML to display when streams are unavailable."; capa["optional"]["nostreamtext"]["default"] = ""; capa["optional"]["nostreamtext"]["type"] = "str"; capa["optional"]["nostreamtext"]["option"] = "--nostreamtext"; cfg->addOption("pubaddr", JSON::fromString("{\"arg\":\"string\", \"default\":\"\", \"short\":\"A\",\"long\":\"public-address\",\"help\":\"Full public address this output is available as.\"}")); capa["optional"]["pubaddr"]["name"] = "Public address"; capa["optional"]["pubaddr"]["help"] = "Full public address this output is available as, if being proxied"; capa["optional"]["pubaddr"]["default"] = ""; capa["optional"]["pubaddr"]["type"] = "str"; capa["optional"]["pubaddr"]["option"] = "--public-address"; /*LTS-END*/ } /// Sorts the JSON::Value objects that hold source information by preference. struct sourceCompare { bool operator() (const JSON::Value& lhs, const JSON::Value& rhs) const { //first compare simultaneous tracks if (lhs["simul_tracks"].asInt() > rhs["simul_tracks"].asInt()){ //more tracks = higher priority = true. return true; } if (lhs["simul_tracks"].asInt() < rhs["simul_tracks"].asInt()){ //less tracks = lower priority = false return false; } //same amount of tracks - compare "hardcoded" priorities if (lhs["priority"].asInt() > rhs["priority"].asInt()){ //higher priority = true. return true; } if (lhs["priority"].asInt() < rhs["priority"].asInt()){ //lower priority = false return false; } //same priority - compare total matches if (lhs["total_matches"].asInt() > rhs["total_matches"].asInt()){ //more matches = higher priority = true. return true; } if (lhs["total_matches"].asInt() < rhs["total_matches"].asInt()){ //less matches = lower priority = false return false; } //also same amount of matches? just compare the URL then. return lhs["url"].asStringRef() < rhs["url"].asStringRef(); } }; void addSource(const std::string & rel, std::set & sources, const HTTP::URL & url, JSON::Value & conncapa, unsigned int most_simul, unsigned int total_matches){ JSON::Value tmp; tmp["type"] = conncapa["type"]; tmp["relurl"] = rel; tmp["priority"] = conncapa["priority"]; if (conncapa.isMember("player_url")){tmp["player_url"] = conncapa["player_url"].asStringRef();} tmp["simul_tracks"] = most_simul; tmp["total_matches"] = total_matches; if (url.path.size()){ tmp["url"] = url.protocol + "://" + url.host + ":" + url.port + "/" + url.path + rel; }else{ tmp["url"] = url.protocol + "://" + url.host + ":" + url.port + rel; } sources.insert(tmp); } /// Checks if a given user agent is allowed according to the given exception. bool checkException(const JSON::Value & ex, const std::string & useragent){ //No user agent? Always allow everything. if (!useragent.size()){return true;} if (!ex.isArray() || !ex.size()){return true;} bool ret = true; jsonForEachConst(ex, e){ if (!e->isArray() || !e->size()){continue;} bool setTo = ((*e)[0u].asStringRef() == "whitelist"); if (e->size() == 1){ ret = setTo; continue; } if (!(*e)[1].isArray()){continue;} jsonForEachConst((*e)[1u], i){ if (useragent.find(i->asStringRef()) != std::string::npos){ ret = setTo; } } } return ret; } void addSources(std::string & streamname, std::set & sources, HTTP::URL url, JSON::Value & conncapa, JSON::Value & strmMeta, const std::string & useragent){ if (strmMeta.isMember("live") && conncapa.isMember("exceptions") && conncapa["exceptions"].isObject() && conncapa["exceptions"].size()){ jsonForEach(conncapa["exceptions"], ex){ if (ex.key() == "live"){ if (!checkException(*ex, useragent)){ return; } } } } const std::string & rel = conncapa["url_rel"].asStringRef(); unsigned int most_simul = 0; unsigned int total_matches = 0; if (conncapa.isMember("codecs") && conncapa["codecs"].size() > 0){ jsonForEach(conncapa["codecs"], it) { unsigned int simul = 0; if ((*it).size() > 0){ jsonForEach((*it), itb) { unsigned int matches = 0; if ((*itb).size() > 0){ jsonForEach((*itb), itc) { jsonForEach(strmMeta["tracks"], trit) { if ((*trit)["codec"].asStringRef() == (*itc).asStringRef()){ matches++; total_matches++; if (conncapa.isMember("exceptions") && conncapa["exceptions"].isObject() && conncapa["exceptions"].size()){ jsonForEach(conncapa["exceptions"], ex){ if (ex.key() == "codec:"+(*trit)["codec"].asStringRef()){ if (!checkException(*ex, useragent)){ matches--; total_matches--; } break; } } } } } } } if (matches){ simul++; } } } if (simul > most_simul){ most_simul = simul; } } } if (conncapa.isMember("methods") && conncapa["methods"].size() > 0){ std::string relurl; size_t found = rel.find('$'); if (found != std::string::npos){ relurl = rel.substr(0, found) + Encodings::URL::encode(streamname) + rel.substr(found+1); }else{ relurl = "/"; } jsonForEach(conncapa["methods"], it) { if (it->isMember("url_rel")){ size_t foundb = (*it)["url_rel"].asStringRef().find('$'); if (foundb != std::string::npos){ relurl = (*it)["url_rel"].asStringRef().substr(0, foundb) + Encodings::URL::encode(streamname) + (*it)["url_rel"].asStringRef().substr(foundb+1); } } if (!strmMeta.isMember("live") || !it->isMember("nolive")){ if (!url.protocol.size() && it->isMember("handler")){ url.protocol = (*it)["handler"].asStringRef(); } addSource(relurl, sources, url, *it, most_simul, total_matches); } } } } void OutHTTP::HTMLResponse(){ std::string method = H.method; HTTP::URL fullURL(H.GetHeader("Host")); if (!fullURL.protocol.size()){ fullURL.protocol = getProtocolForPort(fullURL.getPort()); } /*LTS-START*/ if (config->getString("pubaddr") != ""){ HTTP::URL altURL(config->getString("pubaddr")); fullURL.protocol = altURL.protocol; if (altURL.host.size()){fullURL.host = altURL.host;} fullURL.port = altURL.port; fullURL.path = altURL.path; } /*LTS-END*/ std::string uAgent = H.GetHeader("User-Agent"); H.Clean(); H.SetHeader("Content-Type", "text/html"); H.SetHeader("Server", "MistServer/" PACKAGE_VERSION); H.setCORSHeaders(); if(method == "OPTIONS" || method == "HEAD"){ H.SendResponse("200", "OK", myConn); H.Clean(); return; } std::string hlsUrl = "/hls/"+streamName+"/index.m3u8"; std::string mp4Url = "/"+streamName+".mp4"; H.SetBody(""+streamName+"
"); if ((uAgent.find("iPad") != std::string::npos) || (uAgent.find("iPod") != std::string::npos) || (uAgent.find("iPhone") != std::string::npos)) { H.SetHeader("Location",hlsUrl); H.SendResponse("307", "HLS redirect", myConn); return; } H.SendResponse("200", "OK", myConn); } JSON::Value OutHTTP::getStatusJSON(std::string & reqHost, const std::string & useragent){ JSON::Value json_resp; if (config->getString("nostreamtext") != ""){ json_resp["on_error"] = config->getString("nostreamtext"); } uint8_t streamStatus = Util::getStreamStatus(streamName); if (streamStatus != STRMSTAT_READY){ switch (streamStatus){ case STRMSTAT_OFF: json_resp["error"] = "Stream is offline"; break; case STRMSTAT_INIT: json_resp["error"] = "Stream is initializing"; break; case STRMSTAT_BOOT: json_resp["error"] = "Stream is booting"; break; case STRMSTAT_WAIT: json_resp["error"] = "Stream is waiting for data"; break; case STRMSTAT_SHUTDOWN: json_resp["error"] = "Stream is shutting down"; break; case STRMSTAT_INVALID: json_resp["error"] = "Stream status is invalid?!"; break; default: json_resp["error"] = "Stream status is unknown?!"; break; } return json_resp; } initialize(); if (!myConn){ return json_resp; } bool hasVideo = false; for (std::map::iterator trit = myMeta.tracks.begin(); trit != myMeta.tracks.end(); trit++){ if (trit->second.type == "video"){ hasVideo = true; if (trit->second.width > json_resp["width"].asInt()){ json_resp["width"] = trit->second.width; } if (trit->second.height > json_resp["height"].asInt()){ json_resp["height"] = trit->second.height; } } } if (json_resp["width"].asInt() < 1 || json_resp["height"].asInt() < 1){ json_resp["width"] = 640ll; json_resp["height"] = 480ll; if (!hasVideo){json_resp["height"] = 20ll;} } if (myMeta.vod){ json_resp["type"] = "vod"; } if (myMeta.live){ json_resp["type"] = "live"; } // show ALL the meta datas! json_resp["meta"] = myMeta.toJSON(); jsonForEach(json_resp["meta"]["tracks"], it) { if (it->isMember("lang")){ (*it)["language"] = Encodings::ISO639::decode((*it)["lang"].asStringRef()); } it->removeMember("fragments"); it->removeMember("keys"); it->removeMember("keysizes"); it->removeMember("parts"); it->removeMember("ivecs");/*LTS*/ } json_resp["meta"].removeMember("source"); //Get sources/protocols information Util::DTSCShmReader rCapa(SHM_CAPA); DTSC::Scan connectors = rCapa.getMember("connectors"); Util::DTSCShmReader rProto(SHM_PROTO); DTSC::Scan prots = rProto.getScan(); if (!prots || !connectors){ json_resp["error"] = "Server configuration unavailable at this time."; return json_resp; } //create a set for storing source information std::set sources; //find out which connectors are enabled std::set conns; unsigned int prots_ctr = prots.getSize(); for (unsigned int i = 0; i < prots_ctr; ++i){ conns.insert(prots.getIndice(i).getMember("connector").asString()); } //loop over the connectors. for (unsigned int i = 0; i < prots_ctr; ++i){ std::string cName = prots.getIndice(i).getMember("connector").asString(); DTSC::Scan capa = connectors.getMember(cName); //if the connector has a port, if (capa.getMember("optional").getMember("port")){ HTTP::URL outURL(reqHost); //get the default port if none is set outURL.port = prots.getIndice(i).getMember("port").asString(); if (!outURL.port.size()){ outURL.port = capa.getMember("optional").getMember("port").getMember("default").asString(); } outURL.protocol = capa.getMember("protocol").asString(); if (outURL.protocol.find(':') != std::string::npos){ outURL.protocol.erase(outURL.protocol.find(':')); } /*LTS-START*/ if (prots.getIndice(i).hasMember("pubaddr") && prots.getIndice(i).getMember("pubaddr").asString().size()){ HTTP::URL altURL(prots.getIndice(i).getMember("pubaddr").asString()); outURL.protocol = altURL.protocol; if (altURL.host.size()){outURL.host = altURL.host;} outURL.port = altURL.port; outURL.path = altURL.path; } /*LTS-END*/ //and a URL - then list the URL JSON::Value capa_json = capa.asJSON(); if (capa.getMember("url_rel") || capa.getMember("methods")){ addSources(streamName, sources, outURL, capa_json, json_resp["meta"], useragent); } //Make note if this connector can be depended upon by other connectors if (capa.getMember("provides")){ std::string cProv = capa.getMember("provides").asString(); //if this connector can be depended upon by other connectors, loop over the rest //check each enabled protocol separately to see if it depends on this connector unsigned int capa_lst_ctr = connectors.getSize(); for (unsigned int j = 0; j < capa_lst_ctr; ++j){ //if it depends on this connector and has a URL, list it if (conns.count(connectors.getIndiceName(j)) && connectors.getIndice(j).getMember("deps").asString() == cProv && connectors.getIndice(j).getMember("methods")){ JSON::Value subcapa_json = connectors.getIndice(j).asJSON(); addSources(streamName, sources, outURL, subcapa_json, json_resp["meta"], useragent); } } } } } //loop over the added sources, add them to json_resp["sources"] for (std::set::iterator it = sources.begin(); it != sources.end(); it++){ if ((*it)["simul_tracks"].asInt() > 0){ json_resp["source"].append(*it); } } return json_resp; } void OutHTTP::onHTTP(){ std::string method = H.method; if (H.url == "/crossdomain.xml"){ H.Clean(); H.SetHeader("Content-Type", "text/xml"); H.SetHeader("Server", "MistServer/" PACKAGE_VERSION); H.setCORSHeaders(); if(method == "OPTIONS" || method == "HEAD"){ H.SendResponse("200", "OK", myConn); H.Clean(); return; } H.SetBody(""); H.SendResponse("200", "OK", myConn); H.Clean(); return; } //crossdomain.xml if (H.url == "/clientaccesspolicy.xml"){ H.Clean(); H.SetHeader("Content-Type", "text/xml"); H.SetHeader("Server", "MistServer/" PACKAGE_VERSION); H.setCORSHeaders(); if(method == "OPTIONS" || method == "HEAD"){ H.SendResponse("200", "OK", myConn); H.Clean(); return; } H.SetBody(""); H.SendResponse("200", "OK", myConn); H.Clean(); return; } //clientaccesspolicy.xml if (H.url == "/flashplayer.swf"){ H.Clean(); H.SetHeader("Content-Type", "application/x-shockwave-flash"); H.SetHeader("Server", "MistServer/" PACKAGE_VERSION); H.SetBody((const char*)FlashMediaPlayback_101_swf, FlashMediaPlayback_101_swf_len); H.SendResponse("200", "OK", myConn); return; } if (H.url == "/oldflashplayer.swf"){ H.Clean(); H.SetHeader("Content-Type", "application/x-shockwave-flash"); H.SetHeader("Server", "MistServer/" PACKAGE_VERSION); H.SetBody((const char *)FlashMediaPlayback_swf, FlashMediaPlayback_swf_len); H.SendResponse("200", "OK", myConn); return; } // send logo icon if (H.url.length() > 4 && H.url.substr(H.url.length() - 4, 4) == ".ico"){ sendIcon(); return; } // send generic HTML page if (H.url.length() > 6 && H.url.substr(H.url.length() - 5, 5) == ".html"){ HTMLResponse(); return; } // send smil MBR index if (H.url.length() > 6 && H.url.substr(H.url.length() - 5, 5) == ".smil"){ std::string reqHost = HTTP::URL(H.GetHeader("Host")).host; std::string port, url_rel; std::string trackSources;//this string contains all track sources for MBR smil { Util::DTSCShmReader rProto(SHM_PROTO); DTSC::Scan prtcls = rProto.getScan(); Util::DTSCShmReader rCapa(SHM_CAPA); DTSC::Scan capa = rCapa.getMember("connectors").getMember("RTMP"); unsigned int pro_cnt = prtcls.getSize(); for (unsigned int i = 0; i < pro_cnt; ++i){ if (prtcls.getIndice(i).getMember("connector").asString() != "RTMP"){ continue; } port = prtcls.getIndice(i).getMember("port").asString(); //get the default port if none is set if (!port.size()){ port = capa.getMember("optional").getMember("port").getMember("default").asString(); } //extract url url_rel = capa.getMember("url_rel").asString(); if (url_rel.find('$')){ url_rel.resize(url_rel.find('$')); } } initialize(); if (!myConn){return;} for (std::map::iterator trit = myMeta.tracks.begin(); trit != myMeta.tracks.end(); trit++){ if (trit->second.type == "video"){ trackSources += "