#ifdef _WIN32 #undef _WIN32_WINNT #define _WIN32_WINNT 0x0601 #include #endif #include #include "global.h" #include "aolxml/aolxml.h" #include "threadedRunner.h" #include "w3cLog.h" #include "file/fileUtils.h" #include "services/stdServiceImpl.h" #ifndef _WIN32 #include #endif using namespace std; using namespace uniString; using namespace stringUtil; config gOptions; unsigned char appname_tmpbuf[] = { (unsigned char)~'B', (unsigned char)~'a', (unsigned char)~'n', (unsigned char)~'a', (unsigned char)~'n', (unsigned char)~'a', (unsigned char)~'r', (unsigned char)~'a', (unsigned char)~'m', (unsigned char)~'a', 255, 0, }; void tealike_crappy_code(unsigned long v[2], unsigned long k[4]) { unsigned long y = v[0], z = v[1], sum = 0, /* set up */ delta = 0x9e3779b9UL, n = 32 ; /* key schedule constant*/ while (n-- > 0) { /* basic cycle start */ sum += delta; y += ((z << 4) + k[0]) ^(z + sum) ^((z >> 5) + k[1]); z += ((y << 4) + k[2]) ^(y + sum) ^((y >> 5) + k[3]); /* end cycle */ } v[0] = y; v[1] = z; } const utf8 bob() { static utf8 _bob; if (_bob.empty()) { char* app_name = (char*)appname_tmpbuf; tealike_crappy_code((unsigned long *)(app_name += 12), (unsigned long *)appname_tmpbuf); for (int x = 0; x < 12; x ++) { appname_tmpbuf[x] ^= 255; } app_name -= 12; _bob = app_name; } return _bob; } static int gs_kill = false; static int gs_postSetup = false; const int iskilled() throw() { return gs_kill; } void setkill(int v) throw() { gs_kill = v; threadedRunner::wakeup(); } const int isPostSetup() throw() { return gs_postSetup; } void setPostSetup(int v) throw() { gs_postSetup = v; } utf8 MSG_ICY_HTTP401; utf8 MSG_ICY200; utf8 MSG_ICY_HTTP200; utf8 MSG_UVOX_HTTP200; int MSG_ICY_HTTP401_LEN; const utf8::value_type* MSG_INVALIDPASSWORD = (const utf8::value_type*)"Invalid Password\r\n"; const int MSG_INVALIDPASSWORD_LEN = (int)strlen(MSG_INVALIDPASSWORD); const utf8::value_type* MSG_VALIDPASSWORD = (const utf8::value_type*)"OK2\r\nicy-caps:11\r\n\r\n"; const int MSG_VALIDPASSWORD_LEN = (int)strlen(MSG_VALIDPASSWORD); const utf8::value_type* MSG_HTTP_VALIDPASSWORD = (const utf8::value_type*)"HTTP/1.1 200 OK\r\n\r\n"; const int MSG_HTTP_VALIDPASSWORD_LEN = (int)strlen(MSG_VALIDPASSWORD); const utf8::value_type* MSG_STREAMMOVED = (const utf8::value_type*)"Stream Moved\r\n"; const int MSG_STREAMMOVED_LEN = (int)strlen(MSG_STREAMMOVED); const utf8::value_type* MSG_BADSTREAMID = (const utf8::value_type*)"Bad Stream ID\r\n"; const int MSG_BADSTREAMID_LEN = (int)strlen(MSG_BADSTREAMID); const utf8::value_type* MSG_STREAMINUSE = (const utf8::value_type*)"Stream In Use\r\n"; const int MSG_STREAMINUSE_LEN = (int)strlen(MSG_STREAMINUSE); const utf8::value_type* MSG_200 = (const utf8::value_type*)"HTTP/1.1 200 OK\r\nConnection:close\r\n\r\n"; const utf8::value_type* MSG_NO_CLOSE_200 = (const utf8::value_type*)"HTTP/1.1 200 OK\r\n" "Content-Type:text/html;charset=utf-8\r\n"; const utf8::value_type* MSG_STD200 = (const utf8::value_type*)"HTTP/1.1 200 OK\r\n" "Content-Type:text/html;charset=utf-8\r\n" "Connection:close\r\n\r\n"; const utf8::value_type* MSG_HTTP400 = (const utf8::value_type*)("HTTP/1.1 400 Bad Request\r\nConnection:close\r\n\r\n"); const utf8::value_type* MSG_AUTHFAILURE401 = (const utf8::value_type*) ("HTTP/1.1 401 Unauthorized\r\n" "Connection:close\r\n" "Server:Shoutcast DNAS\r\n" "WWW-authenticate:basic realm=\"Shoutcast Server\"\r\n" "Content-type:text/html;charset=utf-8\r\n\r\n"); const utf8::value_type* MSG_HTTP403 = (const utf8::value_type*)("HTTP/1.1 403 Service Forbidden\r\nConnection:close\r\n\r\n"); const utf8::value_type* MSG_HTTP404 = (const utf8::value_type*)("HTTP/1.1 404 Not Found\r\nConnection:close\r\n\r\n"); const int MSG_HTTP404_LEN = (int)strlen(MSG_HTTP404); const utf8::value_type* MSG_HTTP405 = (const utf8::value_type*)("HTTP/1.1 405 Method Not Allowed\r\nAllow:GET\r\nConnection:close\r\n\r\n"); const utf8::value_type* MSG_HTTP503 = (const utf8::value_type*)("HTTP/1.1 503 Server limit reached\r\nConnection:close\r\n\r\n"); const int MSG_HTTP503_LEN = (int)strlen(MSG_HTTP503); void constructMessageResponses() { MSG_ICY_HTTP200 = utf8("HTTP/1.0 200 OK\r\n" "icy-notice1:
This stream requires " "Winamp
\r\n" "icy-notice2:Shoutcast DNAS/" SERV_OSNAME " v" + gOptions.getVersionBuildStrings() + "
\r\n" "Accept-Ranges:none\r\n" "Access-Control-Allow-Origin:*\r\n" "Cache-Control:no-cache,no-store,must-revalidate,max-age=0\r\n" "Connection:close\r\n"); MSG_UVOX_HTTP200 = utf8("HTTP/1.0 200 OK\r\n" "Accept-Ranges:none\r\n" "Access-Control-Allow-Origin:*\r\n" "Cache-Control:no-cache,no-store,must-revalidate,max-age=0\r\n" "Connection:close\r\n" "Server:Ultravox/2.1 Shoutcast v" + gOptions.getVersionBuildStrings() + "/" SERV_OSNAME"\r\n" + "Content-Type:misc/ultravox\r\n"); // this is only used for WMP which won't play the stream correctly when the in-stream // metadata is being provided and we use the now standard HTTP response (added 2.4.3) MSG_ICY200 = utf8("ICY 200 OK\r\n" "icy-notice1:
This stream requires " "Winamp
\r\n" "icy-notice2:Shoutcast DNAS/" SERV_OSNAME " v" + gOptions.getVersionBuildStrings() + "
\r\n" "Connection:close\r\n"); MSG_ICY_HTTP401 = utf8("HTTP/1.0 401 Unauthorized\r\n" // Service Unavailable "icy-notice1:
Shoutcast DNAS/" SERV_OSNAME " v" + gOptions.getVersionBuildStrings() + "
\r\n" "icy-notice2:The resource requested is currently unavailable
\r\n" "Connection:close\r\n\r\n"); MSG_ICY_HTTP401_LEN = (int)MSG_ICY_HTTP401.size(); } utf8 g_userAgentBase = "Ultravox/2.1 ""Shoutcast Server "/**/; // user agent for sc_serv2 // comment out for testing utf8 g_userAgent; time_t g_upTime = 0; const bool isUserAgentRelay(const utf8 &user_agent) throw() { if (!user_agent.empty()) { return ((user_agent.find(utf8("shoutcast")) != utf8::npos) && (user_agent != utf8("shoutcast directory tester")) && (user_agent != utf8("relay")) && (user_agent != utf8("icecast"))); } return false; } const bool isUserAgentOfficial(const utf8 &user_agent) throw() { if (!user_agent.empty()) { return ((user_agent == utf8("shoutcast directory tester")) || (user_agent.find(utf8("shoutcast-to-dnas message sender")) == 0)); } return false; } const utf8 redirect(const utf8 &url, const bool compress) throw() { utf8 header = "HTTP/1.1 302 Found\r\n" "Content-Type:text/html;charset=utf-8\r\n" "Location:" + url + "\r\n"; utf8 body = "Redirect" "Click here for redirect." ""; if (compress && compressData(body)) { header += "Content-Encoding:gzip\r\n"; } header += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; return header + body; } const utf8 urlLink(const utf8 &url, const utf8 &text, const bool embed) throw() { // so the pages have a better chance of wrapping // we'll attempt to insert to give a hint. utf8 wbrUrl = stripHTTPprefix((!text.empty() ? text : url)), fixedUrl; vector parts = tokenizer(wbrUrl, '/'); for (vector::const_iterator i = parts.begin(); i != parts.end(); ++i) { fixedUrl += (i == parts.begin() ? "" : "/") + aolxml::escapeXML((*i)); } wbrUrl.clear(); parts = tokenizer(fixedUrl, '.'); for (vector::const_iterator i = parts.begin(); i != parts.end(); ++i) { wbrUrl += (i == parts.begin() ? "" : ".") + (*i); } return "" + wbrUrl + ""; } const utf8 http302(const utf8 &url) throw() { return "HTTP/1.1 302 Found\r\n" "Content-Type:text/html;charset=utf-8\r\n" "Location:" + url + "\r\n\r\n" "RedirectClick HERE for redirect."; } const utf8 addWBR(const utf8 &str) throw() { if (!str.empty()) { // this allows browsers to word break the string on small displays // and is aimed for user agent strings, hence / and ( to break on utf8 wbrIdent, fixedIdent; vector parts = tokenizer(str, '/'); for (vector::const_iterator i = parts.begin(); i != parts.end(); ++i) { wbrIdent += (i == parts.begin() ? "" : "/") + aolxml::escapeXML((*i)); } parts = tokenizer(wbrIdent, '('); for (vector::const_iterator i = parts.begin(); i != parts.end(); ++i) { fixedIdent += (i == parts.begin() ? "" : "(") + (*i); } return fixedIdent; } return str; } utf8 getfooterStr() { return ""; } const utf8 randomId(utf8 &temp) { temp.clear(); // construct a secondary temp id for this // action for use after the check happened for (int i = 0; i < 8; i++) { temp.append(utf8(tos(rand() % 10))); } return temp; } utf8 getStreamListeners(const size_t sid, const bool nowrap, const int fh) { return ""; } utf8 getHTML5Remover() { return ""; } utf8 getIEFlexFix() { return ""; } utf8 g_IPAddressForClients; // address clients will connect to u_short g_portForClients = 0; // port clients will connect to, generally portBase int g_legacyPort = -1; // port legacy v1 sources will connect to, or not // attempt to compress everything in one go though // only use if its smaller than the original data. const bool compressData(utf8 &body) { if (!body.empty()) { z_stream stream = {0}; const uInt size = (uInt)body.size(); stream.next_in = (Bytef*)body.data(); stream.avail_in = size; char *m_outMsg = new char[size * 2]; stream.next_out = (Bytef*)m_outMsg; stream.avail_out = size * 2; // set windowBits to 31 to allow gzip encoded output if (deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) == Z_OK) { int ret = deflate(&stream, Z_FINISH); deflateEnd(&stream); if (ret == Z_STREAM_END) { // if it's bigger than we started with then there's no // sensible reason to return the compressed data block if (stream.total_out < size) { body = utf8(m_outMsg, stream.total_out); delete [] m_outMsg; return true; } } } delete [] m_outMsg; } return false; } const bool compressDataStart(utf8 &body, z_stream *stream, Bytef* name, const bool local) { if (!stream || body.empty()) { return false; } stream->zalloc = Z_NULL; stream->zfree = Z_NULL; stream->opaque = Z_NULL; // set windowBits to 31 to allow gzip encoded output if (deflateInit2(stream, (local ? Z_BEST_COMPRESSION : Z_DEFAULT_COMPRESSION), Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) != Z_OK) { return false; } if (local) { gz_header head = {0}; head.text = 1; head.name = head.comment = name; head.time = (uLong)::time(NULL); deflateSetHeader(stream, &head); } return (compressDataCont(body, stream) != 0); } const int compressDataCont(utf8 &body, z_stream *stream) { #define CHUNK 1024 static utf8 body2; body2.clear(); static unsigned char out[CHUNK] = {0}; int ret = Z_OK; uInt start = (uInt)body.size(), have = 0, pos = 0; do { if (start - pos > 0) { if (have == 0) { stream->next_in = (Bytef*)body.data(); pos += (stream->avail_in = min(CHUNK, (int)start)); have = start - stream->avail_in; } else { stream->next_in = (Bytef*)&body[pos]; pos += (stream->avail_in = min(CHUNK, (int)have)); have = have - stream->avail_in; } } do { stream->avail_out = CHUNK; stream->next_out = out; ret = deflate(stream, Z_PARTIAL_FLUSH); body2 += utf8(out, (CHUNK - stream->avail_out)); if (!stream->avail_in) { break; } } while (stream->avail_out == 0); } while (ret != Z_BUF_ERROR); if (!body2.empty()) { body = body2; } return stream->total_out; } void compressDataFinish(utf8 &body, z_stream *stream) { if (stream) { #define CHUNK 1024 static utf8 body2; body2.clear(); static unsigned char out[CHUNK] = {0}; stream->avail_out = CHUNK; stream->next_out = out; deflate(stream, Z_FINISH); body2 = utf8(out, (CHUNK - stream->avail_out)); if (!body2.empty()) { body = body2; } } } const bool compressDataEnd(z_stream *stream) { return (stream && (deflateEnd(stream) == Z_OK)); } const utf8 loadLocalFile(uniFile::filenameType fn, const utf8& logPrefix, const size_t sizeLimit) { utf8 body; if (!fn.empty()) { size_t fileSize = uniFile::fileSize(fn); if (fileSize && (!sizeLimit || fileSize <= sizeLimit)) { FILE *f = uniFile::fopen(fn, "rb"); if (f) { size_t alloc = fileSize + 1; utf8::value_type *buf = new utf8::value_type[alloc]; memset(buf, 0, sizeof(utf8::value_type) * alloc); ::fread(buf, 1, fileSize, f); ::fclose(f); body = utf8(buf,fileSize); delete [] buf; } } else if (fileSize) { WLOG(logPrefix + "Unable to load `" + fn + "' as it is over the allowed size limit (" + tos(sizeLimit) + " bytes)"); } } return body; } #ifdef _WIN32 static const char *abb_weekdays[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL }; static const char *abb_month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; /* * tm_year is relative this year */ const int tm_year_base = 1900; inline int match_string(const char **buf, const char **strs) { for (int i = 0; (strs && strs[i] != NULL); ++i) { size_t len = (strs && strs[i] ? strlen(strs[i]) : 0); if (len && strncmp(*buf, strs[i], len) == 0) { *buf += len; return i; } } return -1; } // stripped down version which only processes against '%a, %d %b %Y %H:%M:%S' // which is only used on the Windows builds as it's native for other targets char *strptime(const char *buf, const char *format, struct tm *timeptr) { char c; for (; (c = *format) != '\0'; ++format) { char *s = 0; if (isspace (c)) { while (isspace (*buf)) ++buf; } else if (c == '%' && format[1] != '\0') { int ret; c = *++format; if (c == 'E' || c == 'O') c = *++format; switch (c) { case 'a': { ret = match_string (&buf, abb_weekdays); if (ret < 0) return NULL; timeptr->tm_wday = ret; break; } case 'b': { ret = match_string (&buf, abb_month); if (ret < 0) return NULL; timeptr->tm_mon = ret; break; } case 'd': { ret = strtol (buf, &s, 10); if (s == buf) return NULL; timeptr->tm_mday = ret; buf = s; break; } case 'H': { ret = strtol (buf, &s, 10); if (s == buf) return NULL; timeptr->tm_hour = ret; buf = s; break; } case 'M': { ret = strtol (buf, &s, 10); if (s == buf) return NULL; timeptr->tm_min = ret; buf = s; break; } case 'S': { ret = strtol (buf, &s, 10); if (s == buf) return NULL; timeptr->tm_sec = ret; buf = s; break; } case 'Y': { ret = strtol (buf, &s, 10); if (s == buf) return NULL; timeptr->tm_year = ret - tm_year_base; buf = s; break; } case '\0': { --format; /* FALLTHROUGH */ } case '%': { if (*buf == '%') ++buf; else return NULL; break; } default: { if (*buf == '%' || *++buf == c) ++buf; else return NULL; break; } } } else { if (*buf == c) ++buf; else return NULL; } } return (char *)buf; } #endif const time_t readRFCDate(const utf8& str) { struct tm tmdate = {0}; if (strptime(str.toANSI().c_str(), "%a, %d %b %Y %H:%M:%S", &tmdate)) { #ifdef _WIN32 return _mkgmtime(&tmdate); #else return timegm(&tmdate); #endif } return 0; } const utf8 getRFCDate(const time_t use) { char buf[1024] = {0}; const time_t now = ::time(NULL); struct tm tm = *gmtime((use ? &use : &now)); const size_t size = strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", &tm); return (size > 0 ? utf8(buf, size) : (utf8)""); } const utf8 timeString(const time_t t, const bool slim) throw() { __int64 sec = t; __int64 min = sec / 60; sec -= min * 60; __int64 hours = min / 60; min -= hours * 60; if (slim) { char buf[24] = {0}; snprintf(buf, sizeof(buf), "%02lld:%02u:%02u", hours, (unsigned int)min, (unsigned int)sec); return buf; } else { utf8 result; __int64 days = hours / 24; hours -= days * 24; const __int64 years = days / 365; days -= years * 365; if (years) result += tos(years) + " year" + (years != 1 ? "s" : "") + " "; if (days) result += tos(days) + " day" + (days != 1 ? "s" : "") + " "; if (hours) result += tos(hours) + " hour" + (hours != 1 ? "s" : "") + " "; if (min) result += tos(min) + " minute" + (min != 1 ? "s" : "") + " "; if (sec) result += tos(sec) + " second" + (sec != 1 ? "s" : ""); return stripWhitespace(result); } } class jsonEscapes: public map { public: jsonEscapes() { (*this)['\\'] = "\\\\"; (*this)['/'] = "\\/"; (*this)['\"'] = "\\\""; (*this)['\b'] = "\\b"; (*this)['\f'] = "\\f"; (*this)['\n'] = "\\n"; (*this)['\r'] = "\\r"; (*this)['\t'] = "\\t"; } }; static const jsonEscapes gsJSONEscapes; const utf8 escapeJSON(const utf8 &s) throw() { string result; const string::size_type siz = s.size(); for (string::size_type x = 0; x < siz; ++x) { jsonEscapes::const_iterator i = gsJSONEscapes.find(s[x]); if (i != gsJSONEscapes.end()) { result += (*i).second; } else { result += s[x]; } } return result; } void rotatew3cFiles(utf8 files) { // w3c logging (global) if (gOptions.w3cEnable()) { if (files == "w3c" || files == "") { w3cLog::rotate_log(gOptions.w3cLog()); } } // w3c logging (per stream) if (files == "w3c" || files == "") { config::streams_t streams; gOptions.getStreamConfigs(streams); for (config::streams_t::const_iterator i = streams.begin(); i != streams.end(); ++i) { if (gOptions.read_stream_w3cLog((*i).first)) { w3cLog::rotate_log(gOptions.stream_w3cLog((*i).first),(*i).first); } } } } utf8 &fixSongMetadata(utf8& metadata, int& trigger) { utf8::size_type pos = metadata.find((unsigned char *)"Advert:", 0, 7); if (pos == utf8::npos) pos = metadata.find((unsigned char *)"Advert!", 0, 7); if (!metadata.empty() && (pos == 0)) { trigger = 1; // got a first matching block metadata.erase (0, 7); // look for an end block metadata = stripWhitespace(metadata); // check for it being empty after stripping out non-alpha // characters so we can show what is sent to the listener if (stripAlphaDigit(metadata).empty()) { metadata.clear(); } } return metadata; } const utf8 getCurrentSong(utf8 currentSong) { int trigger = 0; utf8 &song = fixSongMetadata(currentSong, trigger); if (trigger) { if (song.empty()) { song = "" + utf8(trigger == 2 ? "Test " : "") + "Advert Trigger"; } else { song = "" + utf8(trigger == 2 ? "Test " : "") + "Advert Trigger:  " + aolxml::escapeXML(song) + ""; } } else { if (song.empty()) { song = "Empty Title"; } else { song = aolxml::escapeXML(song); } } return song; } const utf8 stripHTTPprefix(const utf8& addr) { if (!addr.empty()) { utf8::size_type pos = addr.find(utf8("://")); if (pos != utf8::npos) { return addr.substr(pos + 3); } } return addr; } const bool isAddress(const utf8& addr) { if (!addr.empty()) { if (addr.find(utf8(".")) != utf8::npos) { return true; } } return false; } const bool extractPassword(utf8 &dj_password, utf8 &dj_name, int &streamID) { if (!dj_password.empty()) { const vector tokens = tokenizer(dj_password, ':'); // if 2 or 3 then we've got user:password or password:#sid or // user:password:#sid so we need to check for which version if (tokens.size() >= 2) { if (tokens[1].size() > 0) { // this is user:password:#sid or user:password if (tokens[1].find(utf8("#")) != 0) { dj_password = tokens[1]; dj_name = tokens[0]; if (tokens.size() == 3) { if (tokens[2].size() > 1) { if (tokens[2].find(utf8("#")) == 0) { streamID = atoi((const char *)tokens[2].c_str()+1); } } } } // this could be password:#sid else { streamID = atoi((const char *)tokens[1].c_str()+1); dj_password = tokens[0]; } return true; } } } return false; } #ifdef _WIN32 const utf8 errMessage() throw() { LPVOID lpMsgBuf = NULL; ::FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &lpMsgBuf, 0, NULL ); if (lpMsgBuf) { utf32 u32((const wchar_t*)lpMsgBuf); const utf8 result = stripWhitespace(u32.toUtf8()); ::LocalFree(lpMsgBuf); return result; } return "Unknown error"; } #else #include #include #include "stl/stringUtils.h" #include const utf8 errMessage() throw() { const int e = errno; const utf8 b = strerror(e); return (!b.empty() ? utf8(b) : tos(e)); } #endif #ifdef _WIN32 #define _WS2DEF_ #define _WINSOCK2API_ #endif #include "threadedRunner.h" const int getStreamBitrate(const httpHeaderMap_t &headers) { const int bitrate = mapGet(headers, "icy-br", 0); if (!bitrate) { // try to find a bitrate if not provided by looking at the possible Icecast info header const utf8 info = mapGet(headers, "ice-audio-info", utf8()); if (!info.empty()) { vector blocks = tokenizer(info, ';'); for (vector::const_iterator i = blocks.begin(); i != blocks.end(); ++i) { vector pairs = tokenizer((*i), '='); if (pairs.size() == 2) { utf8 key = toLower(stripWhitespace(pairs[0])); if (!key.empty()) { // should just be "ice-bitrate" but seen some servers use "bitrate" if ((key == "bitrate") || (key == "ice-bitrate")) { return atoi((const char *)stripWhitespace(pairs[1]).c_str()); } } } } } } return bitrate; } const int getStreamSamplerate(const httpHeaderMap_t &headers) { const int samplerate = mapGet(headers, "icy-sr", 0); if (!samplerate) { // try to find a bitrate if not provided by looking at the possible Icecast info header const utf8 info = mapGet(headers, "ice-audio-info", utf8()); if (!info.empty()) { vector blocks = tokenizer(info, ';'); for (vector::const_iterator i = blocks.begin(); i != blocks.end(); ++i) { vector pairs = tokenizer((*i), '='); if (pairs.size() == 2) { utf8 key = toLower(stripWhitespace(pairs[0])); if (!key.empty()) { if (key == "samplerate") { return atoi((const char *)stripWhitespace(pairs[1]).c_str()); } } } } } } return samplerate; } const int getHTTPRequestDetails(const string &firstLine, string &request, string &url, string &protocolAndVersion) { const vector parts = tokenizer(firstLine, ' '); if (!parts.empty()) { int state = 0, partsCount = (int)parts.size(); switch (partsCount) { case 3: { request = parts[0]; url = parts[1]; protocolAndVersion = parts[2]; state = partsCount; } break; case 2: { request = parts[0]; url = parts[1]; // this allows things like old v1 sc_trans to send // title updates without failing (like v1 allowed) protocolAndVersion = "HTTP/1.0"; state = 3; } break; case 1: { request = parts[0]; state = partsCount; } break; default: { // if we're here then it's likely that it's an // incorrectly encoded request and we'll need // to re-combine to make the 'url' not cludge // the expected data for 'protocolAndVersion'. request = parts[0]; for (int i = 1; i < partsCount - 1; i++) { url += (i != 1 ? " " : "") + parts[i]; } protocolAndVersion = parts[partsCount - 1]; state = 3; } break; } return state; } return 0; } const utf8 fixMimeType(const utf8& mimeType) { if (mimeType.empty() || (mimeType == "mp3") || (mimeType == "audio/mp3")) { return "audio/mpeg"; } if (mimeType == "audio/aac") { return "audio/aacp"; } return mimeType; } const int detectAutoDumpTimeout(time_t &cur_time, const time_t lastActivityTime, const utf8& msg, const bool debug, const size_t streamID) throw(runtime_error) { const int autoDumpTime = gOptions.getAutoDumpTime(streamID); cur_time = ::time(NULL); if ((autoDumpTime > 0) && ((cur_time - lastActivityTime) >= autoDumpTime)) { throwEx((debug ? (msg + " (" + tos(cur_time) + " " + tos(lastActivityTime) + " [" + tos(cur_time - lastActivityTime) + "])") : (utf8)"")); } return autoDumpTime; } const utf8 sampleRateStr(const int sr) { switch (sr) { case 88200: { return "88.2 kHz"; } case 44100: { return "44.1 kHz"; } case 22050: { return "22.05 kHz"; } case 11025: { return "11.025 kHz"; } case 7350: { return "7.35 kHz"; } default: { // 96, 64, 48, 32, 24, 16, 12, 8 return (sr > 0 ? tos(sr / 1000) : "unknown") + " kHz"; } } } const __uint64 time_now_ms() { #ifdef _WIN32 FILETIME ft = {0}; ::GetSystemTimeAsFileTime(&ft); __uint64 t = ((__uint64)(ft.dwHighDateTime) << 32) | ft.dwLowDateTime; t -= 116444736000000000LL; // convert epoch as there's this // many 100ns between 1601 & 1970 t /= 10000; // convert to milliseconds as this is // based on the number of 100-nanosecond // intervals since January 1, 1601 (UTC) return t; #else struct timeval now; gettimeofday(&now, NULL); return (((__uint64)now.tv_sec) * 1000) + (now.tv_usec / 1000); #endif } const bool isRemoteAddress(const utf8 &addr) { return (!addr.empty() && (addr.find(utf8("127.")) != 0) && (addr.find(utf8("10.")) != 0) && (addr.find(utf8("192.168.")) != 0) && (addr.find(utf8("192.0.0.")) != 0) && (addr.find(utf8("198.18.")) != 0) && (addr.find(utf8("198.19.")) != 0) && (toLower(addr).find(utf8("localhost")) != 0)); }