summaryrefslogtreecommitdiffstats
path: root/lib/libUPnP/Platinum/Source/Devices/MediaServer/PltFileMediaServer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libUPnP/Platinum/Source/Devices/MediaServer/PltFileMediaServer.cpp')
-rw-r--r--lib/libUPnP/Platinum/Source/Devices/MediaServer/PltFileMediaServer.cpp519
1 files changed, 519 insertions, 0 deletions
diff --git a/lib/libUPnP/Platinum/Source/Devices/MediaServer/PltFileMediaServer.cpp b/lib/libUPnP/Platinum/Source/Devices/MediaServer/PltFileMediaServer.cpp
new file mode 100644
index 0000000..421d0c2
--- /dev/null
+++ b/lib/libUPnP/Platinum/Source/Devices/MediaServer/PltFileMediaServer.cpp
@@ -0,0 +1,519 @@
+/*****************************************************************
+|
+| Platinum - File Media Server Delegate
+|
+| Copyright (c) 2004-2010, Plutinosoft, LLC.
+| All rights reserved.
+| http://www.plutinosoft.com
+|
+| This program is free software; you can redistribute it and/or
+| modify it under the terms of the GNU General Public License
+| as published by the Free Software Foundation; either version 2
+| of the License, or (at your option) any later version.
+|
+| OEMs, ISVs, VARs and other distributors that combine and
+| distribute commercially licensed software with Platinum software
+| and do not wish to distribute the source code for the commercially
+| licensed software under version 2, or (at your option) any later
+| version, of the GNU General Public License (the "GPL") must enter
+| into a commercial license agreement with Plutinosoft, LLC.
+| licensing@plutinosoft.com
+|
+| This program 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 General Public License for more details.
+|
+| You should have received a copy of the GNU General Public License
+| along with this program; see the file LICENSE.txt. If not, write to
+| the Free Software Foundation, Inc.,
+| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+| http://www.gnu.org/licenses/gpl-2.0.html
+|
+****************************************************************/
+
+/*----------------------------------------------------------------------
+| includes
++---------------------------------------------------------------------*/
+#include "PltUPnP.h"
+#include "PltFileMediaServer.h"
+#include "PltMediaItem.h"
+#include "PltService.h"
+#include "PltTaskManager.h"
+#include "PltHttpServer.h"
+#include "PltDidl.h"
+#include "PltVersion.h"
+#include "PltMimeType.h"
+
+NPT_SET_LOCAL_LOGGER("platinum.media.server.file.delegate")
+
+/*----------------------------------------------------------------------
+| PLT_FileMediaServerDelegate::PLT_FileMediaServerDelegate
++---------------------------------------------------------------------*/
+PLT_FileMediaServerDelegate::PLT_FileMediaServerDelegate(const char* url_root,
+ const char* file_root,
+ bool use_cache) :
+ m_UrlRoot(url_root),
+ m_FileRoot(file_root),
+ m_FilterUnknownOut(false),
+ m_UseCache(use_cache)
+{
+ /* Trim excess separators */
+ m_FileRoot.TrimRight("/\\");
+}
+
+/*----------------------------------------------------------------------
+| PLT_FileMediaServerDelegate::~PLT_FileMediaServerDelegate
++---------------------------------------------------------------------*/
+PLT_FileMediaServerDelegate::~PLT_FileMediaServerDelegate()
+{
+}
+
+/*----------------------------------------------------------------------
+| PLT_FileMediaServerDelegate::ProcessFileRequest
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_FileMediaServerDelegate::ProcessFileRequest(NPT_HttpRequest& request,
+ const NPT_HttpRequestContext& context,
+ NPT_HttpResponse& response)
+{
+ NPT_HttpUrlQuery query(request.GetUrl().GetQuery());
+
+ PLT_LOG_HTTP_REQUEST(NPT_LOG_LEVEL_FINE, "PLT_FileMediaServerDelegate::ProcessFileRequest:", &request);
+
+ if (request.GetMethod().Compare("GET") && request.GetMethod().Compare("HEAD")) {
+ response.SetStatus(500, "Internal Server Error");
+ return NPT_SUCCESS;
+ }
+
+ /* Extract file path from url */
+ NPT_String file_path;
+ NPT_CHECK_LABEL_WARNING(ExtractResourcePath(request.GetUrl(), file_path), failure);
+
+ /* Serve file */
+ NPT_CHECK_WARNING(ServeFile(request, context, response, NPT_FilePath::Create(m_FileRoot, file_path)));
+ return NPT_SUCCESS;
+
+failure:
+ response.SetStatus(404, "File Not Found");
+ return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+| PLT_FileMediaServerDelegate::ServeFile
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_FileMediaServerDelegate::ServeFile(const NPT_HttpRequest& request,
+ const NPT_HttpRequestContext& context,
+ NPT_HttpResponse& response,
+ const NPT_String& file_path)
+{
+ NPT_CHECK_WARNING(PLT_HttpServer::ServeFile(request, context, response, file_path));
+ return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+| PLT_FileMediaServerDelegate::OnBrowseMetadata
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_FileMediaServerDelegate::OnBrowseMetadata(PLT_ActionReference& action,
+ const char* object_id,
+ const char* filter,
+ NPT_UInt32 starting_index,
+ NPT_UInt32 requested_count,
+ const char* sort_criteria,
+ const PLT_HttpRequestContext& context)
+{
+ NPT_COMPILER_UNUSED(sort_criteria);
+ NPT_COMPILER_UNUSED(requested_count);
+ NPT_COMPILER_UNUSED(starting_index);
+
+ NPT_String didl;
+ PLT_MediaObjectReference item;
+
+ /* locate the file from the object ID */
+ NPT_String filepath;
+ if (NPT_FAILED(GetFilePath(object_id, filepath))) {
+ /* error */
+ NPT_LOG_WARNING("PLT_FileMediaServerDelegate::OnBrowse - ObjectID not found.");
+ action->SetError(701, "No Such Object.");
+ return NPT_FAILURE;
+ }
+
+ /* build the object didl */
+ item = BuildFromFilePath(filepath, context, true, false, (NPT_String(filter).Find("ALLIP")!=-1));
+ if (item.IsNull()) return NPT_FAILURE;
+ NPT_String tmp;
+ NPT_CHECK_SEVERE(PLT_Didl::ToDidl(*item.AsPointer(), filter, tmp));
+
+ /* add didl header and footer */
+ didl = didl_header + tmp + didl_footer;
+
+ NPT_CHECK_SEVERE(action->SetArgumentValue("Result", didl));
+ NPT_CHECK_SEVERE(action->SetArgumentValue("NumberReturned", "1"));
+ NPT_CHECK_SEVERE(action->SetArgumentValue("TotalMatches", "1"));
+
+ /* update ID may be wrong here, it should be the one of the container?
+ TODO: We need to keep track of the overall updateID of the CDS */
+ NPT_CHECK_SEVERE(action->SetArgumentValue("UpdateId", "1"));
+
+ return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+| PLT_FileMediaServerDelegate::OnBrowseDirectChildren
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_FileMediaServerDelegate::OnBrowseDirectChildren(PLT_ActionReference& action,
+ const char* object_id,
+ const char* filter,
+ NPT_UInt32 starting_index,
+ NPT_UInt32 requested_count,
+ const char* sort_criteria,
+ const PLT_HttpRequestContext& context)
+{
+ NPT_COMPILER_UNUSED(sort_criteria);
+
+ /* locate the file from the object ID */
+ NPT_String dir;
+ NPT_FileInfo info;
+ if (NPT_FAILED(GetFilePath(object_id, dir)) || NPT_FAILED(NPT_File::GetInfo(dir, &info)) || info.m_Type != NPT_FileInfo::FILE_TYPE_DIRECTORY) {
+ /* error */
+ NPT_LOG_WARNING_1("ObjectID \'%s\' not found or not allowed", object_id);
+ action->SetError(701, "No such Object");
+ NPT_CHECK_WARNING(NPT_FAILURE);
+ }
+
+ /* get uuid from device via action reference */
+ NPT_String uuid = action->GetActionDesc().GetService()->GetDevice()->GetUUID();
+
+ /* Try to get list from cache if allowed */
+ NPT_Result res;
+ NPT_Reference<NPT_List<NPT_String> > entries;
+ NPT_TimeStamp cached_entries_time;
+ if (!m_UseCache || NPT_FAILED(m_DirCache.Get(uuid, dir, entries, &cached_entries_time)) || cached_entries_time < info.m_ModificationTime) {
+ /* if not found in cache or if current dir has newer modified time fetch fresh new list from source */
+ entries = new NPT_List<NPT_String>();
+ res = NPT_File::ListDir(dir, *entries);
+ if (NPT_FAILED(res)) {
+ NPT_LOG_WARNING_1("PLT_FileMediaServerDelegate::OnBrowseDirectChildren - failed to open dir %s", (const char*) dir);
+ NPT_CHECK_WARNING(res);
+ }
+
+ /* sort results according to modification date */
+ res = entries->Sort(NPT_FileDateComparator(dir));
+ if (NPT_FAILED(res)) {
+ NPT_LOG_WARNING_1("PLT_FileMediaServerDelegate::OnBrowseDirectChildren - failed to open sort dir %s", (const char*) dir);
+ NPT_CHECK_WARNING(res);
+ }
+
+ /* add new list to cache */
+ if (m_UseCache) {
+ m_DirCache.Put(uuid, dir, entries, &info.m_ModificationTime);
+ }
+ }
+
+ unsigned long cur_index = 0;
+ unsigned long num_returned = 0;
+ unsigned long total_matches = 0;
+ NPT_String didl = didl_header;
+ bool allip = (NPT_String(filter).Find("ALLIP") != -1);
+
+ PLT_MediaObjectReference item;
+ for (NPT_List<NPT_String>::Iterator it = entries->GetFirstItem();
+ it;
+ ++it) {
+ NPT_String filepath = NPT_FilePath::Create(dir, *it);
+
+ /* verify we want to process this file first */
+ if (!ProcessFile(filepath, filter)) continue;
+
+ /* build item object from file path */
+ item = BuildFromFilePath(filepath,
+ context,
+ true,
+ false,
+ allip);
+
+ /* generate didl if within range requested */
+ if (!item.IsNull()) {
+ if ((cur_index >= starting_index) &&
+ ((num_returned < requested_count) || (requested_count == 0))) {
+ NPT_String tmp;
+ NPT_CHECK_SEVERE(PLT_Didl::ToDidl(*item.AsPointer(), filter, tmp));
+
+ didl += tmp;
+ ++num_returned;
+ }
+ ++cur_index;
+ ++total_matches;
+ }
+ };
+
+ didl += didl_footer;
+
+ NPT_LOG_FINE_6("BrowseDirectChildren from %s returning %d-%lu/%lu objects (%lu out of %d requested)",
+ (const char*)context.GetLocalAddress().GetIpAddress().ToString(),
+ starting_index, starting_index+num_returned, total_matches, num_returned, requested_count);
+
+ NPT_CHECK_SEVERE(action->SetArgumentValue("Result", didl));
+ NPT_CHECK_SEVERE(action->SetArgumentValue("NumberReturned", NPT_String::FromInteger(num_returned)));
+ NPT_CHECK_SEVERE(action->SetArgumentValue("TotalMatches", NPT_String::FromInteger(total_matches))); // 0 means we don't know how many we have but most browsers don't like that!!
+ NPT_CHECK_SEVERE(action->SetArgumentValue("UpdateId", "1"));
+
+ return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+| PLT_FileMediaServerDelegate::OnSearchContainer
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_FileMediaServerDelegate::OnSearchContainer(PLT_ActionReference& action,
+ const char* object_id,
+ const char* search_criteria,
+ const char* /* filter */,
+ NPT_UInt32 /* starting_index */,
+ NPT_UInt32 /* requested_count */,
+ const char* /* sort_criteria */,
+ const PLT_HttpRequestContext& /* context */)
+{
+ /* parse search criteria */
+
+ /* TODO: HACK TO PASS DLNA */
+ if (search_criteria && NPT_StringsEqual(search_criteria, "Unknownfieldname")) {
+ /* error */
+ NPT_LOG_WARNING_1("Unsupported or invalid search criteria %s", search_criteria);
+ action->SetError(708, "Unsupported or invalid search criteria");
+ return NPT_FAILURE;
+ }
+
+ /* locate the file from the object ID */
+ NPT_String dir;
+ if (NPT_FAILED(GetFilePath(object_id, dir))) {
+ /* error */
+ NPT_LOG_WARNING("ObjectID not found.");
+ action->SetError(710, "No Such Container.");
+ return NPT_FAILURE;
+ }
+
+ /* retrieve the item type */
+ NPT_FileInfo info;
+ NPT_Result res = NPT_File::GetInfo(dir, &info);
+ if (NPT_FAILED(res) || (info.m_Type != NPT_FileInfo::FILE_TYPE_DIRECTORY)) {
+ /* error */
+ NPT_LOG_WARNING("No such container");
+ action->SetError(710, "No such container");
+ return NPT_FAILURE;
+ }
+
+ return NPT_ERROR_NOT_IMPLEMENTED;
+}
+
+/*----------------------------------------------------------------------
+| PLT_FileMediaServerDelegate::GetFilePath
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_FileMediaServerDelegate::GetFilePath(const char* object_id,
+ NPT_String& filepath)
+{
+ if (!object_id) return NPT_ERROR_INVALID_PARAMETERS;
+
+ filepath = m_FileRoot;
+
+ /* object id is formatted as 0/<filepath> */
+ if (NPT_StringLength(object_id) >= 1) {
+ filepath += (object_id + (object_id[0]=='0'?1:0));
+ }
+
+ return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+| PLT_FileMediaServerDelegate::BuildSafeResourceUri
++---------------------------------------------------------------------*/
+NPT_String
+PLT_FileMediaServerDelegate::BuildSafeResourceUri(const NPT_HttpUrl& base_uri,
+ const char* host,
+ const char* file_path)
+{
+ NPT_String result;
+ NPT_HttpUrl uri = base_uri;
+
+ if (host) uri.SetHost(host);
+
+ NPT_String uri_path = uri.GetPath();
+ if (!uri_path.EndsWith("/")) uri_path += "/";
+
+ /* some controllers (like WMP) will call us with an already urldecoded version.
+ We're intentionally prepending a known urlencoded string
+ to detect it when we receive the request urlencoded or already decoded to avoid double decoding*/
+ uri_path += "%/";
+ uri_path += file_path;
+
+ /* set path */
+ uri.SetPath(uri_path);
+
+ /* 360 hack: force inclusion of port in case it's 80 */
+ return uri.ToStringWithDefaultPort(0);
+}
+
+/*----------------------------------------------------------------------
+| PLT_FileMediaServerDelegate::ExtractResourcePath
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_FileMediaServerDelegate::ExtractResourcePath(const NPT_HttpUrl& url,
+ NPT_String& file_path)
+{
+ /* Extract non decoded path, we need to autodetect urlencoding */
+ NPT_String uri_path = url.GetPath();
+ NPT_String url_root_encode = NPT_Uri::PercentEncode(m_UrlRoot, NPT_Uri::PathCharsToEncode);
+
+ NPT_Ordinal skip = 0;
+ if (uri_path.StartsWith(m_UrlRoot)) {
+ skip = m_UrlRoot.GetLength();
+ } else if (uri_path.StartsWith(url_root_encode)) {
+ skip = url_root_encode.GetLength();
+ } else {
+ return NPT_FAILURE;
+ }
+
+ /* account for extra slash */
+ skip += ((m_UrlRoot=="/")?0:1);
+ file_path = uri_path.SubString(skip);
+
+ /* detect if client such as WMP sent a non urlencoded url */
+ if (file_path.StartsWith("%/")) {
+ NPT_LOG_FINE("Received a urldecoded version of our url!");
+ file_path.Erase(0, 2);
+ } else {
+ /* remove our prepended string we used to detect urldecoded version */
+ if (file_path.StartsWith("%25/")) file_path.Erase(0, 4);
+
+ /* ok to urldecode */
+ file_path = NPT_Uri::PercentDecode(file_path);
+ }
+
+ return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+| PLT_FileMediaServerDelegate::BuildResourceUri
++---------------------------------------------------------------------*/
+NPT_String
+PLT_FileMediaServerDelegate::BuildResourceUri(const NPT_HttpUrl& base_uri,
+ const char* host,
+ const char* file_path)
+{
+ return BuildSafeResourceUri(base_uri, host, file_path);
+}
+
+/*----------------------------------------------------------------------
+| PLT_FileMediaServerDelegate::BuildFromFilePath
++---------------------------------------------------------------------*/
+PLT_MediaObject*
+PLT_FileMediaServerDelegate::BuildFromFilePath(const NPT_String& filepath,
+ const PLT_HttpRequestContext& context,
+ bool with_count /* = true */,
+ bool keep_extension_in_title /* = false */,
+ bool allip /* = false */)
+{
+ NPT_String root = m_FileRoot;
+ PLT_MediaItemResource resource;
+ PLT_MediaObject* object = NULL;
+
+ NPT_LOG_FINEST_1("Building didl for file '%s'", (const char*)filepath);
+
+ /* retrieve the entry type (directory or file) */
+ NPT_FileInfo info;
+ NPT_CHECK_LABEL_FATAL(NPT_File::GetInfo(filepath, &info), failure);
+
+ if (info.m_Type == NPT_FileInfo::FILE_TYPE_REGULAR) {
+ object = new PLT_MediaItem();
+
+ /* Set the title using the filename for now */
+ object->m_Title = NPT_FilePath::BaseName(filepath, keep_extension_in_title);
+ if (object->m_Title.GetLength() == 0) goto failure;
+
+ /* make sure we return something with a valid mimetype */
+ if (m_FilterUnknownOut &&
+ NPT_StringsEqual(PLT_MimeType::GetMimeType(filepath, &context),
+ "application/octet-stream")) {
+ goto failure;
+ }
+
+ /* Set the protocol Info from the extension */
+ resource.m_ProtocolInfo = PLT_ProtocolInfo::GetProtocolInfo(filepath, true, &context);
+ if (!resource.m_ProtocolInfo.IsValid()) goto failure;
+
+ /* Set the resource file size */
+ resource.m_Size = info.m_Size;
+
+ /* format the resource URI */
+ NPT_String url = filepath.SubString(root.GetLength()+1);
+
+ // get list of ip addresses
+ NPT_List<NPT_IpAddress> ips;
+ NPT_CHECK_LABEL_SEVERE(PLT_UPnPMessageHelper::GetIPAddresses(ips), failure);
+
+ /* if we're passed an interface where we received the request from
+ move the ip to the top so that it is used for the first resource */
+ if (context.GetLocalAddress().GetIpAddress().ToString() != "0.0.0.0") {
+ ips.Remove(context.GetLocalAddress().GetIpAddress());
+ ips.Insert(ips.GetFirstItem(), context.GetLocalAddress().GetIpAddress());
+ } else if (!allip) {
+ NPT_LOG_WARNING("Couldn't determine local interface IP so we might return an unreachable IP");
+ }
+ object->m_ObjectClass.type = PLT_MediaItem::GetUPnPClass(filepath, &context);
+
+ /* add as many resources as we have interfaces s*/
+ NPT_HttpUrl base_uri("127.0.0.1", context.GetLocalAddress().GetPort(), NPT_HttpUrl::PercentEncode(m_UrlRoot, NPT_Uri::PathCharsToEncode));
+ NPT_List<NPT_IpAddress>::Iterator ip = ips.GetFirstItem();
+ while (ip) {
+ resource.m_Uri = BuildResourceUri(base_uri, ip->ToString(), url);
+ object->m_Resources.Add(resource);
+ ++ip;
+
+ /* if we only want the one resource reachable by client */
+ if (!allip) break;
+ }
+ } else {
+ object = new PLT_MediaContainer;
+
+ /* Assign a title for this container */
+ if (filepath.Compare(root, true) == 0) {
+ object->m_Title = "Root";
+ } else {
+ object->m_Title = NPT_FilePath::BaseName(filepath, keep_extension_in_title);
+ if (object->m_Title.GetLength() == 0) goto failure;
+ }
+
+ /* Get the number of children for this container */
+ NPT_LargeSize count = 0;
+ if (with_count && NPT_SUCCEEDED(NPT_File::GetSize(filepath, count))) {
+ ((PLT_MediaContainer*)object)->m_ChildrenCount = (NPT_Int32)count;
+ }
+
+ object->m_ObjectClass.type = "object.container.storageFolder";
+ }
+
+ /* is it the root? */
+ if (filepath.Compare(root, true) == 0) {
+ object->m_ParentID = "-1";
+ object->m_ObjectID = "0";
+ } else {
+ NPT_String directory = NPT_FilePath::DirName(filepath);
+ /* is the parent path the root? */
+ if (directory.GetLength() == root.GetLength()) {
+ object->m_ParentID = "0";
+ } else {
+ object->m_ParentID = "0" + filepath.SubString(root.GetLength(), directory.GetLength() - root.GetLength());
+ }
+ object->m_ObjectID = "0" + filepath.SubString(root.GetLength());
+ }
+
+ return object;
+
+failure:
+ delete object;
+ return NULL;
+}