/* * httpd.c * * See the README file for copyright information and how to reach the author. * */ #include "lib/python.h" // at first du to symbol conflict with older python headers #include #include #include #include "lib/common.h" #include "lib/epgservice.h" #include "lib/curl.h" #include "lib/wol.h" #include "svdrpclient.h" #include "httpd.h" const char* logPrefix = LOG_PREFIX; class Python; const char* realm = "Maintenance"; //*************************************************************************** // EPG Http Daemon //*************************************************************************** int cEpgHttpd::shutdown = no; cEpgHttpd* cEpgHttpd::singleton = 0; const char* confDir = (char*)confDirDefault; //*************************************************************************** // Rights Management //*************************************************************************** cEpgHttpd::UserRight cEpgHttpd::userrights[] = { { "/data/parameters", umAll|umNologin }, { "/data/channels", umAll|umNologin }, { "/data/vdrs", umAll|umNologin }, { "/data/login", umAll|umNologin }, { "/data/channellogo", umAll|umNologin }, { "/data/eventimg", umAll|umNologin }, { "/data/moviemedia", umAll|umNologin }, { "/data/seriesmedia", umAll|umNologin }, { "/data/genres", umAll|umNologin }, { "/data/categories", umAll|umNologin }, { "/data/events", umAll }, { "/data/event", umAll }, { "/data/timers", umAll }, { "/data/recordings", umAll }, { "/data/recording", umAll }, { "/data/recordingdirs", umAll }, { "/data/pendingtimerjobs", umAll }, { "/data/deltimerjob", umTimerEdit }, { "/data/donetimers", umAll }, { "/data/donetimer", umAll }, { "/data/searchtimers", umAll }, { "/data/updatesearchtimer", umAll }, { "/data/updaterecordings", umAll }, { "/data/replayrecording", umRecordings }, { "/data/channelswitch", umAll }, { "/data/hitkey", umAll }, { "/data/log", umAll|umNologin }, { "/data/debug", umAll }, { "/data/proxy", umAll }, { "/data/wakeupvdr", umAll }, { "/data/messages", umAll }, // needed for store of needLogin :o ! { "/data/save-parameters", umAll }, // data request { "/data/users", umConfig }, // data store { "/data/sendmail", umAll }, { "/data/save-channels", umConfigEdit }, { "/data/delete-timerjobs", umTimerEdit }, { "/data/store-donetimers", umTimerEdit }, { "/data/save-timer", umTimerEdit }, { "/data/save-searchtimer", umSearchTimerEdit }, { "/data/search", umSearchTimer }, { "/data/save-users", umConfigEdit }, { "/data/renamerecording", umRecordingsEdit }, { "/data/deleterecording", umRecordingsEdit }, { "/data/markmessages", umAll }, { 0, umNone } }; UserMask cEpgHttpd::toRightMask(const char* url) { for (int i = 0; userrights[i].url; i++) { if (strcmp(userrights[i].url, url) == 0) return (UserMask)userrights[i].mask; } return umNone; } //*************************************************************************** // Object //*************************************************************************** cEpgHttpd::cEpgHttpd() { daemon = 0; connection = 0; useeventsDb = 0; mapDb = 0; imageDb = 0; imageRefDb = 0; vdrDb = 0; timerDb = 0; timersDoneDb = 0; searchtimerDb = 0; movieDb = 0; movieActorDb = 0; movieActorsDb = 0; movieMediaDb = 0; seriesDb = 0; seriesEpisodeDb = 0; seriesActorsDb = 0; seriesMediaDb = 0; recordingDirDb = 0; recordingListDb = 0; userDb = 0; messageDb = 0; currentSession = 0; loginWithSession = no; lastSdWatchdogAt = time(0); selectEventsAt = 0; selectEventsNext = 0; selectEventsStartInRange = 0; selectGenres = 0; selectCategories = 0; selectEvent = 0; selectEventByTime = 0; selectAllMap = 0; selectPendingTimerActions = 0; selectDoneTimers = 0; selectMapById = 0; selectVdrs = 0; selectUsers = 0; updateMap = 0; updateTimerAName = 0; updateDoneAName = 0; selectAllTimer = 0; selectTimerById = 0; selectTimerByEventId = 0; selectAllSearchTimer = 0; selectMovie = 0; selectMovieActors = 0; selectMovieMedia = 0; selectSerie = 0; selectSeriesEpisode = 0; selectSeriesMedia = 0; selectActiveVdrs = 0; selectRecordingDirs = 0; selectRecordingByPath = 0; selectChannelFromMap = 0; selectUserByMd5 = 0; selectAllRecordings = 0; selectRecordingForEventByLv = 0; selectPendingMessages = 0; selectWebUsers = 0; withutf8 = no; search = new cSearchTimer(this); ptyRecName = new Python("recording", "name"); setlocale(LC_CTYPE, ""); const char* lang = setlocale(LC_CTYPE, 0); if (lang) { tell(0, "Set locale to '%s'", lang); if ((strcasestr(lang, "UTF-8") != 0) || (strcasestr(lang, "UTF8") != 0)) { tell(0, "detected UTF-8"); withutf8 = yes; } } else { tell(0, "Warning: Detecting locale setting for LC_CTYPE failed"); } tzset(); // init timezone environment } cEpgHttpd::~cEpgHttpd() { cSystemNotification::notify(evStopping); delete search; delete ptyRecName; } //*************************************************************************** // Init / Exit //*************************************************************************** int cEpgHttpd::init() { char* dictPath = 0; #if MHD_VERSION >= 0x00095102 int mhdFlags = MHD_USE_EPOLL_INTERNALLY; #else int mhdFlags = MHD_USE_SELECT_INTERNALLY; #endif singleton = this; if (readConfig() != success) return fail; tell(0, "Log level is set to (%d)", EpgdConfig.loglevel); if (search->init(confDir) != success) return fail; if (ptyRecName->init(confDir) != success) return fail; // initialize the dictionary asprintf(&dictPath, "%s/epg.dat", confDir); if (dbDict.in(dictPath) != success) { tell(0, "Fatal: Dictionary not loaded, aborting!"); return 1; } tell(0, "Dictionary '%s' loaded", dictPath); free(dictPath); // init database ... cDbConnection::init(); cDbConnection::setEncoding(withutf8 ? "utf8": "latin1"); // mysql use latin1 for ISO8851-1 cDbConnection::setHost(EpgdConfig.dbHost); cDbConnection::setPort(EpgdConfig.dbPort); cDbConnection::setName(EpgdConfig.dbName); cDbConnection::setUser(EpgdConfig.dbUser); cDbConnection::setPass(EpgdConfig.dbPass); cDbConnection::setConfPath(confDir); initDb(); if (EpgdConfig.httpUseTls) { int status; char* path = 0; #if MHD_VERSION >= 0x00095102 mhdFlags |= MHD_USE_TLS; #else mhdFlags |= MHD_USE_SSL; #endif asprintf(&path, "%s/server.key", confDir); status = loadFromFile(path, &keyPem); free(path); keyPem.append("\0", 1); asprintf(&path, "%s/server.pem", confDir); status += loadFromFile(path, &certPem); free(path); certPem.append("\0", 1); if (status != success) { tell(0, "The key/certificate files could not be loaded"); return fail; } tell(0, "Loading key and certificate files succeeded"); } // bind to net device - if configured if (!isEmpty(EpgdConfig.httpDevice)) { struct sockaddr_in localSockAddr; const char* bindIp = getIpOf(EpgdConfig.httpDevice); long localAddr; memset((char*)&localSockAddr, 0, sizeof(localSockAddr)); localSockAddr.sin_family = AF_INET; if ((localAddr = inet_addr(bindIp)) == INADDR_NONE) return fail; // set local endpoint memcpy(&localSockAddr.sin_addr, &localAddr, sizeof(struct in_addr)); localSockAddr.sin_port = htons(EpgdConfig.httpPort); tell(0, "Binding listener to '%s' at '%s'", bindIp, EpgdConfig.httpDevice); // establish listener tell(0, "Starting http server ..."); if (!EpgdConfig.httpUseTls) { daemon = MHD_start_daemon(mhdFlags, EpgdConfig.httpPort, 0, 0, // accept policy callback &dispatcher, 0, // access handler callback MHD_OPTION_SOCK_ADDR, (struct sockaddr*)&localSockAddr, MHD_OPTION_END); } else { daemon = MHD_start_daemon(mhdFlags, EpgdConfig.httpPort, 0, 0, // accept policy callback &dispatcher, 0, // access handler callback MHD_OPTION_SOCK_ADDR, (struct sockaddr*)&localSockAddr, MHD_OPTION_HTTPS_MEM_KEY, keyPem.memory, MHD_OPTION_HTTPS_MEM_CERT, certPem.memory, MHD_OPTION_END); } } else { // establish listener tell(0, "Starting http server ..."); if (!EpgdConfig.httpUseTls) { daemon = MHD_start_daemon(mhdFlags, EpgdConfig.httpPort, 0, 0, // accept policy callback &dispatcher, 0, // access handler callback MHD_OPTION_END); } else { daemon = MHD_start_daemon(mhdFlags, EpgdConfig.httpPort, 0, 0, // accept policy callback &dispatcher, 0, // access handler callback MHD_OPTION_HTTPS_MEM_KEY, keyPem.memory, MHD_OPTION_HTTPS_MEM_CERT, certPem.memory, MHD_OPTION_END); } } if (!daemon) { tell(0, "Error: Start of http server failed"); return fail; } tell(0, "Listener at port %d established, waiting for connections", EpgdConfig.httpPort); cSystemNotification::notify(evReady); cSystemNotification::getWatchdogState(10); return success; } int cEpgHttpd::exit() { singleton = 0; if (daemon) MHD_stop_daemon(daemon); exitDb(); cDbConnection::exit(); return done; } //*************************************************************************** // Init DB //*************************************************************************** cDbFieldDef endTimeDef("ENDTIME", "cnt_starttime+cnt_duration", cDBS::ffInt, 10, cDBS::ftData); cDbFieldDef startTimeDef("STARTTIME", "cnt_starttime", cDBS::ffInt, 10, cDBS::ftData); cDbFieldDef mergeDef("MERGE", "merge", cDBS::ffAscii, 50, cDBS::ftData); cDbFieldDef imageidDef("IMAGEID", "imageid", cDBS::ffUBigInt, 0, cDBS::ftData); cDbFieldDef matchDensityTitleDef("MATCHDENSITYTITLE", "matchdensitytitle", cDBS::ffInt, 0, cDBS::ftData); cDbFieldDef matchDensityShorttextDef("MATCHDENSITYSHORTTEXT", "matchdensityshorttext", cDBS::ffInt, 0, cDBS::ftData); //cDbFieldDef timerStateDef("STATE", "state", cDBS::ffAscii, 100, cDBS::ftData); //cDbFieldDef timerActionDef("ACTION", "action", cDBS::ffAscii, 100, cDBS::ftData); //*************************************************************************** int cEpgHttpd::initDb() { int status = success; // db connection tell(0, "Connecting to database at '%s:%d'", cDbConnection::getHost(), cDbConnection::getPort()); if (!connection) connection = new cDbConnection(); // open tables vdrDb = new cDbTable(connection, "vdrs"); if (vdrDb->open() != success) return fail; useeventsDb = new cDbTable(connection, "useevents"); if (useeventsDb->open() != success) return fail; mapDb = new cDbTable(connection, "channelmap"); if (mapDb->open() != success) return fail; imageDb = new cDbTable(connection, "images"); if (imageDb->open() != success) return fail; imageRefDb = new cDbTable(connection, "imagerefs"); if (imageRefDb->open() != success) return fail; timerDb = new cDbTable(connection, "timers"); if (timerDb->open() != success) return fail; timersDoneDb = new cDbTable(connection, "timersdone"); if (timersDoneDb->open(yes) != success) return fail; searchtimerDb = new cDbTable(connection, "searchtimers"); if (searchtimerDb->open() != success) return fail; movieDb = new cDbTable(connection, "movie"); if (movieDb->open() != success) return fail; movieActorDb = new cDbTable(connection, "movie_actor"); if (movieActorDb->open() != success) return fail; movieActorsDb = new cDbTable(connection, "movie_actors"); if (movieActorsDb->open() != success) return fail; movieMediaDb = new cDbTable(connection, "movie_media"); if (movieMediaDb->open() != success) return fail; seriesDb = new cDbTable(connection, "series"); if (seriesDb->open() != success) return fail; seriesEpisodeDb = new cDbTable(connection, "series_episode"); if (seriesEpisodeDb->open() != success) return fail; seriesMediaDb = new cDbTable(connection, "series_media"); if (seriesMediaDb->open() != success) return fail; seriesActorsDb = new cDbTable(connection, "series_actor"); if (seriesActorsDb->open() != success) return fail; recordingDirDb = new cDbTable(connection, "recordingdirs"); if (recordingDirDb->open() != success) return fail; recordingListDb = new cDbTable(connection, "recordinglist"); if ((status = recordingListDb->open()) != success) return status; userDb = new cDbTable(connection, "users"); if ((status = userDb->open()) != success) return status; messageDb = new cDbTable(connection, "messages"); if (messageDb->open() != success) return fail; if ((status = cParameters::initDb(connection)) != success) return status; // ---------- // select * // from vdrs selectVdrs = new cDbStatement(vdrDb); selectVdrs->build("select "); selectVdrs->bindAllOut(); selectVdrs->build(" from %s", vdrDb->TableName()); status += selectVdrs->prepare(); // ---------- // select ip, svdrp from vdrs // where state = 'attached' selectActiveVdrs = new cDbStatement(vdrDb); selectActiveVdrs->build("select "); selectActiveVdrs->bind("IP", cDBS::bndOut); selectActiveVdrs->bind("SVDRP", cDBS::bndOut, ", "); selectActiveVdrs->bind("SHAREINWEB", cDBS::bndOut, ", "); selectActiveVdrs->build(" from %s where state = 'attached' and svdrp > 0", vdrDb->TableName()); status += selectActiveVdrs->prepare(); // ---------- // select * // from users selectUsers = new cDbStatement(userDb); selectUsers->build("select "); selectUsers->bindAllOut(); selectUsers->build(" from %s", userDb->TableName()); status += selectUsers->prepare(); // select distinct(genre) // from events selectGenres = new cDbStatement(useeventsDb); selectGenres->build("select distinct("); selectGenres->bind("Genre", cDBS::bndOut); selectGenres->build(") from %s", useeventsDb->TableName()); status += selectGenres->prepare(); // select distinct(category) // from events selectCategories = new cDbStatement(useeventsDb); selectCategories->build("select distinct("); selectCategories->bind("Category", cDBS::bndOut); selectCategories->build(") from %s", useeventsDb->TableName()); status += selectCategories->prepare(); // -> select events running at specific time // // select e.useid, e.channelid, // e.imagecount, e.title, e.shorttext, e.shortdescription, // e.starttime, e.duration, e.category, e.genre, e.tipp, e.numrating // from eventsviewplain e, (select distinct channelid,channelname,ord,visible from channelmap) c // where // e.channelid = c.channelid // and c.visible & 1 // and e.starttime <= ? // and e.cnt_starttime+cnt_duration >= ? // and e.channelid like ? // and e.updflg in (...) // order by c.ord, e.cnt_starttime selectEventsAt = new cDbStatement(useeventsDb); endTime.setField(&endTimeDef); selectEventsAt->build("select "); selectEventsAt->setBindPrefix("e."); selectEventsAt->bind("UseId", cDBS::bndOut); selectEventsAt->bind("ChannelId", cDBS::bndOut, ", "); // selectEventsAt->bind("UpdFlg", cDBS::bndOut, ", "); selectEventsAt->bind("ImageCount", cDBS::bndOut, ", "); selectEventsAt->bind("Title", cDBS::bndOut, ", "); selectEventsAt->bind("ShortText", cDBS::bndOut, ", "); selectEventsAt->bind("ShortDescription", cDBS::bndOut, ", "); selectEventsAt->bind("StartTime", cDBS::bndOut, ", "); selectEventsAt->bind("Duration", cDBS::bndOut, ", "); selectEventsAt->bind("Category", cDBS::bndOut, ", "); selectEventsAt->bind("Genre", cDBS::bndOut, ", "); selectEventsAt->bind("Tipp", cDBS::bndOut, ", "); selectEventsAt->bind("Numrating", cDBS::bndOut, ", "); selectEventsAt->build(" from eventsviewplain e, (select distinct channelid,channelname,ord,visible from %s) c where ", mapDb->TableName()); selectEventsAt->build("e.%s = c.%s and c.visible & 1", useeventsDb->getField("ChannelId")->getDbName(), mapDb->getField("ChannelId")->getDbName()); selectEventsAt->bindCmp(0, "StartTime", 0, "<=", " and "); selectEventsAt->bindCmp(0, &endTime, ">", " and "); selectEventsAt->bindCmp(0, "ChannelId", 0, "like", " and "); selectEventsAt->build(" and e.updflg in (%s) order by c.ord, e.cnt_starttime", cEventState::getVisible()); status += selectEventsAt->prepare(); // -> select events starting in specified range // // select e.useid, e.channelid, // e.imagecount, e.title, e.shorttext, e.shortdescription, // e.starttime, e.duration, e.category, e.genre, e.tipp, e.numrating // from eventsviewplain e, (select distinct channelid, channelname, ord, visible from channelmap) c // where // e.channelid = c.channelid // and c.visible & 1 // and e.cnt_starttime >= ? // and e.cnt_starttime <= ? // and e.channelid like ? // and e.updflg in (...) // order by c.ord, e.cnt_starttime selectEventsStartInRange = new cDbStatement(useeventsDb); startTime.setField(&startTimeDef); selectEventsStartInRange->build("select "); selectEventsStartInRange->setBindPrefix("e."); selectEventsStartInRange->bind("UseId", cDBS::bndOut); selectEventsStartInRange->bind("ChannelId", cDBS::bndOut, ", "); // selectEventsStartInRange->bind("UpdFlg", cDBS::bndOut, ", "); selectEventsStartInRange->bind("ImageCount", cDBS::bndOut, ", "); selectEventsStartInRange->bind("Title", cDBS::bndOut, ", "); selectEventsStartInRange->bind("ShortText", cDBS::bndOut, ", "); selectEventsStartInRange->bind("ShortDescription", cDBS::bndOut, ", "); selectEventsStartInRange->bind("StartTime", cDBS::bndOut, ", "); selectEventsStartInRange->bind("Duration", cDBS::bndOut, ", "); selectEventsStartInRange->bind("Category", cDBS::bndOut, ", "); selectEventsStartInRange->bind("Genre", cDBS::bndOut, ", "); selectEventsStartInRange->bind("Tipp", cDBS::bndOut, ", "); selectEventsStartInRange->bind("Numrating", cDBS::bndOut, ", "); selectEventsStartInRange->build(" from eventsviewplain e, (select distinct channelid,channelname,ord,visible from %s) c where ", mapDb->TableName()); selectEventsStartInRange->build("e.%s = c.%s and c.visible & 1", useeventsDb->getField("ChannelId")->getDbName(), mapDb->getField("ChannelId")->getDbName()); selectEventsStartInRange->bindCmp(0, "StartTime", 0, ">=", " and "); selectEventsStartInRange->bindCmp(0, &startTime, "<=", " and "); selectEventsStartInRange->bindCmp(0, "ChannelId", 0, "like", " and "); selectEventsStartInRange->build(" and e.updflg in (%s) order by c.ord, e.cnt_starttime", cEventState::getVisible()); status += selectEventsStartInRange->prepare(); // -> select events running after current event // // select e.useid, e.channelid, // e.imagecount, e.title, e.shorttext, e.shortdescription, e.starttime, // e.duration, e.category, e.genre, e.tipp, e.numrating // from // (select // min(concat(e.starttime,e.channelid,e.source)) PK, // e.channelid, // c.ord // from eventsviewplain e, (select distinct channelid,channelname,ord,visible from channelmap) c // where // e.channelid = c.channelid and // e.updflg in ('A','L','P') and // c.visible & 1 and // e.starttime between unix_timestamp() and unix_timestamp() + 127800 // group by e.channelid, c.ord // ) s, // eventsviewplain e // where // s.PK = concat(e.starttime, e.channelid, e.source) and // e.starttime between unix_timestamp() and unix_timestamp() + 127800 // order by s.ord, e.starttime; selectEventsNext = new cDbStatement(useeventsDb); selectEventsNext->build("select "); selectEventsNext->setBindPrefix("e."); selectEventsNext->bind("UseId", cDBS::bndOut); selectEventsNext->bind("ChannelId", cDBS::bndOut, ", "); selectEventsNext->bind("ImageCount", cDBS::bndOut, ", "); selectEventsNext->bind("Title", cDBS::bndOut, ", "); selectEventsNext->bind("ShortText", cDBS::bndOut, ", "); selectEventsNext->bind("ShortDescription", cDBS::bndOut, ", "); selectEventsNext->bind("StartTime", cDBS::bndOut, ", "); selectEventsNext->bind("Duration", cDBS::bndOut, ", "); selectEventsNext->bind("Category", cDBS::bndOut, ", "); selectEventsNext->bind("Genre", cDBS::bndOut, ", "); selectEventsNext->bind("Tipp", cDBS::bndOut, ", "); selectEventsNext->bind("Numrating", cDBS::bndOut, ", "); selectEventsNext->build(" from (select min(concat(e.%s, e.%s, e.%s)) PK, e.%s, c.%s", useeventsDb->getField("STARTTIME")->getDbName(), useeventsDb->getField("CHANNELID")->getDbName(), useeventsDb->getField("CNTSOURCE")->getDbName(), useeventsDb->getField("CHANNELID")->getDbName(), mapDb->getField("ORDER")->getDbName()); selectEventsNext->build(" from eventsviewplain e, (select distinct channelid,channelname,ord,visible from %s) c where ", mapDb->TableName()); selectEventsNext->build("e.%s = c.%s and c.visible & 1 and e.updflg in (%s)", useeventsDb->getField("ChannelId")->getDbName(), mapDb->getField("ChannelId")->getDbName(), cEventState::getVisible()); selectEventsNext->bindCmp(0, "STARTTIME", 0, ">", " and "); selectEventsNext->bindCmp(0, "STARTTIME", 0, "< 127800 +", " and "); selectEventsNext->build(" group by e.%s, c.%s) s,", useeventsDb->getField("CHANNELID")->getDbName(), mapDb->getField("ORDER")->getDbName()); selectEventsNext->build(" eventsviewplain e where "); selectEventsNext->build("s.PK = concat(e.%s, e.%s, e.%s)", useeventsDb->getField("STARTTIME")->getDbName(), useeventsDb->getField("CHANNELID")->getDbName(), useeventsDb->getField("CNTSOURCE")->getDbName()); selectEventsNext->bindCmp(0, "STARTTIME", 0, ">", " and "); selectEventsNext->bindCmp(0, "STARTTIME", 0, "< 127800 +", " and "); selectEventsNext->build(" order by s.%s, e.%s", mapDb->getField("ORDER")->getDbName(), useeventsDb->getField("STARTTIME")->getDbName()); status += selectEventsNext->prepare(); // prepare fields for selects merge.setField(&mergeDef); imageid.setField(&imageidDef); // select imageid, merge, * // from eventsviewplain where // useid = ? selectEvent = new cDbStatement(useeventsDb); selectEvent->build("select "); selectEvent->bind(&imageid, cDBS::bndOut); selectEvent->bind(&merge, cDBS::bndOut, ", "); selectEvent->bindAllOut(", "); selectEvent->build(" from eventsviewplain where "); selectEvent->bind("USEID", cDBS::bndIn | cDBS::bndSet); status += selectEvent->prepare(); // select imageid, merge, * // from eventsviewplain where // channleid = ? // and cnt_starttime <= ? // and cnt_starttime+cnt_duration >= ? selectEventByTime = new cDbStatement(useeventsDb); selectEventByTime->build("select "); selectEventByTime->bind(&imageid, cDBS::bndOut); selectEventByTime->bind(&merge, cDBS::bndOut, ", "); selectEventByTime->bindAllOut(", "); selectEventByTime->build(" from eventsviewplain where "); selectEventByTime->bind("CHANNELID", cDBS::bndIn | cDBS::bndSet); selectEventByTime->bindCmp(0, "STARTTIME", 0, "<=", " and "); selectEventByTime->bindCmp(0, &endTime, ">=", " and "); status += selectEventByTime->prepare(); // select * from movie // where movie_id = ? selectMovie = new cDbStatement(movieDb); selectMovie->build("select "); selectMovie->bindAllOut(); selectMovie->build(" from %s where ", movieDb->TableName()); selectMovie->bind("MOVIEID", cDBS::bndIn | cDBS::bndSet); status += selectMovie->prepare(); // select act.actor_id, act.actor_name, actors.actor_role, media.media_url, media.media_type, // length(media.media_content) // from movie_actor act, movie_actors actors // left outer join movie_media media on (actors.actor_id = media.actor_id) // where act.actor_id = actors.actor_id and actors.movie_id = ? // imageSize.setField(&imageSizeDef); selectMovieActors = new cDbStatement(movieActorDb); selectMovieActors->build("select "); selectMovieActors->setBindPrefix("act."); selectMovieActors->bind("ActorId", cDBS::bndOut); selectMovieActors->bind("ActorName", cDBS::bndOut, ", "); selectMovieActors->setBindPrefix("actors."); selectMovieActors->bind(movieActorsDb, "Role", cDBS::bndOut, ", "); selectMovieActors->setBindPrefix("media."); selectMovieActors->bind(movieMediaDb, "MediaUrl", cDBS::bndOut, ", "); selectMovieActors->bind(movieMediaDb, "MediaType", cDBS::bndOut, ", "); // selectMovieActors->build(", length("); // selectMovieActors->bind(&imageSize, cDBS::bndOut); // selectMovieActors->build(")"); selectMovieActors->clrBindPrefix(); selectMovieActors->build(" from %s act, %s actors left outer join %s media on (actors.%s = media.%s) where ", movieActorDb->TableName(), movieActorsDb->TableName(), movieMediaDb->TableName(), movieActorsDb->getField("ActorId")->getDbName(), movieMediaDb->getField("ActorId")->getDbName()); selectMovieActors->build("act.%s = actors.%s ", movieActorDb->getField("ActorId")->getDbName(), movieActorsDb->getField("ActorId")->getDbName()); selectMovieActors->setBindPrefix("actors."); selectMovieActors->bind(movieActorsDb, "MovieId", cDBS::bndIn | cDBS::bndSet, " and "); status += selectMovieActors->prepare(); // select media_type, media.media_url, length(media_content) // from movie_media // where // media.movie_id = ? selectMovieMedia = new cDbStatement(movieMediaDb); selectMovieMedia->build("select "); selectMovieMedia->bind("MediaUrl", cDBS::bndOut); selectMovieMedia->bind("MediaType", cDBS::bndOut, ", "); // selectMovieMedia->build(", length("); // selectMovieMedia->bind(&imageSize, cDBS::bndOut); // selectMovieMedia->build(")"); selectMovieMedia->build(" from %s where ", movieMediaDb->TableName()); selectMovieMedia->bind("MovieId", cDBS::bndIn | cDBS::bndSet); status += selectMovieMedia->prepare(); // select .... from series // where series_id = ? selectSerie = new cDbStatement(seriesDb); selectSerie->build("select "); selectSerie->bindAllOut(); selectSerie->build(" from %s where ", seriesDb->TableName()); selectSerie->bind("SeriesId", cDBS::bndIn | cDBS::bndSet); status += selectSerie->prepare(); // select .... from series_episode // where episode_id = ? selectSeriesEpisode = new cDbStatement(seriesEpisodeDb); selectSeriesEpisode->build("select "); selectSeriesEpisode->bindAllOut(); selectSeriesEpisode->build(" from %s where ", seriesEpisodeDb->TableName()); selectSeriesEpisode->bind("EPISODEID", cDBS::bndIn | cDBS::bndSet); status += selectSeriesEpisode->prepare(); // select // m.episode_id, m.season_number, m.actor_id, m.media_type, m.media_url, m.media_rating, // a.actor_name, a.actor_role, a.actor_sortorder // from series_media m // left outer join series_actor a on (a.actor_id = m.actor_id) // where m.series_id = ? // and (m.episode_id = 0 or m.episode_id = ?) // and (m.season_number = 0 or m.season_number = ?) selectSeriesMedia = new cDbStatement(seriesMediaDb); selectSeriesMedia->build("select "); selectSeriesMedia->setBindPrefix("m."); selectSeriesMedia->bind("EpisodeId", cDBS::bndOut); selectSeriesMedia->bind("SeasonNumber", cDBS::bndOut, ", "); selectSeriesMedia->bind("ActorId", cDBS::bndOut, ", "); selectSeriesMedia->bind("MediaType", cDBS::bndOut, ", "); selectSeriesMedia->bind("MediaUrl", cDBS::bndOut, ", "); selectSeriesMedia->bind("MediaRating", cDBS::bndOut, ", "); selectSeriesMedia->setBindPrefix("a."); selectSeriesMedia->bind(seriesActorsDb, "ActorName", cDBS::bndOut, ", "); selectSeriesMedia->bind(seriesActorsDb, "ActorRole", cDBS::bndOut, ", "); selectSeriesMedia->bind(seriesActorsDb, "SortOrder", cDBS::bndOut, ", "); selectSeriesMedia->clrBindPrefix(); selectSeriesMedia->build(" from %s m left outer join %s a on (m.%s = a.%s) where ", seriesMediaDb->TableName(), seriesActorsDb->TableName(), movieActorsDb->getField("ActorId")->getDbName(), movieMediaDb->getField("ActorId")->getDbName()); selectSeriesMedia->setBindPrefix("m."); selectSeriesMedia->bind("SeriesId", cDBS::bndIn | cDBS::bndSet); selectSeriesMedia->build(" and (m.episode_id = 0 or "); selectSeriesMedia->bind("EpisodeId", cDBS::bndIn | cDBS::bndSet); selectSeriesMedia->build(") and (m.season_number = 0 or "); selectSeriesMedia->bind("SeasonNumber", cDBS::bndIn | cDBS::bndSet); selectSeriesMedia->build(")"); status += selectSeriesMedia->prepare(); // select * // from channelmap order by ord; selectAllMap = new cDbStatement(mapDb); selectAllMap->build("select "); selectAllMap->bindAllOut(); selectAllMap->build(" from %s order by %s", mapDb->TableName(), mapDb->getField("ORDER")->getDbName()); status += selectAllMap->prepare(); // select t.*, // e.eventid, e.channelid, e.title, e.shorttext, e.shortdescription, e.category, e.genre, e.tipp, e.numrating // from timers t left outer join events e // on (t.eventid = e.masterid and e.updflg in (...)) // where // t.state in (?) // timerIncState.setField(&timerStateDef); // timerExcState.setField(&timerStateDef); // timerIncAction.setField(&timerActionDef); // timerExcAction.setField(&timerActionDef); selectAllTimer = new cDbStatement(timerDb); selectAllTimer->build("select "); selectAllTimer->setBindPrefix("t."); selectAllTimer->bindAllOut(); selectAllTimer->setBindPrefix("e."); selectAllTimer->bind(useeventsDb, "USEID", cDBS::bndOut, ", "); selectAllTimer->bind(useeventsDb, "CHANNELID", cDBS::bndOut, ", "); selectAllTimer->bind(useeventsDb, "CNTSOURCE", cDBS::bndOut, ", "); selectAllTimer->bind(useeventsDb, "CNTEVENTID", cDBS::bndOut, ", "); selectAllTimer->bind(useeventsDb, "TITLE", cDBS::bndOut, ", "); selectAllTimer->bind(useeventsDb, "SHORTTEXT", cDBS::bndOut, ", "); selectAllTimer->bind(useeventsDb, "SHORTDESCRIPTION", cDBS::bndOut, ", "); selectAllTimer->bind(useeventsDb, "CATEGORY", cDBS::bndOut, ", "); selectAllTimer->bind(useeventsDb, "GENRE", cDBS::bndOut, ", "); selectAllTimer->bind(useeventsDb, "TIPP", cDBS::bndOut, ", "); selectAllTimer->bind(useeventsDb, "NUMRATING", cDBS::bndOut, ", "); selectAllTimer->clrBindPrefix(); selectAllTimer->build(" from %s t left outer join %s e", timerDb->TableName(), "eventsviewplain"); selectAllTimer->build(" on (t.eventid = e.cnt_useid) and e.updflg in (%s)", cEventState::getVisible()); // selectAllTimer->build(" where "); // selectAllTimer->bindInChar("t", "STATE", &timerIncState); // selectAllTimer->bindInChar("t", "STATE", &timerExcState, " and not "); // selectAllTimer->bindInChar("t", "ACTION", &timerIncAction, " and "); // selectAllTimer->bindInChar("t", "ACTION", &timerExcAction, " and not "); status += selectAllTimer->prepare(); // select * // from timers where // id = ? selectTimerById = new cDbStatement(timerDb); selectTimerById->build("select "); selectTimerById->bindAllOut(); selectTimerById->build(" from %s where ", timerDb->TableName()); selectTimerById->bind("ID", cDBS::bndIn | cDBS::bndSet); status += selectTimerById->prepare(); // select * // from timers where // state not in ('D','E') // eventid = ? and channelid = ? selectTimerByEventId = new cDbStatement(timerDb); selectTimerByEventId->build("select "); selectTimerByEventId->bindAllOut(); selectTimerByEventId->build(" from %s where ", timerDb->TableName()); selectTimerByEventId->build(" %s not in ('D', 'E', '-')", timerDb->getField("STATE")->getDbName()); selectTimerByEventId->bind("EVENTID", cDBS::bndIn | cDBS::bndSet, " and "); selectTimerByEventId->bind("CHANNELID", cDBS::bndIn | cDBS::bndSet, " and "); status += selectTimerByEventId->prepare(); // select * // from searchtimers // where state <> 'D' selectAllSearchTimer = new cDbStatement(searchtimerDb); selectAllSearchTimer->build("select "); selectAllSearchTimer->bindAllOut(); selectAllSearchTimer->build(" from %s where ", searchtimerDb->TableName()); selectAllSearchTimer->build(" %s <> 'D'", searchtimerDb->getField("STATE")->getDbName()); status += selectAllSearchTimer->prepare(); // update searchtimers set autotimername = ? // whrer autotimerid = ? updateTimerAName = new cDbStatement(timerDb); updateTimerAName->build("update %s set ", timerDb->TableName()); updateTimerAName->bind("AUTOTIMERNAME", cDBS::bndIn | cDBS::bndSet); updateTimerAName->build(" where "); updateTimerAName->bind("AUTOTIMERID", cDBS::bndIn | cDBS::bndSet); status += updateTimerAName->prepare(); // update timersdonedb set autotimername = ? // whrer autotimerid = ? updateDoneAName = new cDbStatement(timersDoneDb); updateDoneAName->build("update %s set ", timersDoneDb->TableName()); updateDoneAName->bind("AUTOTIMERNAME", cDBS::bndIn | cDBS::bndSet); updateDoneAName->build(" where "); updateDoneAName->bind("AUTOTIMERID", cDBS::bndIn | cDBS::bndSet); status += updateDoneAName->prepare(); // select *, inssp, updsp // from timers // where ACTION != 'A' selectPendingTimerActions = new cDbStatement(timerDb); selectPendingTimerActions->build("select "); selectPendingTimerActions->bindAllOut(); selectPendingTimerActions->bind("INSSP", cDBS::bndOut, ", "); selectPendingTimerActions->bind("UPDSP", cDBS::bndOut, ", "); selectPendingTimerActions->build(" from %s where %s != 'A'", // taAssumed timerDb->TableName(), timerDb->getField("ACTION")->getDbName()); status += selectPendingTimerActions->prepare(); // select *, inssp, updsp // from timersdone selectDoneTimers = new cDbStatement(timersDoneDb); selectDoneTimers->build("select "); selectDoneTimers->bindAllOut(); selectDoneTimers->bind("INSSP", cDBS::bndOut, ", "); selectDoneTimers->bind("UPDSP", cDBS::bndOut, ", "); selectDoneTimers->build(" from %s", timersDoneDb->TableName()); status += selectDoneTimers->prepare(); // select channelname // from channelmap // where channelid = ? // and channelname is not null selectMapById = new cDbStatement(mapDb); selectMapById->build("select "); selectMapById->bind("ChannelName", cDBS::bndOut); selectMapById->build(" from %s where ", mapDb->TableName()); selectMapById->bind("ChannelId", cDBS::bndIn | cDBS::bndSet); selectMapById->build(" and channelname is not null"); status += selectMapById->prepare(); // update channelmap set ord = ?, visible = ?, channelname = ? // where channelid = ? updateMap = new cDbStatement(mapDb); updateMap->build("update %s set ", mapDb->TableName()); updateMap->bind("ORDER", cDBS::bndIn | cDBS::bndSet); updateMap->bind("VISIBLE", cDBS::bndIn | cDBS::bndSet, ", "); updateMap->bind("CHANNELNAME", cDBS::bndIn | cDBS::bndSet, ", "); updateMap->build(" where "); updateMap->bind("CHANNELID", cDBS::bndIn | cDBS::bndSet); status += updateMap->prepare(); // ---------- // select channelname // from channelmap // where channelid = ? selectChannelFromMap = new cDbStatement(mapDb); selectChannelFromMap->build("select "); selectChannelFromMap->bind("CHANNELNAME", cDBS::bndOut); selectChannelFromMap->bind("UNKNOWNATVDR", cDBS::bndOut, ", "); selectChannelFromMap->build(" from %s where ", mapDb->TableName()); selectChannelFromMap->bind("CHANNELID", cDBS::bndIn | cDBS::bndSet); status += selectChannelFromMap->prepare(); // ---------- // select * from recordingdirs // where owner = ? selectRecordingDirs = new cDbStatement(recordingDirDb); selectRecordingDirs->build("select "); selectRecordingDirs->bindAllOut(); selectRecordingDirs->build(" from %s order by vdruuid, directory", recordingDirDb->TableName()); status += selectRecordingDirs->prepare(); // ---------- // select * from users // where passwd = ? selectUserByMd5 = new cDbStatement(userDb); selectUserByMd5->build("select "); selectUserByMd5->bindAllOut(); selectUserByMd5->build(" from %s where ", userDb->TableName()); selectUserByMd5->bind("PASSWD", cDBS::bndIn | cDBS::bndSet); // selectUserByMd5->bindText("md5(passwd)", userDb->getValue("passwd"), "="); status += selectUserByMd5->prepare(); // ---------- // select * // from recordinglist selectAllRecordings = new cDbStatement(recordingListDb); selectAllRecordings->build("select "); selectAllRecordings->setBindPrefix("r."); selectAllRecordings->bindAllOut(); selectAllRecordings->setBindPrefix("v."); selectAllRecordings->bind(vdrDb, "SHAREINWEB", cDBS::bndOut, ", "); selectAllRecordings->clrBindPrefix(); selectAllRecordings->build(" from %s r, %s v where", recordingListDb->TableName(), vdrDb->TableName()); selectAllRecordings->build(" r.%s = v.%s", recordingListDb->getField("VDRUUID")->getDbName(), vdrDb->getField("UUID")->getDbName()); selectAllRecordings->build(" and (r.%s <> 'D' or r.%s is null)", recordingListDb->getField("STATE")->getDbName(), vdrDb->getField("STATE")->getDbName()); status += selectAllRecordings->prepare(); // select *, // epglvr(title, ?) // epglvr(shorttext, ?) // from recordinglist where // (state <> 'D' or state is null) // and epglvr(title, ?) < 50 // // order by lv matchDensityTitle.setField(&matchDensityTitleDef); matchDensityShorttext.setField(&matchDensityShorttextDef); selectRecordingForEventByLv = new cDbStatement(recordingListDb); selectRecordingForEventByLv->build("select "); selectRecordingForEventByLv->bindAllOut(); selectRecordingForEventByLv->bindTextFree(", 100 - ifNull(epglvr(title, ?), 100)", &matchDensityTitle, cDBS::bndOut); selectRecordingForEventByLv->appendBinding(recordingListDb->getValue("TITLE"), cDBS::bndIn); selectRecordingForEventByLv->bindTextFree(", 100 - ifNull(epglvr(shorttext, ?), 100)", &matchDensityShorttext, cDBS::bndOut); selectRecordingForEventByLv->appendBinding(recordingListDb->getValue("SHORTTEXT"), cDBS::bndIn); selectRecordingForEventByLv->build(" from %s where ", recordingListDb->TableName()); selectRecordingForEventByLv->build(" (%s <> 'D' or %s is null)", recordingListDb->getField("STATE")->getDbName(), recordingListDb->getField("STATE")->getDbName()); selectRecordingForEventByLv->bindTextFree("and epglvr(title, ?) < 47", recordingListDb->getValue("TITLE"), cDBS::bndIn); status += selectRecordingForEventByLv->prepare(); // select * // from recordinglist where // state <> 'D' // snd path = ? selectRecordingByPath = new cDbStatement(recordingListDb); selectRecordingByPath->build("select "); selectRecordingByPath->bindAllOut(); selectRecordingByPath->build(" from %s where ", recordingListDb->TableName()); selectRecordingByPath->build(" (%s <> 'D' or %s is null)", recordingListDb->getField("STATE")->getDbName(), recordingListDb->getField("STATE")->getDbName()); selectRecordingByPath->bind("PATH", cDBS::bndIn | cDBS::bndSet, " and "); status += selectRecordingByPath->prepare(); // select *, inssp, updsp // from messages // where STATE != 'D' selectPendingMessages = new cDbStatement(messageDb); selectPendingMessages->build("select "); selectPendingMessages->bindAllOut(); selectPendingMessages->bind("INSSP", cDBS::bndOut, ", "); selectPendingMessages->bind("UPDSP", cDBS::bndOut, ", "); selectPendingMessages->build(" from %s where %s != 'D'", messageDb->TableName(), messageDb->getField("STATE")->getDbName()); status += selectPendingMessages->prepare(); // ---------- // select distinct(owner) from parameters // where owner like '@%'; selectWebUsers = new cDbStatement(parametersDb); selectWebUsers->build("select distinct("); selectWebUsers->bind("OWNER", cDBS::bndOut); selectWebUsers->build(") from %s where %s like '@%%'", parametersDb->TableName(), parametersDb->getField("OWNER")->getDbName()); status += selectWebUsers->prepare(); // --------- // .... status += search->initDb(); // init some DB values for faster and easier access status += vdrDb->init(vdrState, "State") + vdrDb->init(vdrIp, "Ip") + vdrDb->init(vdrSvdrp, "Svdrp") + vdrDb->init(vdrUuid, "UUID") + useeventsDb->init(eventsUpdSp, "UpdSp") + useeventsDb->init(eventsGenre, "Genre") + useeventsDb->init(eventsCategory, "Category") + mapDb->init(mapChannelName, "ChannelName") + mapDb->init(mapChannelId, "ChannelId") + mapDb->init(mapSource, "Source") + imageRefDb->init(imagerefImgName, "ImgName") + imageDb->init(imageUpdSp, "UpdSp") + imageDb->init(imageImage, "Image") + movieActorDb->init(movieactorActorId, "ActorId") + movieMediaDb->init(moviemediaMediaContent, "MediaContent") + movieMediaDb->init(moviemediaMediaType, "MediaType") + seriesEpisodeDb->init(seriesepisodeSeasonNumber, "SeasonNumber") + seriesEpisodeDb->init(seriesepisodeEpisodeId, "EpisodeId") + seriesMediaDb->init(seriesmediaMediaContent, "MediaContent") + seriesMediaDb->init(seriesmediaMediaType, "MediaType") + seriesMediaDb->init(seriesmediaActorId, "ActorId"); return status; } //*************************************************************************** // ExitDb //*************************************************************************** int cEpgHttpd::exitDb() { search->exitDb(); cParameters::exitDb(); delete selectEventsAt; selectEventsAt = 0; delete selectEventsNext; selectEventsNext = 0; delete selectEventsStartInRange; selectEventsStartInRange = 0; delete selectGenres; selectGenres = 0; delete selectCategories; selectCategories = 0; delete selectEvent; selectEvent = 0; delete selectEventByTime; selectEventByTime = 0; delete selectAllMap; selectAllMap = 0; delete selectMapById; selectMapById = 0; delete selectVdrs; selectVdrs = 0; delete selectUsers; selectUsers = 0; delete updateMap; updateMap = 0; delete updateTimerAName; updateTimerAName = 0; delete updateDoneAName; updateDoneAName = 0; delete selectAllTimer; selectAllTimer = 0; delete selectTimerById; selectTimerById = 0; delete selectTimerByEventId; selectTimerByEventId = 0; delete selectAllSearchTimer; selectAllSearchTimer = 0; delete selectMovie; selectMovie = 0; delete selectMovieActors; selectMovieActors = 0; delete selectMovieMedia; selectMovieMedia = 0; delete selectSerie; selectSerie = 0; delete selectSeriesEpisode; selectSeriesEpisode = 0; delete selectSeriesMedia; selectSeriesMedia = 0; delete selectPendingTimerActions; selectPendingTimerActions = 0; delete selectDoneTimers; selectDoneTimers = 0; delete selectActiveVdrs; selectActiveVdrs = 0; delete selectRecordingDirs; selectRecordingDirs = 0; delete selectRecordingByPath; selectRecordingByPath = 0; delete selectRecordingForEventByLv; selectRecordingForEventByLv = 0; delete selectChannelFromMap; selectChannelFromMap = 0; delete selectPendingMessages; selectPendingMessages = 0; delete selectWebUsers; selectWebUsers = 0; delete timerDb; timerDb = 0; delete timersDoneDb; timersDoneDb = 0; delete searchtimerDb; searchtimerDb = 0; delete vdrDb; vdrDb = 0; delete useeventsDb; useeventsDb = 0; delete mapDb; mapDb = 0; delete imageDb; imageDb = 0; delete imageRefDb; imageRefDb = 0; delete movieDb; movieDb = 0; delete movieActorsDb; movieActorsDb = 0; delete movieActorDb; movieActorDb = 0; delete movieMediaDb; movieMediaDb = 0; delete seriesDb; seriesDb = 0; delete seriesEpisodeDb; seriesEpisodeDb = 0; delete seriesMediaDb; seriesMediaDb= 0; delete seriesActorsDb; seriesActorsDb = 0; delete recordingDirDb; recordingDirDb = 0; delete recordingListDb; recordingListDb = 0; delete userDb; userDb = 0; delete messageDb; messageDb = 0; delete connection; connection = 0; return done; } //*************************************************************************** // Loop //*************************************************************************** int cEpgHttpd::loop() { while (!doShutDown()) { cSystemNotification::check(); sleep(1); } return done; } //*************************************************************************** // Check Connection //*************************************************************************** int cEpgHttpd::checkConnection() { static int retry = 0; // check connection if (!dbConnected(yes)) { // try to connect tell(0, "Trying to re-connect to database!"); retry++; if (initDb() != success) { tell(0, "Retry #%d failed!", retry); exitDb(); return fail; } retry = 0; tell(0, "Connection established successfull!"); } return success; } //*************************************************************************** // Check Session //*************************************************************************** int cEpgHttpd::setSession(const char* sessionId) { std::map::iterator it; Session* s; currentSession = 0; loginWithSession = yes; if ((it = sessions.find(std::string(sessionId))) != sessions.end()) { s = &it->second; if (time(0) > s->last + tmeSecondsPerHour) { tell(0, "Session '%s' for user '%s' expired, removing it", s->id.c_str(), s->user.c_str()); sessions.erase(it); return fail; } tell(2, "Session now '%s' for user '%s'", s->id.c_str(), s->user.c_str()); currentSession = s; currentSession->last = time(0); return success; } return fail; } //*************************************************************************** // Need Login //*************************************************************************** int cEpgHttpd::needLogin() { long int need = no; getParameter("webif", "needLogin", need); return need; } //*************************************************************************** // Has Rights //*************************************************************************** int cEpgHttpd::hasRights(const char* url, json_t* response, int& statusCode) { UserMask mask = toRightMask(url); statusCode = MHD_HTTP_OK; if (!needLogin()) return yes; tell(2, "Checking rights of '%s' for user '%s' with%s session", url, currentSession ? currentSession->user.c_str() : "", currentSession ? "" : "out"); if (!currentSession || !hasUserMask(currentSession->rights, mask)) { if (currentSession) { statusCode = buildResponse(response, MHD_HTTP_FORBIDDEN, "Rejecting '%s' request of user " "'%s' due to insufficient rights!", url, currentSession->user.c_str()); return no; } else if (!hasUserMask(umNologin, mask)) { statusCode = buildResponse(response, MHD_HTTP_UNAUTHORIZED, "Rejecting '%s' request, missing login!", url); return no; } } // special case, request has invalid or expired session // -> reject even in umNologin case if (!currentSession && loginWithSession) { statusCode = buildResponse(response, MHD_HTTP_UNAUTHORIZED, "Rejecting '%s' request " "due to invalid session!", url); return no; } return yes; } //*************************************************************************** // Build Response //*************************************************************************** int cEpgHttpd::buildResponse(json_t* obj, int state, const char* format, ...) { va_list ap; json_t* oResult = json_object(); char* message; // {"result": {"state": 200, "message": "success"}} va_start(ap, format); vasprintf(&message, format, ap); va_end(ap); if (state != MHD_HTTP_OK) tell(0, "Error: %s", message); json_object_set_new(oResult, "state", json_integer(state)); json_object_set_new(oResult, "message", json_string(message)); json_object_set_new(obj, "result", oResult); free(message); return state; } //*************************************************************************** // Method Of //*************************************************************************** const char* cEpgHttpd::methodOf(const char* url) { const char* p; if (url && (p = strchr(url+1, '/'))) return p+1; return ""; } //*************************************************************************** // Perform Data Request //*************************************************************************** int cEpgHttpd::performDataRequest(MHD_Connection* tcp, const char* url, MemoryStruct* data) { const char* method = methodOf(url); const char* encoding = MHD_lookup_connection_value(tcp, MHD_HEADER_KIND, "Accept-Encoding"); json_t* response = json_object(); int statusCode = MHD_HTTP_OK; int jsonResponse = yes; if (checkConnection() != success) // check connection .. { statusCode = buildResponse(response, MHD_HTTP_SERVICE_UNAVAILABLE, "Lost database connection, retry later"); } else if (!hasRights(url, response, statusCode)) // check rights .. { // ... } else { data->modTime = time(0); if (strcmp(method, "events") == 0) statusCode = doEvents(tcp, response); else if (strcmp(method, "timers") == 0) statusCode = doTimers(tcp, response); else if (strcmp(method, "recordings") == 0) statusCode = doRecordings(tcp, response); else if (strcmp(method, "recording") == 0) statusCode = doRecording(tcp, response); else if (strcmp(method, "parameters") == 0) statusCode = doParameters(tcp, response); else if (strcmp(method, "recordingdirs") == 0) statusCode = doRecDirs(tcp, response); else if (strcmp(method, "pendingtimerjobs") == 0) statusCode = doTimerJobs(tcp, response); else if (strcmp(method, "messages") == 0) statusCode = doMessages(tcp, response); else if (strcmp(method, "donetimers") == 0) statusCode = doDoneTimers(tcp, response); else if (strcmp(method, "donetimer") == 0) statusCode = doDoneTimer(tcp, response); else if (strcmp(method, "searchtimers") == 0) statusCode = doSearchtimers(tcp, response); else if (strcmp(method, "event") == 0) statusCode = doEvent(tcp, response, data); else if (strcmp(method, "channels") == 0) statusCode = doChannels(tcp, response); else if (strcmp(method, "genres") == 0) statusCode = doGenres(tcp, response); else if (strcmp(method, "categories") == 0) statusCode = doCategories(tcp, response); else if (strcmp(method, "vdrs") == 0) statusCode = doVdrs(tcp, response); else if (strcmp(method, "users") == 0) statusCode = doUsers(tcp, response); else if (strcmp(method, "updatesearchtimer") == 0) statusCode = doUpdateSearchtimer(tcp, response); else if (strcmp(method, "updaterecordings") == 0) statusCode = doUpdateRecordingTable(tcp, response); else if (strcmp(method, "wakeupvdr") == 0) statusCode = doWakeupVdr(tcp, response); else if (strcmp(method, "renamerecording") == 0) statusCode = doRenameRecording(tcp, response); else if (strcmp(method, "replayrecording") == 0) statusCode = doReplayRecording(tcp, response); else if (strcmp(method, "deleterecording") == 0) statusCode = doDeleteRecording(tcp, response); else if (strcmp(method, "channelswitch") == 0) statusCode = doChannelSwitch(tcp, response); else if (strcmp(method, "hitkey") == 0) statusCode = doHitKey(tcp, response); else if (strcmp(method, "log") == 0) statusCode = doLog(tcp, response); else if (strcmp(method, "debug") == 0) statusCode = doDebug(tcp, response); else { // requests with *non* json response (data buffer is ready - nothing to prepare later) jsonResponse = no; if (strcmp(method, "channellogo") == 0) statusCode = doChannelLogo(tcp, response, data); else if (strcmp(method, "eventimg") == 0) statusCode = doEpgImage(tcp, response, data); else if (strcmp(method, "moviemedia") == 0) statusCode = doMovieMedia(tcp, response, data); else if (strcmp(method, "seriesmedia") == 0) statusCode = doSeriesMedia(tcp, response, data); else if (strcmp(method, "proxy") == 0) statusCode = doProxy(tcp, response, data); else { statusCode = buildResponse(response, MHD_HTTP_NOT_FOUND, "Unexpected method '%s' requested, ignoring", method); jsonResponse = yes; } } } // if json response convert to data if (jsonResponse) { json2Data(response, data, encoding); if (EpgdConfig.loglevel >= 2) json_dump_file(response, "debug-dump.json", JSON_PRESERVE_ORDER); } json_decref(response); // free the json object return statusCode; } //*************************************************************************** // Perform Http Get //*************************************************************************** int cEpgHttpd::performHttpGet(MHD_Connection* tcp, const char* inurl, MemoryStruct* data) { const char* url; int statusCode = MHD_HTTP_OK; if (strcmp(inurl, "/") == 0) url = "index.html"; else url = inurl; data->modTime = getModTimeOf(url, ""); tell(3, "file: %s; expire: %s", l2pTime(data->modTime).c_str(), l2pTime(data->expireAt).c_str()); if (!data->expireAt || data->modTime > data->expireAt) { if (loadFromFs(data, url, "") == success) { const char* encoding = MHD_lookup_connection_value(tcp, MHD_HEADER_KIND, "Accept-Encoding"); sprintf(data->name, "%.*s", (int)sizeof(data->name), url); if (encoding && strstr(encoding, "gzip")) { if (data->toGzip() != success) statusCode = 500; } } else { statusCode = MHD_HTTP_NOT_FOUND; } } else { statusCode = MHD_HTTP_NOT_MODIFIED; } return statusCode; } //*************************************************************************** // Perform Post Data //*************************************************************************** int cEpgHttpd::performPostData(const char* url, MemoryStruct* data) { int statusCode = MHD_HTTP_OK;; json_t* response = json_object(); json_error_t error; json_t* jInData = json_loads(data->memory, 0, &error); tell(2, "<- post (%s) '%s'", url, data->memory); if (!jInData) { statusCode = buildResponse(response, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE, "Ignoring invalid post object [%s]", data->memory); } else if (checkConnection() != success) // check db connection .. { statusCode = buildResponse(response, MHD_HTTP_SERVICE_UNAVAILABLE, "Lost database connection, retry later"); } else if (!hasRights(url, response, statusCode)) // check rights .. { // ... } else { // dispatch post requests if (strcmp(url, "/data/save-channels") == 0) statusCode = storeChannels(jInData, response); else if (strcmp(url, "/data/delete-timerjobs") == 0) statusCode = deleteTimerJobs(jInData, response); else if (strcmp(url, "/data/store-donetimers") == 0) statusCode = deleteDoneTimers(jInData, response); else if (strcmp(url, "/data/save-timer") == 0) statusCode = storeTimerJob(jInData, response); else if (strcmp(url, "/data/save-searchtimer") == 0) statusCode = storeSearchTimer(jInData, response); else if (strcmp(url, "/data/save-parameters") == 0) statusCode = storeParameters(jInData, response); else if (strcmp(url, "/data/search") == 0) statusCode = doSearch(jInData, response); else if (strcmp(url, "/data/sendmail") == 0) statusCode = doSendMail(jInData, response); else if (strcmp(url, "/data/save-users") == 0) statusCode = storeUsers(jInData, response); else if (strcmp(url, "/data/login") == 0) statusCode = doLogin(jInData, response); else if (strcmp(url, "/data/markmessages") == 0) statusCode = markMessages(jInData, response); else statusCode = buildResponse(response, MHD_HTTP_NOT_FOUND, "Unexpected post request '%s', ignoring", url); } json_decref(jInData); // free the json object data->clear(); // prepare response data json2Data(response, data); json_decref(response); // free the json object return statusCode; } //*************************************************************************** // Debug ... //*************************************************************************** int debugPrint(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { if (kind == MHD_GET_ARGUMENT_KIND) tell(0, "Parameter: '%s' - '%s'", key, value); else if (kind == MHD_HEADER_KIND) tell(0, "Header: '%s' - '%s'", key, value); return MHD_YES; } int parameterInfo(void* cls, enum MHD_ValueKind kind, const char* key, const char* value) { if (strlen((char*)cls) < 1000 - strlen(notNull(value)) - strlen(notNull(key)) - 10) sprintf(eos((char*)cls), "%s:%s;", notNull(key), notNull(value)); return MHD_YES; } //*************************************************************************** // Dispatcher //*************************************************************************** int cEpgHttpd::dispatcher(void* cls, MHD_Connection* tcp, const char* url, const char* method, const char* version, const char* upload_data, size_t* upload_data_size, void** con_cls) { const char* contentNotFound = "Page not found"; unsigned int statusCode = MHD_HTTP_OK; MHD_Response* response; MemoryStruct data; double requestStartAt = cMyTimeMs::Now(); // int acceptRequest = no; int state; // reset session singleton->currentSession = 0; singleton->loginWithSession = no; // get header infos const char* id = getStrParameter(tcp, "id"); data.expireAt = getTimeHeader(tcp, "If-Modified-Since", ""); // Sat, 08 May 2010 11:25:27 GMT. const char* cacheTag = getStrHeader(tcp, "If-None-Match", ""); // "2a28c-73a1-6eb997c0" const char* cacheControl = getStrHeader(tcp, "Cache-Control", "") ; // max-age=0 int checkCache = strstr(cacheControl, "max-age=0") != 0; if (!checkCache) data.expireAt = 0; // debug if (EpgdConfig.loglevel >= 3) { // MHD_get_connection_values(tcp, MHD_GET_ARGUMENT_KIND, debugPrint, 0); MHD_get_connection_values(tcp, MHD_HEADER_KIND, debugPrint, 0); } // check session in url // "/sid3cf33d41332e3e2702e7a8e39429f848/data/parameters" if (strncmp(url, "/sid", 4) == 0) { char* session = 0; asprintf(&session, "%.*s", (int)(strchr(url+4, '/') - url - 4), url + 4); url = strchr(url+4, '/'); tell(3, "SESSION: '%s'", session); singleton->setSession(session); free(session); } tell(3, "Cache info: cacheExpire = %s; cacheTag = %s; cacheControl = %s", l2pTime(data.expireAt).c_str(), cacheTag, cacheControl); // dispatch ... if (strcmp(method, "POST") == 0) { if (EpgdConfig.loglevel >= 2) tell(3, "<- '%s' / %s%s [%s](%d)", url, id ? "id=" : "", id ? id : "", upload_data, (int)(*upload_data_size)); if (!(*con_cls)) // start of new incoming data { *con_cls = (void*) new MemoryStruct; return MHD_YES; } if (EpgdConfig.httpUseTls && !isEmpty(EpgdConfig.httpUser)) { if (!isAuthenticated(tcp, EpgdConfig.httpUser, EpgdConfig.httpPass)) return askForAuthentication(tcp, realm); } MemoryStruct* postData = (MemoryStruct*)*con_cls; if (*upload_data_size) // chunk of POST data? { // processing of POST data postData->append(upload_data, (int)*upload_data_size); *upload_data_size = 0; // set 0 to indicate 'successfully processed' return MHD_YES; } else // end of incoming data { postData->memory = (char*)realloc(postData->memory, postData->size+1); postData->memory[postData->size] = 0; postData->size++; data.size = postData->size; data.memory = (char*)malloc(data.size); urlUnescape(data.memory, postData->memory); delete postData; statusCode = singleton->performPostData(url, &data); } } else if (strcmp(method, "GET") == 0) { if (EpgdConfig.httpUseTls && !isEmpty(EpgdConfig.httpUser)) { if (!isAuthenticated(tcp, EpgdConfig.httpUser, EpgdConfig.httpPass)) return askForAuthentication(tcp, realm); } if (EpgdConfig.loglevel >= 2) { char parmDebug[1000+TB] = ""; MHD_get_connection_values(tcp, MHD_GET_ARGUMENT_KIND, parameterInfo, parmDebug); tell(2, "<- %s with [%s] (expire at %s)", url, parmDebug, l2pTime(data.expireAt).c_str()); tell(3, "data: %s; expire: %s", l2pTime(data.modTime).c_str(), l2pTime(data.expireAt).c_str()); } if (strstr(url, "/data") == url) // data requested ... statusCode = cEpgHttpd::singleton->performDataRequest(tcp, url, &data); else // file request ... statusCode = cEpgHttpd::singleton->performHttpGet(tcp, url, &data); if (statusCode == MHD_HTTP_OK) { if (data.modTime <= data.expireAt) { statusCode = MHD_HTTP_NOT_MODIFIED; tell(2, "-> %d 'not modified'", statusCode); } else if (!data.size) { data.memory = strdup(contentNotFound); data.size = strlen(data.memory); statusCode = MHD_HTTP_NOT_FOUND; tell(1, "-> %d 'file not found'", statusCode); } } } // --------------------- // prepare response ... response = createHttpResponse(&data); // header char* server; asprintf(&server, "epghttpd/%s from %s\n", VERSION, VERSION_DATE); addHeaderItem(response, "Server", "server"); if (strcmp(method, "POST") == 0) addHeaderItem(response, "Cache-Control", "max-age=0, must-revalidate"); else addHeaderItem(response, "Cache-Control", "max-age=300, must-revalidate"); free(server); if (statusCode == MHD_HTTP_OK) { addHeaderItem(response, "Last-Modified", l2HttpTime(data.modTime).c_str()); addHeaderItem(response, "Content-Type", data.contentType); } if (!isEmpty(data.contentEncoding)) addHeaderItem(response, "Content-Encoding", data.contentEncoding); // response state = MHD_queue_response(tcp, statusCode, response); MHD_destroy_response(response); // log and debug ... { // const uint dspWidth = 6000; // debug, later around 50 or 100 ...? const char* dump = "..."; // uint dumpSize = 3; if ((strncmp(data.contentType, "test", 4) == 0 || strstr(data.contentType, "json"))) { dump = data.memory; // dumpSize = data.size; } else if (!isEmpty(data.name)) { dump = data.name; // dumpSize = strlen(data.name); } tell(2, "-> %s (%ld) (%s); Content-Type: %s; %s%s [%s]", !isEmpty(data.name) ? "file" : "data", data.size, ms2Dur(cMyTimeMs::Now()-requestStartAt).c_str(), data.contentType, !isEmpty(data.contentEncoding) ? "Content-Encoding: " : "", data.contentEncoding, dump); // tell(2, "-> %s (%ld); Content-Type: %s; %s%s [%.*s%s]", // !isEmpty(data.name) ? "file" : "data", // data.size, data.contentType, // !isEmpty(data.contentEncoding) ? "Content-Encoding: " : "", data.contentEncoding, // dspWidth,dump, dumpSize > dspWidth ? "..." : ""); } return state; } //*************************************************************************** // Trigger Epgd //*************************************************************************** int cEpgHttpd::triggerEpgd() { int pid = na; vdrDb->clear(); vdrDb->setValue("UUID", EPGDNAME); if (!vdrDb->find() || vdrDb->getValue("PID")->isNull()) { tell(0, "Error: Can't lookup epgd information, abort trigger"); return fail; } vdrDb->reset(); pid = vdrDb->getIntValue("PID"); if (pid > 0) { if (kill(pid, SIGUSR1) == 0) tell(1, "Triggered searchtimer update at %s", EPGDNAME); } return success; } //*************************************************************************** // Trigger VDRs //*************************************************************************** int cEpgHttpd::triggerVdrs(const char* trg, const char* plug, const char* options) { if (!selectActiveVdrs) // wait for dbInit ;) return done; vdrDb->clear(); for (int f = selectActiveVdrs->find(); f; f = selectActiveVdrs->fetch()) { const char* ip = vdrDb->getStrValue("IP"); unsigned int port = vdrDb->getIntValue("SVDRP"); if (vdrDb->getIntValue("SHAREINWEB")) triggerVdr(ip, port, trg, plug, options); } selectActiveVdrs->freeResult(); return success; } //*************************************************************************** // Trigger VDRs //*************************************************************************** int cEpgHttpd::triggerVdr(const char* ip, unsigned int port, const char* trg, const char* plug, const char* options, char* res) { int status = success; char* command = 0; cList result; cSvdrpClient cl(ip, port); // open tcp connection if (cl.open() != success) return fail; if (!isEmpty(plug)) asprintf(&command, "PLUG %s %s %s", plug, trg, !isEmpty(options) ? options : ""); else asprintf(&command, "%s %s", trg, !isEmpty(options) ? options : ""); tell(1, "Send '%s' to '%s:%d'", command, ip, port); if (!cl.send(command)) { status = fail; tell(0, "Error: Send '%s' at '%s:%d' failed!", command, ip, port); } else { int code = cl.receive(&result); if (code != 900) { status = fail; if (res && result.First()) sprintf(res, "%.*s", 512, result.First()->Text()); } } free(command); cl.close(no); return status; } //*************************************************************************** // IP of VDR //*************************************************************************** int cEpgHttpd::ipOfVdr(const char* uuid, const char*& ip, int& port) { int alive = no; port = 0; ip = 0; vdrDb->clear(); vdrDb->setValue("UUID", uuid); if (vdrDb->find()) { ip = vdrDb->getStrValue("IP"); port = vdrDb->getIntValue("SVDRP"); alive = vdrDb->hasValue("STATE", "attached"); } vdrDb->reset(); return alive; } //*************************************************************************** // Wakekup VDR //*************************************************************************** int cEpgHttpd::wakeupVdr(const char* uuid) { int status = fail; vdrDb->clear(); vdrDb->setValue("UUID", uuid); if (vdrDb->find()) { const char* mac = vdrDb->getStrValue("MAC"); if (!isEmpty(mac)) status = sendWol(mac, EpgdConfig.netDevice); } vdrDb->reset(); return status; } //*************************************************************************** // Message //*************************************************************************** int cEpgHttpd::message(int level, char type, const char* title, const char* format, ...) { va_list ap; char* message; std::string receivers; va_start(ap, format); vasprintf(&message, format, ap); va_end(ap); messageDb->setCharValue("TYPE", type); messageDb->setValue("TITLE", title); messageDb->setValue("STATE", "N"); messageDb->setValue("TEXT", message); messageDb->insert(); tell(level, "%s: %s", title, message); // loop over web users parametersDb->clear(); for (int found = selectWebUsers->find(); found; found = selectWebUsers->fetch()) { char receiver[255+TB] = ""; char typesToMail[10+TB] = ""; const char* owner = parametersDb->getStrValue("OWNER"); getParameter(owner, "messageMailTypes", typesToMail); if (!strchr(typesToMail, type)) continue; getParameter(owner, "mailReceiver", receiver); if (isEmpty(receiver)) tell(0, "Warning: Missing mail receiver, can't send mail to '%s'", owner+1); else receivers += receiver + std::string(","); } sendMail("text/plain", receivers.c_str(), title, message); free(message); return done; } //*************************************************************************** // Configuration //*************************************************************************** int cEpgHttpd::atConfigItem(const char* Name, const char* Value) { // Parse setup parameters and store values. if (!strcasecmp(Name, "DbHost")) sstrcpy(EpgdConfig.dbHost, Value, sizeof(EpgdConfig.dbHost)); else if (!strcasecmp(Name, "DbPort")) EpgdConfig.dbPort = atoi(Value); else if (!strcasecmp(Name, "DbName")) sstrcpy(EpgdConfig.dbName, Value, sizeof(EpgdConfig.dbName)); else if (!strcasecmp(Name, "DbUser")) sstrcpy(EpgdConfig.dbUser, Value, sizeof(EpgdConfig.dbUser)); else if (!strcasecmp(Name, "DbPass")) sstrcpy(EpgdConfig.dbPass, Value, sizeof(EpgdConfig.dbPass)); else if (!strcasecmp(Name, "NetDevice")) sstrcpy(EpgdConfig.netDevice, Value, sizeof(EpgdConfig.netDevice)); else if (!strcasecmp(Name, "CachePath")) sstrcpy(EpgdConfig.cachePath, Value, sizeof(EpgdConfig.cachePath)); else if (!strcasecmp(Name, "LogLevel")) EpgdConfig.loglevel = EpgdConfig.argLoglevel == na ? atoi(Value) : EpgdConfig.argLoglevel; else if (!strcasecmp(Name, "HttpDevice")) sstrcpy(EpgdConfig.httpDevice, Value, sizeof(EpgdConfig.httpDevice)); else if (!strcasecmp(Name, "HttpPort")) EpgdConfig.httpPort = atoi(Value); else if (!strcasecmp(Name, "HttpTls")) EpgdConfig.httpUseTls = atoi(Value); else if (!strcasecmp(Name, "HttpUser")) sstrcpy(EpgdConfig.httpUser, Value, sizeof(EpgdConfig.httpUser)); else if (!strcasecmp(Name, "HttpPass")) sstrcpy(EpgdConfig.httpPass, Value, sizeof(EpgdConfig.httpPass)); return success; } //*************************************************************************** // Usage //*************************************************************************** void showUsage() { printf("Usage: epghttpd [-n][-c ][-l ][-t]\n"); printf(" -v show version and exit\n"); printf(" -n don't daemonize\n"); printf(" -t log to stdout\n"); printf(" -c use config in \n"); printf(" -l set log level\n"); printf(" -i \n"); } //*************************************************************************** // Main //*************************************************************************** int main(int argc, char** argv) { cEpgHttpd* job; int nofork = no; int pid; int logstdout = na; int loglevel = na; // Usage .. if (argc > 1 && (argv[1][0] == '?' || (strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0))) { showUsage(); return 0; } // Parse command line for (int i = 0; argv[i]; i++) { if (argv[i][0] != '-' || strlen(argv[i]) != 2) continue; switch (argv[i][1]) { case 'v': printf("epghttpd version %s from %s\n", VERSION, VERSION_DATE); return 0; case 't': logstdout = yes; break; case 'n': nofork = yes; break; case 'l': if (argv[i+1]) loglevel = atoi(argv[++i]); break; case 'c': if (argv[i+1]) confDir = argv[++i]; break; case 'i': if (argv[i+1]) { cSystemNotification::setPidFile(argv[++i]); break; } default: { showUsage(); return 0; } } } if (logstdout != na) EpgdConfig.logstdout = logstdout; if (loglevel != na) EpgdConfig.loglevel = loglevel; if (loglevel != na) EpgdConfig.argLoglevel = loglevel; EpgdConfig.logName = "epghttpd"; EpgdConfig.logFacility = Syslog::toCode("user"); // fork daemon if (!nofork) { if ((pid = fork()) < 0) { printf("Can't fork daemon, %s\n", strerror(errno)); return 1; } if (pid != 0) return 0; } job = new cEpgHttpd; if (job->init() != success) { job->exit(); delete job; return 1; } // register signal handler ::signal(SIGINT, cEpgHttpd::downF); ::signal(SIGTERM, cEpgHttpd::downF); // do work ... job->loop(); // shutdown job->exit(); tell(0, "normal exit"); delete job; return 0; }