summaryrefslogtreecommitdiffstats
path: root/lib/libUPnP/Platinum/Source/Apps/MediaCrawler/MediaCrawler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libUPnP/Platinum/Source/Apps/MediaCrawler/MediaCrawler.cpp')
-rw-r--r--lib/libUPnP/Platinum/Source/Apps/MediaCrawler/MediaCrawler.cpp566
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;
+}