/** * @file llmediaentry.cpp * @brief This is a single instance of media data related to the face of a prim * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * 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 * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "linden_common.h" #include "llmediaentry.h" #include "lllslconstants.h" #include <boost/regex.hpp> // LLSD key defines // DO NOT REORDER OR REMOVE THESE! // Some LLSD keys. Do not change! #define MEDIA_ALT_IMAGE_ENABLE_KEY_STR "alt_image_enable" #define MEDIA_CONTROLS_KEY_STR "controls" #define MEDIA_CURRENT_URL_KEY_STR "current_url" #define MEDIA_HOME_URL_KEY_STR "home_url" #define MEDIA_AUTO_LOOP_KEY_STR "auto_loop" #define MEDIA_AUTO_PLAY_KEY_STR "auto_play" #define MEDIA_AUTO_SCALE_KEY_STR "auto_scale" #define MEDIA_AUTO_ZOOM_KEY_STR "auto_zoom" #define MEDIA_FIRST_CLICK_INTERACT_KEY_STR "first_click_interact" #define MEDIA_WIDTH_PIXELS_KEY_STR "width_pixels" #define MEDIA_HEIGHT_PIXELS_KEY_STR "height_pixels" // "security" fields #define MEDIA_WHITELIST_ENABLE_KEY_STR "whitelist_enable" #define MEDIA_WHITELIST_KEY_STR "whitelist" // "permissions" fields #define MEDIA_PERMS_INTERACT_KEY_STR "perms_interact" #define MEDIA_PERMS_CONTROL_KEY_STR "perms_control" // "general" fields const char* LLMediaEntry::ALT_IMAGE_ENABLE_KEY = MEDIA_ALT_IMAGE_ENABLE_KEY_STR; const char* LLMediaEntry::CONTROLS_KEY = MEDIA_CONTROLS_KEY_STR; const char* LLMediaEntry::CURRENT_URL_KEY = MEDIA_CURRENT_URL_KEY_STR; const char* LLMediaEntry::HOME_URL_KEY = MEDIA_HOME_URL_KEY_STR; const char* LLMediaEntry::AUTO_LOOP_KEY = MEDIA_AUTO_LOOP_KEY_STR; const char* LLMediaEntry::AUTO_PLAY_KEY = MEDIA_AUTO_PLAY_KEY_STR; const char* LLMediaEntry::AUTO_SCALE_KEY = MEDIA_AUTO_SCALE_KEY_STR; const char* LLMediaEntry::AUTO_ZOOM_KEY = MEDIA_AUTO_ZOOM_KEY_STR; const char* LLMediaEntry::FIRST_CLICK_INTERACT_KEY = MEDIA_FIRST_CLICK_INTERACT_KEY_STR; const char* LLMediaEntry::WIDTH_PIXELS_KEY = MEDIA_WIDTH_PIXELS_KEY_STR; const char* LLMediaEntry::HEIGHT_PIXELS_KEY = MEDIA_HEIGHT_PIXELS_KEY_STR; // "security" fields const char* LLMediaEntry::WHITELIST_ENABLE_KEY = MEDIA_WHITELIST_ENABLE_KEY_STR; const char* LLMediaEntry::WHITELIST_KEY = MEDIA_WHITELIST_KEY_STR; // "permissions" fields const char* LLMediaEntry::PERMS_INTERACT_KEY = MEDIA_PERMS_INTERACT_KEY_STR; const char* LLMediaEntry::PERMS_CONTROL_KEY = MEDIA_PERMS_CONTROL_KEY_STR; #define DEFAULT_URL_PREFIX "http://" // Constructor(s) LLMediaEntry::LLMediaEntry() : mAltImageEnable(false), mControls(STANDARD), mCurrentURL(""), mHomeURL(""), mAutoLoop(false), mAutoPlay(false), mAutoScale(false), mAutoZoom(false), mFirstClickInteract(false), mWidthPixels(0), mHeightPixels(0), mWhiteListEnable(false), // mWhiteList mPermsInteract(PERM_ALL), mPermsControl(PERM_ALL), mMediaIDp(NULL) { } LLMediaEntry::LLMediaEntry(const LLMediaEntry &rhs) : mMediaIDp(NULL) { // "general" fields mAltImageEnable = rhs.mAltImageEnable; mControls = rhs.mControls; mCurrentURL = rhs.mCurrentURL; mHomeURL = rhs.mHomeURL; mAutoLoop = rhs.mAutoLoop; mAutoPlay = rhs.mAutoPlay; mAutoScale = rhs.mAutoScale; mAutoZoom = rhs.mAutoZoom; mFirstClickInteract = rhs.mFirstClickInteract; mWidthPixels = rhs.mWidthPixels; mHeightPixels = rhs.mHeightPixels; // "security" fields mWhiteListEnable = rhs.mWhiteListEnable; mWhiteList = rhs.mWhiteList; // "permissions" fields mPermsInteract = rhs.mPermsInteract; mPermsControl = rhs.mPermsControl; } LLMediaEntry::~LLMediaEntry() { if (NULL != mMediaIDp) { delete mMediaIDp; } } LLSD LLMediaEntry::asLLSD() const { LLSD sd; asLLSD(sd); return sd; } // // LLSD functions // void LLMediaEntry::asLLSD(LLSD& sd) const { // "general" fields sd[ALT_IMAGE_ENABLE_KEY] = mAltImageEnable; sd[CONTROLS_KEY] = (LLSD::Integer)mControls; sd[CURRENT_URL_KEY] = mCurrentURL; sd[HOME_URL_KEY] = mHomeURL; sd[AUTO_LOOP_KEY] = mAutoLoop; sd[AUTO_PLAY_KEY] = mAutoPlay; sd[AUTO_SCALE_KEY] = mAutoScale; sd[AUTO_ZOOM_KEY] = mAutoZoom; sd[FIRST_CLICK_INTERACT_KEY] = mFirstClickInteract; sd[WIDTH_PIXELS_KEY] = mWidthPixels; sd[HEIGHT_PIXELS_KEY] = mHeightPixels; // "security" fields sd[WHITELIST_ENABLE_KEY] = mWhiteListEnable; sd.erase(WHITELIST_KEY); for (U32 i=0; i<mWhiteList.size(); i++) { sd[WHITELIST_KEY].append(mWhiteList[i]); } // "permissions" fields sd[PERMS_INTERACT_KEY] = mPermsInteract; sd[PERMS_CONTROL_KEY] = mPermsControl; } // static bool LLMediaEntry::checkLLSD(const LLSD& sd) { if (sd.isUndefined()) return true; LLMediaEntry temp; return temp.fromLLSDInternal(sd, true); } void LLMediaEntry::fromLLSD(const LLSD& sd) { (void)fromLLSDInternal(sd, true); } void LLMediaEntry::mergeFromLLSD(const LLSD& sd) { (void)fromLLSDInternal(sd, false); } // *NOTE: returns true if NO failures to set occurred, false otherwise. // However, be aware that if a failure to set does occur, it does // not stop setting fields from the LLSD! bool LLMediaEntry::fromLLSDInternal(const LLSD& sd, bool overwrite) { // *HACK: we sort of cheat here and assume that status is a // bit field. We "or" into status and instead of returning // it, we return whether it finishes off as LSL_STATUS_OK or not. U32 status = LSL_STATUS_OK; // "general" fields if ( overwrite || sd.has(ALT_IMAGE_ENABLE_KEY) ) { status |= setAltImageEnable( sd[ALT_IMAGE_ENABLE_KEY] ); } if ( overwrite || sd.has(CONTROLS_KEY) ) { status |= setControls( (MediaControls)(LLSD::Integer)sd[CONTROLS_KEY] ); } if ( overwrite || sd.has(CURRENT_URL_KEY) ) { // Don't check whitelist status |= setCurrentURLInternal( sd[CURRENT_URL_KEY], false ); } if ( overwrite || sd.has(HOME_URL_KEY) ) { status |= setHomeURL( sd[HOME_URL_KEY] ); } if ( overwrite || sd.has(AUTO_LOOP_KEY) ) { status |= setAutoLoop( sd[AUTO_LOOP_KEY] ); } if ( overwrite || sd.has(AUTO_PLAY_KEY) ) { status |= setAutoPlay( sd[AUTO_PLAY_KEY] ); } if ( overwrite || sd.has(AUTO_SCALE_KEY) ) { status |= setAutoScale( sd[AUTO_SCALE_KEY] ); } if ( overwrite || sd.has(AUTO_ZOOM_KEY) ) { status |= setAutoZoom( sd[AUTO_ZOOM_KEY] ); } if ( overwrite || sd.has(FIRST_CLICK_INTERACT_KEY) ) { status |= setFirstClickInteract( sd[FIRST_CLICK_INTERACT_KEY] ); } if ( overwrite || sd.has(WIDTH_PIXELS_KEY) ) { status |= setWidthPixels( (LLSD::Integer)sd[WIDTH_PIXELS_KEY] ); } if ( overwrite || sd.has(HEIGHT_PIXELS_KEY) ) { status |= setHeightPixels( (LLSD::Integer)sd[HEIGHT_PIXELS_KEY] ); } // "security" fields if ( overwrite || sd.has(WHITELIST_ENABLE_KEY) ) { status |= setWhiteListEnable( sd[WHITELIST_ENABLE_KEY] ); } if ( overwrite || sd.has(WHITELIST_KEY) ) { status |= setWhiteList( sd[WHITELIST_KEY] ); } // "permissions" fields if ( overwrite || sd.has(PERMS_INTERACT_KEY) ) { status |= setPermsInteract( 0xff & (LLSD::Integer)sd[PERMS_INTERACT_KEY] ); } if ( overwrite || sd.has(PERMS_CONTROL_KEY) ) { status |= setPermsControl( 0xff & (LLSD::Integer)sd[PERMS_CONTROL_KEY] ); } return LSL_STATUS_OK == status; } LLMediaEntry& LLMediaEntry::operator=(const LLMediaEntry &rhs) { if (this != &rhs) { // "general" fields mAltImageEnable = rhs.mAltImageEnable; mControls = rhs.mControls; mCurrentURL = rhs.mCurrentURL; mHomeURL = rhs.mHomeURL; mAutoLoop = rhs.mAutoLoop; mAutoPlay = rhs.mAutoPlay; mAutoScale = rhs.mAutoScale; mAutoZoom = rhs.mAutoZoom; mFirstClickInteract = rhs.mFirstClickInteract; mWidthPixels = rhs.mWidthPixels; mHeightPixels = rhs.mHeightPixels; // "security" fields mWhiteListEnable = rhs.mWhiteListEnable; mWhiteList = rhs.mWhiteList; // "permissions" fields mPermsInteract = rhs.mPermsInteract; mPermsControl = rhs.mPermsControl; } return *this; } bool LLMediaEntry::operator==(const LLMediaEntry &rhs) const { return ( // "general" fields mAltImageEnable == rhs.mAltImageEnable && mControls == rhs.mControls && mCurrentURL == rhs.mCurrentURL && mHomeURL == rhs.mHomeURL && mAutoLoop == rhs.mAutoLoop && mAutoPlay == rhs.mAutoPlay && mAutoScale == rhs.mAutoScale && mAutoZoom == rhs.mAutoZoom && mFirstClickInteract == rhs.mFirstClickInteract && mWidthPixels == rhs.mWidthPixels && mHeightPixels == rhs.mHeightPixels && // "security" fields mWhiteListEnable == rhs.mWhiteListEnable && mWhiteList == rhs.mWhiteList && // "permissions" fields mPermsInteract == rhs.mPermsInteract && mPermsControl == rhs.mPermsControl ); } bool LLMediaEntry::operator!=(const LLMediaEntry &rhs) const { return ( // "general" fields mAltImageEnable != rhs.mAltImageEnable || mControls != rhs.mControls || mCurrentURL != rhs.mCurrentURL || mHomeURL != rhs.mHomeURL || mAutoLoop != rhs.mAutoLoop || mAutoPlay != rhs.mAutoPlay || mAutoScale != rhs.mAutoScale || mAutoZoom != rhs.mAutoZoom || mFirstClickInteract != rhs.mFirstClickInteract || mWidthPixels != rhs.mWidthPixels || mHeightPixels != rhs.mHeightPixels || // "security" fields mWhiteListEnable != rhs.mWhiteListEnable || mWhiteList != rhs.mWhiteList || // "permissions" fields mPermsInteract != rhs.mPermsInteract || mPermsControl != rhs.mPermsControl ); } U32 LLMediaEntry::setWhiteList( const std::vector<std::string> &whitelist ) { // *NOTE: This code is VERY similar to the setWhitelist below. // IF YOU CHANGE THIS IMPLEMENTATION, BE SURE TO CHANGE THE OTHER! U32 size = 0; U32 count = 0; // First count to make sure the size constraint is not violated std::vector<std::string>::const_iterator iter = whitelist.begin(); std::vector<std::string>::const_iterator end = whitelist.end(); for ( ; iter < end; ++iter) { const std::string &entry = (*iter); size += entry.length() + 1; // Include one for \0 count ++; if (size > MAX_WHITELIST_SIZE || count > MAX_WHITELIST_COUNT) { return LSL_STATUS_BOUNDS_ERROR; } } // Next clear the vector mWhiteList.clear(); // Then re-iterate and copy entries iter = whitelist.begin(); for ( ; iter < end; ++iter) { const std::string &entry = (*iter); mWhiteList.push_back(entry); } return LSL_STATUS_OK; } U32 LLMediaEntry::setWhiteList( const LLSD &whitelist ) { // If whitelist is undef, the whitelist is cleared if (whitelist.isUndefined()) { mWhiteList.clear(); return LSL_STATUS_OK; } // However, if the whitelist is an empty array, erase it. if (whitelist.isArray()) { // *NOTE: This code is VERY similar to the setWhitelist above. // IF YOU CHANGE THIS IMPLEMENTATION, BE SURE TO CHANGE THE OTHER! U32 size = 0; U32 count = 0; // First check to make sure the size and count constraints are not violated LLSD::array_const_iterator iter = whitelist.beginArray(); LLSD::array_const_iterator end = whitelist.endArray(); for ( ; iter < end; ++iter) { const std::string &entry = (*iter).asString(); size += entry.length() + 1; // Include one for \0 count ++; if (size > MAX_WHITELIST_SIZE || count > MAX_WHITELIST_COUNT) { return LSL_STATUS_BOUNDS_ERROR; } } // Next clear the vector mWhiteList.clear(); // Then re-iterate and copy entries iter = whitelist.beginArray(); for ( ; iter < end; ++iter) { const std::string &entry = (*iter).asString(); mWhiteList.push_back(entry); } return LSL_STATUS_OK; } else { return LSL_STATUS_MALFORMED_PARAMS; } } static void prefix_with(std::string &str, const char *chars, const char *prefix) { // Given string 'str', prefix all instances of any character in 'chars' // with 'prefix' size_t found = str.find_first_of(chars); size_t prefix_len = strlen(prefix); while (found != std::string::npos) { str.insert(found, prefix, prefix_len); found = str.find_first_of(chars, found+prefix_len+1); } } static bool pattern_match(const std::string &candidate_str, const std::string &pattern) { // If the pattern is empty, it matches if (pattern.empty()) return true; // 'pattern' is a glob pattern, we only accept '*' chars // copy it std::string expression = pattern; // Escape perl's regexp chars with a backslash, except all "*" chars prefix_with(expression, ".[{()\\+?|^$", "\\"); prefix_with(expression, "*", "."); // case-insensitive matching: boost::regex regexp(expression, boost::regex::perl|boost::regex::icase); return boost::regex_match(candidate_str, regexp); } bool LLMediaEntry::checkCandidateUrl(const std::string& url) const { if (getWhiteListEnable()) { return checkUrlAgainstWhitelist(url, getWhiteList()); } else { return true; } } // static bool LLMediaEntry::checkUrlAgainstWhitelist(const std::string& url, const std::vector<std::string> &whitelist) { bool passes = true; // *NOTE: no entries? Don't check if (whitelist.size() > 0) { passes = false; // Case insensitive: the reason why we toUpper both this and the // filter std::string candidate_url = url; // Use lluri to see if there is a path part in the candidate URL. No path? Assume "/" LLURI candidate_uri(candidate_url); std::vector<std::string>::const_iterator iter = whitelist.begin(); std::vector<std::string>::const_iterator end = whitelist.end(); for ( ; iter < end; ++iter ) { std::string filter = *iter; LLURI filter_uri(filter); bool scheme_passes = pattern_match( candidate_uri.scheme(), filter_uri.scheme() ); if (filter_uri.scheme().empty()) { filter_uri = LLURI(DEFAULT_URL_PREFIX + filter); } bool authority_passes = pattern_match( candidate_uri.authority(), filter_uri.authority() ); bool path_passes = pattern_match( candidate_uri.escapedPath(), filter_uri.escapedPath() ); if (scheme_passes && authority_passes && path_passes) { passes = true; break; } } } return passes; } U32 LLMediaEntry::setStringFieldWithLimit( std::string &field, const std::string &value, U32 limit ) { if ( value.length() > limit ) { return LSL_STATUS_BOUNDS_ERROR; } else { field = value; return LSL_STATUS_OK; } } U32 LLMediaEntry::setControls(LLMediaEntry::MediaControls controls) { if (controls == STANDARD || controls == MINI) { mControls = controls; return LSL_STATUS_OK; } return LSL_STATUS_BOUNDS_ERROR; } U32 LLMediaEntry::setPermsInteract( U8 val ) { mPermsInteract = val & PERM_MASK; return LSL_STATUS_OK; } U32 LLMediaEntry::setPermsControl( U8 val ) { mPermsControl = val & PERM_MASK; return LSL_STATUS_OK; } U32 LLMediaEntry::setCurrentURL(const std::string& current_url) { return setCurrentURLInternal( current_url, true ); } U32 LLMediaEntry::setCurrentURLInternal(const std::string& current_url, bool check_whitelist) { if ( ! check_whitelist || checkCandidateUrl(current_url)) { return setStringFieldWithLimit( mCurrentURL, current_url, MAX_URL_LENGTH ); } else { return LSL_STATUS_WHITELIST_FAILED; } } U32 LLMediaEntry::setHomeURL(const std::string& home_url) { return setStringFieldWithLimit( mHomeURL, home_url, MAX_URL_LENGTH ); } U32 LLMediaEntry::setWidthPixels(U16 width) { if (width > MAX_WIDTH_PIXELS) return LSL_STATUS_BOUNDS_ERROR; mWidthPixels = width; return LSL_STATUS_OK; } U32 LLMediaEntry::setHeightPixels(U16 height) { if (height > MAX_HEIGHT_PIXELS) return LSL_STATUS_BOUNDS_ERROR; mHeightPixels = height; return LSL_STATUS_OK; } const LLUUID &LLMediaEntry::getMediaID() const { // Lazily generate media ID if (NULL == mMediaIDp) { mMediaIDp = new LLUUID(); mMediaIDp->generate(); } return *mMediaIDp; }