diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /lib/libUPnP/Platinum/Source/Apps/MediaCrawler/MediaCrawler.cpp | |
parent | Initial commit. (diff) | |
download | kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip |
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/libUPnP/Platinum/Source/Apps/MediaCrawler/MediaCrawler.cpp')
-rw-r--r-- | lib/libUPnP/Platinum/Source/Apps/MediaCrawler/MediaCrawler.cpp | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/lib/libUPnP/Platinum/Source/Apps/MediaCrawler/MediaCrawler.cpp b/lib/libUPnP/Platinum/Source/Apps/MediaCrawler/MediaCrawler.cpp new file mode 100644 index 0000000..ed0c726 --- /dev/null +++ b/lib/libUPnP/Platinum/Source/Apps/MediaCrawler/MediaCrawler.cpp @@ -0,0 +1,566 @@ +/***************************************************************** +| +| Platinum - Media Crawler +| +| 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 "PltVersion.h" +#include "MediaCrawler.h" +#include "PltLeaks.h" +#include "PltMediaServer.h" +#include "PltHttpServer.h" +#include "PltDidl.h" +#include "PltUtilities.h" + +NPT_SET_LOCAL_LOGGER("platinum.apps.crawler") + +/*---------------------------------------------------------------------- +| CMediaCrawler::CMediaCrawler ++---------------------------------------------------------------------*/ +CMediaCrawler::CMediaCrawler(PLT_CtrlPointReference& ctrlPoint, + const char* friendly_name, + bool show_ip, + const char* udn /* = NULL */, + unsigned int port /* = 0 */) : + PLT_MediaBrowser(ctrlPoint, NULL), + PLT_MediaServer(friendly_name, show_ip, udn, port) +{ +} + +/*---------------------------------------------------------------------- +| CMediaCrawler::~CMediaCrawler ++---------------------------------------------------------------------*/ +CMediaCrawler::~CMediaCrawler() +{ +} + +/*---------------------------------------------------------------------- +| CMediaCrawler::SplitObjectId ++---------------------------------------------------------------------*/ +NPT_Result +CMediaCrawler::SplitObjectId(const NPT_String& object_id, NPT_String& server_uuid, NPT_String& server_object_id) +{ + // reset output params + server_uuid = ""; + server_object_id = ""; + + if (object_id.GetLength() == 0 || object_id[0] != '0') + return NPT_ERROR_INVALID_FORMAT; + + if (object_id.GetLength() > 1) { + if (object_id[1] != '/') return NPT_ERROR_INVALID_FORMAT; + + server_uuid = object_id.SubString(2); + + // look for next delimiter + int index = server_uuid.Find('/'); + if (index >= 0) { + server_object_id = server_uuid.SubString(index+1); + server_uuid.SetLength(index); + } + } + + return NPT_SUCCESS; +} + +/*---------------------------------------------------------------------- +| CMediaCrawler::FormatObjectId ++---------------------------------------------------------------------*/ +NPT_String +CMediaCrawler::FormatObjectId(const NPT_String& server_uuid, const NPT_String& server_object_id) +{ + NPT_String object_id = NPT_String("0/") + server_uuid; + if (server_object_id.GetLength()) + object_id += NPT_String("/") + server_object_id; + + return object_id; +} + +/*---------------------------------------------------------------------- +| PLT_MediaServer::OnBrowse ++---------------------------------------------------------------------*/ +NPT_Result +CMediaCrawler::OnBrowse(PLT_ActionReference& action, + const PLT_HttpRequestContext& context) +{ + NPT_Result res; + + NPT_String object_id; + if (NPT_FAILED(action->GetArgumentValue("ObjectId", object_id))) { + NPT_LOG_WARNING("PLT_FileMediaServer::OnBrowse - invalid arguments."); + return NPT_FAILURE; + } + + NPT_LOG_FINE_1("PLT_FileMediaServer::OnOnBrowse - id = %s\r\n", (const char*)object_id); + + // extract server uuid and server object id from our object id + NPT_String server_uuid; + NPT_String server_object_id; + if (NPT_FAILED(SplitObjectId(object_id, server_uuid, server_object_id))) { + /* error */ + NPT_LOG_WARNING("CMediaCrawler::OnBrowse - ObjectID not found."); + action->SetError(701, "No Such Object."); + return NPT_FAILURE; + } + + // root ? + if (server_uuid.GetLength() == 0) { + res = OnBrowseRoot(action); + } else { + // we have a device to browse + // is it device root? + if (server_object_id.GetLength() == 0) { + server_object_id = "0"; + } + res = OnBrowseDevice(action, server_uuid, server_object_id, context); + } + + if (NPT_FAILED(res) && (action->GetErrorCode() == 0)) { + action->SetError(800, "Internal error"); + } + + return res; +} + +/*---------------------------------------------------------------------- +| CMediaCrawler::OnBrowseRoot ++---------------------------------------------------------------------*/ +NPT_Result +CMediaCrawler::OnBrowseRoot(PLT_ActionReference& action) +{ + NPT_String browseFlagValue; + if (NPT_FAILED(action->GetArgumentValue("BrowseFlag", browseFlagValue))) { + NPT_LOG_WARNING("PLT_FileMediaServer::OnBrowse - invalid arguments."); + return NPT_FAILURE; + } + + /* extract browseFlag */ + BrowseFlags browseFlag; + if (NPT_FAILED(ParseBrowseFlag(browseFlagValue, browseFlag))) { + /* error */ + NPT_LOG_WARNING("PLT_FileMediaServer::OnBrowseRoot - BrowseFlag value not allowed."); + action->SetError(402,"Invalid BrowseFlag arg."); + return NPT_FAILURE; + } + + if (browseFlag == BROWSEMETADATA) { + PLT_MediaContainer item; + item.m_Title = "Root"; + item.m_ParentID = "-1"; + item.m_ObjectID = "0"; + item.m_ChildrenCount = GetMediaServers().GetItemCount(); + item.m_ObjectClass.type = "object.container"; + + /* convert item to didl */ + NPT_String filter; + action->GetArgumentValue("Filter", filter); + NPT_String tmp; + NPT_CHECK_SEVERE(PLT_Didl::ToDidl(item, filter, tmp)); + + /* add didl header and footer */ + NPT_String didl = didl_header + tmp + didl_footer; + + action->SetArgumentValue("Result", didl); + action->SetArgumentValue("NumberReturned", "1"); + action->SetArgumentValue("TotalMatches", "1"); + + // update ID may be wrong here, it should be the one of the container? + action->SetArgumentValue("UpdateId", "1"); + // TODO: We need to keep track of the overall updateID of the CDS + } else { + NPT_String startingInd; + NPT_String reqCount; + NPT_String filter; + + NPT_CHECK_SEVERE(action->GetArgumentValue("StartingIndex", startingInd)); + NPT_CHECK_SEVERE(action->GetArgumentValue("RequestedCount", reqCount)); + NPT_CHECK_SEVERE(action->GetArgumentValue("Filter", filter)); + + NPT_Int32 start_index, req_count; + if (NPT_FAILED(startingInd.ToInteger(start_index)) || + NPT_FAILED(reqCount.ToInteger(req_count))) { + return NPT_FAILURE; + } + + NPT_Int32 cur_index = 0; + NPT_Int32 num_returned = 0; + NPT_Int32 total_matches = 0; + //unsigned long update_id = 0; + PLT_MediaContainer item; + NPT_String item_didl; + NPT_String didl = didl_header; + + { + // populate a list of containers (one container per known servers) + const NPT_Lock<PLT_DeviceDataReferenceList>& devices = GetMediaServers(); + NPT_AutoLock lock((NPT_Mutex&)devices); + + NPT_Lock<PLT_DeviceDataReferenceList>::Iterator entry = devices.GetFirstItem(); + while (entry) { + PLT_DeviceDataReference device = (*entry); + item.m_Title = device->GetFriendlyName(); + item.m_ObjectID = FormatObjectId(device->GetUUID(), "0"); + item.m_ParentID = "0"; + item.m_ObjectClass.type = "object.container"; + + // reset tmp didl + item_didl = ""; + if ((cur_index >= start_index) && ((num_returned < req_count) || (req_count == 0))) { + NPT_CHECK_SEVERE(PLT_Didl::ToDidl(item, filter, item_didl)); + + didl += item_didl; + num_returned++; + } + cur_index++; + total_matches++; + + ++entry; + } + } + + didl += didl_footer; + + action->SetArgumentValue("Result", didl); + action->SetArgumentValue("NumberReturned", NPT_String::FromInteger(num_returned)); + action->SetArgumentValue("TotalMatches", NPT_String::FromInteger(total_matches)); + action->SetArgumentValue("UpdateId", "1"); + } + + return NPT_SUCCESS; +} + +/*---------------------------------------------------------------------- +| CMediaCrawler::OnBrowseDevice ++---------------------------------------------------------------------*/ +NPT_Result +CMediaCrawler::OnBrowseDevice(PLT_ActionReference& action, + const char* server_uuid, + const char* server_object_id, + const NPT_HttpRequestContext& context) +{ + NPT_Result res; + PLT_DeviceDataReference device; + + if (NPT_FAILED(FindServer(server_uuid, device))) { + /* error */ + NPT_LOG_WARNING("CMediaCrawler::OnBrowseDevice - device not found."); + action->SetError(701, "No Such Object."); + return NPT_FAILURE; + } + + // look for args and convert them + NPT_String browseFlagValue; + NPT_String startingInd; + NPT_String reqCount; + NPT_String filter; + NPT_String sort; + + NPT_CHECK_SEVERE(action->GetArgumentValue("BrowseFlag", browseFlagValue)); + NPT_CHECK_SEVERE(action->GetArgumentValue("StartingIndex", startingInd)); + NPT_CHECK_SEVERE(action->GetArgumentValue("RequestedCount", reqCount)); + NPT_CHECK_SEVERE(action->GetArgumentValue("Filter", filter)); + NPT_CHECK_SEVERE(action->GetArgumentValue("SortCriteria", sort)); + + NPT_Int32 start_index, req_count; + if (NPT_FAILED(startingInd.ToInteger(start_index)) || + NPT_FAILED(reqCount.ToInteger(req_count))) { + return NPT_FAILURE; + } + + // create a container for our result + // this will be filled in by OnBrowseResponse + CMediaCrawlerBrowseInfoReference browse_info(new CMediaCrawlerBrowseInfo()); + browse_info->shared_var.SetValue(0); + + // send off the browse packet. Note that this will + // not block. The shared variable is used to block + // until the response has been received. + res = Browse(device, + server_object_id, + start_index, + req_count, + (browseFlagValue == "BrowseMetadata")?1:0, + filter, + sort, + new CMediaCrawlerBrowseInfoReference(browse_info)); + NPT_CHECK_SEVERE(res); + + // wait 10 secs for response + res = browse_info->shared_var.WaitUntilEquals(1, 10000); + NPT_CHECK_SEVERE(res); + + // did the browse fail? + if (NPT_FAILED(browse_info->res)) { + action->SetError(browse_info->code, ""); + return NPT_FAILURE; + } + + action->SetArgumentValue("Result", UpdateDidl(server_uuid, browse_info->didl, &context.GetLocalAddress())); + action->SetArgumentValue("NumberReturned", browse_info->nr); + action->SetArgumentValue("TotalMatches", browse_info->tm); + action->SetArgumentValue("UpdateId", browse_info->uid); + action->SetArgumentValue("ObjectID", FormatObjectId(server_uuid, browse_info->object_id)); + + return NPT_SUCCESS; +} + +/*---------------------------------------------------------------------- +| CMediaCrawler::OnBrowseResponse ++---------------------------------------------------------------------*/ +NPT_Result +CMediaCrawler::OnBrowseResponse(NPT_Result res, + PLT_DeviceDataReference& device, + PLT_ActionReference& action, + void* userdata) +{ + NPT_COMPILER_UNUSED(device); + + if (!userdata) return NPT_FAILURE; + + CMediaCrawlerBrowseInfoReference* info = (CMediaCrawlerBrowseInfoReference*) userdata; + (*info)->res = res; + (*info)->code = action->GetErrorCode(); + + if (NPT_FAILED(action->GetArgumentValue("ObjectID", (*info)->object_id))) { + goto bad_action; + } + if (NPT_FAILED(action->GetArgumentValue("UpdateID", (*info)->uid))) { + goto bad_action; + } + if (NPT_FAILED(action->GetArgumentValue("NumberReturned", (*info)->nr))) { + goto bad_action; + } + if (NPT_FAILED(action->GetArgumentValue("TotalMatches", (*info)->tm))) { + goto bad_action; + } + if (NPT_FAILED(action->GetArgumentValue("Result", (*info)->didl))) { + goto bad_action; + } + + if (NPT_FAILED((*info)->res) || action->GetErrorCode() != 0) { + goto bad_action; + } + + goto done; + +bad_action: + if (NPT_SUCCEEDED((*info)->res)) (*info)->res = NPT_FAILURE; + +done: + (*info)->shared_var.SetValue(1); + delete info; + + return NPT_SUCCESS; +} + +/*---------------------------------------------------------------------- +| CMediaCrawler::UpdateDidl ++---------------------------------------------------------------------*/ +NPT_String +CMediaCrawler::UpdateDidl(const char* server_uuid, + const NPT_String& didl, + const NPT_SocketAddress* req_local_address /* = NULL */) +{ + NPT_String new_didl; + NPT_String str; + NPT_XmlNode* node = NULL; + NPT_XmlWriter writer; + NPT_OutputStreamReference stream(new NPT_StringOutputStream(&new_didl)); + + NPT_LOG_FINE("Parsing Didl..."); + + NPT_XmlElementNode* tree = NULL; + NPT_XmlParser parser; + if (NPT_FAILED(parser.Parse(didl, node)) || !node || !node->AsElementNode()) { + goto cleanup; + } + + tree = node->AsElementNode(); + + NPT_LOG_FINE("Processing Didl xml..."); + if (tree->GetTag().Compare("DIDL-Lite", true)) { + goto cleanup; + } + + // iterate through children + NPT_Result res; + for (NPT_List<NPT_XmlNode*>::Iterator children = tree->GetChildren().GetFirstItem(); children; children++) { + NPT_XmlElementNode* child = (*children)->AsElementNode(); + if (!child) continue; + + // object id remapping + NPT_XmlAttribute* attribute_id; + res = PLT_XmlHelper::GetAttribute(child, "id", attribute_id); + if (NPT_SUCCEEDED(res) && attribute_id) { + attribute_id->SetValue(FormatObjectId(server_uuid, attribute_id->GetValue())); + } + + // parent ID remapping + NPT_XmlAttribute* attribute_parent_id; + res = PLT_XmlHelper::GetAttribute(child, "parentID", attribute_parent_id); + if (NPT_SUCCEEDED(res)) { + attribute_parent_id->SetValue(attribute_parent_id->GetValue().Compare("-1")?FormatObjectId(server_uuid, attribute_parent_id->GetValue()):"0"); + } + + // resources remapping + NPT_Array<NPT_XmlElementNode*> resources; + PLT_XmlHelper::GetChildren(child, resources, "res"); + if (resources.GetItemCount() > 0) { + for (unsigned int i=0; i<resources.GetItemCount(); i++) { + NPT_XmlElementNode* resource = resources[i]; + NPT_XmlAttribute* attribute_prot; + const NPT_String* url; + if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(resource, "protocolInfo", attribute_prot)) && (url = resource->GetText())) { + // special case for Windows Media Connect + // When a browse is done on the same machine, WMC uses localhost + // instead of the IP for all resources urls which means we cannot advertise that + // since it would be useless for a remote device + // so we try to replace it with the right IP address by looking at which interface we received the + // initial browse request on to make sure the remote device will be able to access the modified resource + // urls (in case the local PC has more than 1 NICs) + + // replace the url + NPT_List<NPT_XmlNode*>& nodes = resource->GetChildren(); + NPT_HttpUrl http_url(NPT_Uri::PercentDecode(*url)); + if ((http_url.GetHost() == "localhost" || http_url.GetHost() == "127.0.0.1") && req_local_address) { + if (req_local_address->GetIpAddress().AsLong()) { + http_url.SetHost(req_local_address->GetIpAddress().ToString()); + + // replace text + nodes.Apply(NPT_ObjectDeleter<NPT_XmlNode>()); + nodes.Clear(); + resource->AddText(http_url.ToString()); + url = resource->GetText(); + } + } + + CStreamHandler* handler = NULL; + res = NPT_ContainerFind(m_StreamHandlers, CStreamHandlerFinder(attribute_prot->GetValue(), *url), handler); + if (NPT_SUCCEEDED(res)) { + handler->ModifyResource(resource); + } + } + } + } + } + + // serialize modified node into new didl + writer.Serialize(*node, *stream); + delete node; + return new_didl; + +cleanup: + delete node; + return didl; +} + +/*---------------------------------------------------------------------- +| CMediaCrawler::ProcessFileRequest ++---------------------------------------------------------------------*/ +NPT_Result +CMediaCrawler::ProcessFileRequest(NPT_HttpRequest& request, + const NPT_HttpRequestContext& context, + NPT_HttpResponse& response) +{ + NPT_COMPILER_UNUSED(context); + + PLT_LOG_HTTP_REQUEST(NPT_LOG_LEVEL_FINE, "CMediaCrawler::ProcessFileRequest:", &request); + + if (request.GetMethod().Compare("GET", true) && request.GetMethod().Compare("HEAD", true)) { + response.SetStatus(500, "Internal Server Error"); + return NPT_SUCCESS; + } + + // add the user agent header, some stupid media servers like YME needs it + if (!request.GetHeaders().GetHeader(NPT_HTTP_HEADER_USER_AGENT)) { + request.GetHeaders().SetHeader(NPT_HTTP_HEADER_USER_AGENT, + "Platinum/" PLT_PLATINUM_SDK_VERSION_STRING); + } + + // File requested + NPT_HttpResponse* out_response = NULL; + NPT_HttpUrlQuery query(request.GetUrl().GetQuery()); + const char* url = query.GetField("url"); + if (url) { + // look for handler + CStreamHandler* handler = NULL; + NPT_ContainerFind(m_StreamHandlers, CStreamHandlerFinder(NULL, url), handler); + if (handler && NPT_SUCCEEDED(handler->ProcessFileRequest(request, out_response)) && out_response) { + // copy response code and reason + response.SetStatus(out_response->GetStatusCode(), out_response->GetReasonPhrase()); + + // copy headers + NPT_List<NPT_HttpHeader*>::Iterator headers = out_response->GetHeaders().GetHeaders().GetFirstItem(); + while (headers) { + response.GetHeaders().SetHeader((*headers)->GetName(), (*headers)->GetValue()); + ++headers; + } + response.SetEntity(new NPT_HttpEntity(response.GetHeaders())); + + // update inputstream + NPT_HttpEntity* out_entity; + if ((out_entity = out_response->GetEntity()) != NULL) { + NPT_InputStreamReference inputstream; + out_entity->GetInputStream(inputstream); + if (!inputstream.IsNull()) { + // set the stream but do not update the content length + response.GetEntity()->SetInputStream(inputstream, false); + } + } + + delete out_response; + return NPT_SUCCESS; + } + } + + response.SetStatus(404, "File Not Found"); + return NPT_SUCCESS; +} + +/*---------------------------------------------------------------------- +| CMediaCrawler::AddStreamHandler ++---------------------------------------------------------------------*/ +NPT_Result +CMediaCrawler::AddStreamHandler(CStreamHandler* handler) +{ + // make sure we don't have a metadatahandler registered for the same extension + // PLT_StreamHandler* prev_handler; + // NPT_Result ret = NPT_ContainerFind(m_StreamHandlers, PLT_StreamHandlerFinder(handler->GetExtension()), prev_handler); + // if (NPT_SUCCEEDED(ret)) { + // return NPT_ERROR_INVALID_PARAMETERS; + // } + + m_StreamHandlers.Add(handler); + return NPT_SUCCESS; +} |