Files
SingularityViewer/indra/newview/llmediafilter.cpp

444 lines
12 KiB
C++

/*
* @file llmediafilter.h
* @brief Hyperbalistic SLU paranoia controls
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2011, Sione Lomu.
* Copyright (C) 2014, Cinder Roxley.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "llmediafilter.h"
#include "llaudioengine.h"
#include "llnotificationsutil.h"
#include "llparcel.h"
#include "llsdserialize.h"
#include "lltrans.h"
#include "llvieweraudio.h"
#include "llviewerparcelmgr.h"
#include "llviewerparcelmedia.h"
const std::string MEDIALIST_XML = "medialist.xml";
bool handle_audio_filter_callback(const LLSD& notification, const LLSD& response, const std::string& url);
bool handle_media_filter_callback(const LLSD& notification, const LLSD& response, LLParcel* parcel);
std::string extractDomain(const std::string& in_url);
LLMediaFilter::LLMediaFilter()
: mMediaCommandQueue(0)
, mCurrentParcel(NULL)
, mMediaQueue(NULL)
, mAlertActive(false)
{
loadMediaFilterFromDisk();
}
void LLMediaFilter::filterMediaUrl(LLParcel* parcel)
{
if (!parcel) return;
const std::string& url = parcel->getMediaURL();
if (url.empty())
{
return;
}
const std::string domain = extractDomain(url);
mCurrentParcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
U32 enabled = gSavedSettings.getU32("MediaFilterEnable");
if (enabled > 1 && (filter(domain, WHITELIST) || filter(url, WHITELIST)))
{
LL_DEBUGS("MediaFilter") << "Media filter: URL allowed by whitelist: " << url << LL_ENDL;
LLViewerParcelMedia::play(parcel);
//mAudioState = PLAY;
}
else if (enabled && (filter(domain, BLACKLIST) || filter(url, BLACKLIST)))
{
LLNotificationsUtil::add("MediaBlocked", LLSD().with("DOMAIN", domain));
//mAudioState = STOP;
}
else if (enabled && isAlertActive())
{
mMediaQueue = parcel;
}
else if (enabled > 1)
{
LL_DEBUGS("MediaFilter") << "Media Filter: Unhanded by lists. Toasting: " << url << LL_ENDL;
setAlertStatus(true);
LLSD args, payload;
args["MEDIA_TYPE"] = LLTrans::getString("media");
args["URL"] = url;
args["DOMAIN"] = domain;
payload = url;
LLNotificationsUtil::add("MediaAlert", args, payload,
boost::bind(&handle_media_filter_callback, _1, _2, parcel));
}
else
{
LL_DEBUGS("MediaFilter") << "Media Filter: Skipping filters and playing " << url << LL_ENDL;
LLViewerParcelMedia::play(parcel);
//mAudioState = PLAY;
}
}
void LLMediaFilter::filterAudioUrl(const std::string& url)
{
if (url.empty())
{
gAudiop->startInternetStream(url);
return;
}
if (url == mCurrentAudioURL) return;
const std::string domain = extractDomain(url);
mCurrentParcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
U32 enabled = gSavedSettings.getU32("MediaFilterEnable");
if (enabled > 1 && (filter(domain, WHITELIST) || filter(url, WHITELIST)))
{
LL_DEBUGS("MediaFilter") << "Audio filter: URL allowed by whitelist: " << url << LL_ENDL;
gAudiop->startInternetStream(url);
}
else if (enabled && (filter(domain, BLACKLIST) || filter(url, BLACKLIST)))
{
LLNotificationsUtil::add("MediaBlocked", LLSD().with("DOMAIN", domain));
gAudiop->stopInternetStream();
}
else if (enabled && isAlertActive())
{
mAudioQueue = url;
}
else if (enabled > 1)
{
LL_DEBUGS("MediaFilter") << "Audio Filter: Unhanded by lists. Toasting: " << url << LL_ENDL;
setAlertStatus(true);
LLSD args, payload;
args["MEDIA_TYPE"] = LLTrans::getString("audio");
args["URL"] = url;
args["DOMAIN"] = domain;
LLNotificationsUtil::add("MediaAlert", args, LLSD(),
boost::bind(&handle_audio_filter_callback, _1, _2, url));
}
else
{
LL_DEBUGS("MediaFilter") << "Audio Filter: Skipping filters and playing: " << url << LL_ENDL;
gAudiop->startInternetStream(url);
}
}
bool LLMediaFilter::filter(const std::string& url, EMediaList list)
{
const string_list_t& p_list = (list == WHITELIST) ? mWhiteList : mBlackList;
string_list_t::const_iterator find_itr = std::find(p_list.begin(), p_list.end(), url);
return (find_itr != p_list.end());
}
// List bizznizz
void LLMediaFilter::addToMediaList(const std::string& in_url, EMediaList list, bool extract)
{
const std::string url = extract ? extractDomain(in_url) : in_url;
if (url.empty())
{
LL_INFOS("MediaFilter") << "No url found. Can't add to list." << LL_ENDL;
return;
}
string_list_t& p_list(list == WHITELIST ? mWhiteList : mBlackList);
// Check for duplicates
for (string_list_t::const_iterator itr = p_list.begin(); itr != p_list.end(); ++itr)
{
if (url == *itr)
{
LL_INFOS("MediaFilter") << "URL " << url << " already in list!" << LL_ENDL;
return;
}
}
p_list.push_back(url);
mMediaListUpdate(list);
saveMediaFilterToDisk();
}
void LLMediaFilter::removeFromMediaList(string_vec_t domains, EMediaList list)
{
if (domains.empty()) return;
for (string_vec_t::const_iterator itr = domains.begin(); itr != domains.end(); ++itr)
(list == WHITELIST ? mWhiteList : mBlackList).remove(*itr);
mMediaListUpdate(list);
saveMediaFilterToDisk();
}
void LLMediaFilter::loadMediaFilterFromDisk()
{
const std::string medialist_filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, MEDIALIST_XML);
mWhiteList.clear();
mBlackList.clear();
LLSD medialist;
if (LLFile::isfile(medialist_filename))
{
llifstream medialist_xml(medialist_filename);
LLSDSerialize::fromXML(medialist, medialist_xml);
medialist_xml.close();
}
else
LL_INFOS("MediaFilter") << medialist_filename << " not found." << LL_ENDL;
for (LLSD::array_const_iterator p_itr = medialist.beginArray();
p_itr != medialist.endArray();
++p_itr)
{
LLSD itr = (*p_itr);
/// I hate this string shit more than you could ever imagine,
/// but I'm retaining it for backwards and cross-compatibility. :|
if (itr["action"].asString() == "allow")
{
LL_DEBUGS("MediaFilter") << "Adding " << itr["domain"].asString() << " to whitelist." << LL_ENDL;
mWhiteList.push_back(itr["domain"].asString());
}
else if (itr["action"].asString() == "deny")
{
LL_DEBUGS("MediaFilter") << "Adding " << itr["domain"].asString() << " to blacklist." << LL_ENDL;
mBlackList.push_back(itr["domain"].asString());
}
}
}
void medialist_to_llsd(const LLMediaFilter::string_list_t& list, LLSD& list_llsd, const LLSD& action)
{
for (LLMediaFilter::string_list_t::const_iterator itr = list.begin(); itr != list.end(); ++itr)
{
LLSD item;
item["domain"] = *itr;
item["action"] = action;
list_llsd.append(item);
}
}
void LLMediaFilter::saveMediaFilterToDisk() const
{
const std::string medialist_filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, MEDIALIST_XML);
LLSD medialist_llsd;
medialist_to_llsd(mWhiteList, medialist_llsd, "allow"); // <- $*#@()&%@
medialist_to_llsd(mBlackList, medialist_llsd, "deny"); // sigh.
llofstream medialist;
medialist.open(medialist_filename);
LLSDSerialize::toPrettyXML(medialist_llsd, medialist);
medialist.close();
}
void perform_queued_command(LLMediaFilter& inst)
{
if (U32 command = inst.getQueuedMediaCommand())
{
if (command == PARCEL_MEDIA_COMMAND_STOP)
{
LLViewerParcelMedia::stop();
}
else if (command == PARCEL_MEDIA_COMMAND_PAUSE)
{
LLViewerParcelMedia::pause();
}
else if (command == PARCEL_MEDIA_COMMAND_UNLOAD)
{
LLViewerParcelMedia::stop();
}
else if (command == PARCEL_MEDIA_COMMAND_TIME)
{
LLViewerParcelMedia::seek(LLViewerParcelMedia::sMediaCommandTime);
}
inst.setQueuedMediaCommand(0);
}
}
bool handle_audio_filter_callback(const LLSD& notification, const LLSD& response, const std::string& url)
{
LLMediaFilter& inst(LLMediaFilter::instance());
inst.setAlertStatus(false);
S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
const std::string queue = inst.getQueuedAudio();
switch(option)
{
case 3: // Whitelist domain
inst.addToMediaList(url, LLMediaFilter::WHITELIST);
case 0: // Allow
if (gAudiop != NULL)
{
if (inst.getCurrentParcel() == LLViewerParcelMgr::getInstance()->getAgentParcel())
{
gAudiop->startInternetStream(url);
}
}
break;
case 2: // Blacklist domain
inst.addToMediaList(url, LLMediaFilter::BLACKLIST);
case 1: // Deny
if (gAudiop != NULL)
{
if (inst.getCurrentParcel() == LLViewerParcelMgr::getInstance()->getAgentParcel())
{
gAudiop->stopInternetStream();
}
}
break;
/*case 4: //Whitelist url
inst.addToMediaList(url, LLMediaFilter::WHITELIST, false);
if (gAudiop != NULL)
{
if (inst.getCurrentParcel() == LLViewerParcelMgr::getInstance()->getAgentParcel())
{
gAudiop->startInternetStream(url);
}
}
break;
case 5: //Blacklist url
inst.addToMediaList(url, LLMediaFilter::BLACKLIST, false);
if (gAudiop != NULL)
{
if (inst.getCurrentParcel() == LLViewerParcelMgr::getInstance()->getAgentParcel())
{
gAudiop->stopInternetStream();
}
}
break;*/
default:
// We should never be able to get here.
llassert(option);
break;
}
if (!queue.empty())
{
inst.clearQueuedAudio();
inst.filterAudioUrl(queue);
}
else if (inst.getQueuedMedia())
{
inst.clearQueuedMedia();
LLParcel* queued_media = inst.getQueuedMedia();
if (queued_media)
inst.filterMediaUrl(queued_media);
}
else
{
perform_queued_command(inst);
}
return false;
}
bool handle_media_filter_callback(const LLSD& notification, const LLSD& response, LLParcel* parcel)
{
LLMediaFilter& inst(LLMediaFilter::instance());
inst.setAlertStatus(false);
S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
const std::string& url = notification["payload"].asString();
LLParcel* queue = inst.getQueuedMedia();
switch(option)
{
case 2: // Whitelist domain
inst.addToMediaList(url, LLMediaFilter::WHITELIST);
case 0: // Allow
if (inst.getCurrentParcel() == LLViewerParcelMgr::getInstance()->getAgentParcel())
LLViewerParcelMedia::play(parcel);
break;
case 3: // Blacklist domain
inst.addToMediaList(url, LLMediaFilter::BLACKLIST);
case 1: // Deny
break;
case 4: //Whitelist url
inst.addToMediaList(url, LLMediaFilter::WHITELIST, false);
if (inst.getCurrentParcel() == LLViewerParcelMgr::getInstance()->getAgentParcel())
LLViewerParcelMedia::play(parcel);
break;
case 5:
inst.addToMediaList(url, LLMediaFilter::BLACKLIST, false);
break;
default:
// We should never be able to get here.
llassert(option);
break;
}
const std::string audio_queue = inst.getQueuedAudio();
if (queue)
{
inst.clearQueuedMedia();
inst.filterMediaUrl(queue);
}
else if (!audio_queue.empty())
{
inst.clearQueuedAudio();
inst.filterAudioUrl(audio_queue);
}
else
{
perform_queued_command(inst);
}
return false;
}
// Local Functions
std::string extractDomain(const std::string& in_url)
{
std::string url = in_url;
// First, find and strip any protocol prefix.
size_t pos = url.find("//");
if (pos != std::string::npos)
{
size_t count = url.size()-pos+2;
url = url.substr(pos+2, count);
}
// Now, look for a / marking a local part; if there is one,
// strip it and anything after.
pos = url.find("/");
if (pos != std::string::npos)
{
url = url.substr(0, pos);
}
// If there's a user{,:password}@ part, remove it,
pos = url.find("@");
if (pos != std::string::npos)
{
size_t count = url.size()-pos+1;
url = url.substr(pos+1, count);
}
// Finally, find and strip away any port number. This has to be done
// after the previous step, or else the extra : for the password,
// if supplied, will confuse things.
pos = url.find(":");
if (pos != std::string::npos)
{
url = url.substr(0, pos);
}
// Now map the whole thing to lowercase, since domain names aren't
// case sensitive.
std::transform(url.begin(), url.end(), url.begin(), ::tolower);
return url;
}