Merge pull request #25959 from neo1973/TagLib_deprecation_warnings
[xbmc.git] / lib / libUPnP / Platinum / Source / Devices / MediaServer / PltFileMediaServer.cpp
blob421d0c204271eb2e9e8bd7182f4ec9c0010ea860
1 /*****************************************************************
3 | Platinum - File Media Server Delegate
5 | Copyright (c) 2004-2010, Plutinosoft, LLC.
6 | All rights reserved.
7 | http://www.plutinosoft.com
9 | This program is free software; you can redistribute it and/or
10 | modify it under the terms of the GNU General Public License
11 | as published by the Free Software Foundation; either version 2
12 | of the License, or (at your option) any later version.
14 | OEMs, ISVs, VARs and other distributors that combine and
15 | distribute commercially licensed software with Platinum software
16 | and do not wish to distribute the source code for the commercially
17 | licensed software under version 2, or (at your option) any later
18 | version, of the GNU General Public License (the "GPL") must enter
19 | into a commercial license agreement with Plutinosoft, LLC.
20 | licensing@plutinosoft.com
22 | This program is distributed in the hope that it will be useful,
23 | but WITHOUT ANY WARRANTY; without even the implied warranty of
24 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 | GNU General Public License for more details.
27 | You should have received a copy of the GNU General Public License
28 | along with this program; see the file LICENSE.txt. If not, write to
29 | the Free Software Foundation, Inc.,
30 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
31 | http://www.gnu.org/licenses/gpl-2.0.html
33 ****************************************************************/
35 /*----------------------------------------------------------------------
36 | includes
37 +---------------------------------------------------------------------*/
38 #include "PltUPnP.h"
39 #include "PltFileMediaServer.h"
40 #include "PltMediaItem.h"
41 #include "PltService.h"
42 #include "PltTaskManager.h"
43 #include "PltHttpServer.h"
44 #include "PltDidl.h"
45 #include "PltVersion.h"
46 #include "PltMimeType.h"
48 NPT_SET_LOCAL_LOGGER("platinum.media.server.file.delegate")
50 /*----------------------------------------------------------------------
51 | PLT_FileMediaServerDelegate::PLT_FileMediaServerDelegate
52 +---------------------------------------------------------------------*/
53 PLT_FileMediaServerDelegate::PLT_FileMediaServerDelegate(const char* url_root,
54 const char* file_root,
55 bool use_cache) :
56 m_UrlRoot(url_root),
57 m_FileRoot(file_root),
58 m_FilterUnknownOut(false),
59 m_UseCache(use_cache)
61 /* Trim excess separators */
62 m_FileRoot.TrimRight("/\\");
65 /*----------------------------------------------------------------------
66 | PLT_FileMediaServerDelegate::~PLT_FileMediaServerDelegate
67 +---------------------------------------------------------------------*/
68 PLT_FileMediaServerDelegate::~PLT_FileMediaServerDelegate()
72 /*----------------------------------------------------------------------
73 | PLT_FileMediaServerDelegate::ProcessFileRequest
74 +---------------------------------------------------------------------*/
75 NPT_Result
76 PLT_FileMediaServerDelegate::ProcessFileRequest(NPT_HttpRequest& request,
77 const NPT_HttpRequestContext& context,
78 NPT_HttpResponse& response)
80 NPT_HttpUrlQuery query(request.GetUrl().GetQuery());
82 PLT_LOG_HTTP_REQUEST(NPT_LOG_LEVEL_FINE, "PLT_FileMediaServerDelegate::ProcessFileRequest:", &request);
84 if (request.GetMethod().Compare("GET") && request.GetMethod().Compare("HEAD")) {
85 response.SetStatus(500, "Internal Server Error");
86 return NPT_SUCCESS;
89 /* Extract file path from url */
90 NPT_String file_path;
91 NPT_CHECK_LABEL_WARNING(ExtractResourcePath(request.GetUrl(), file_path), failure);
93 /* Serve file */
94 NPT_CHECK_WARNING(ServeFile(request, context, response, NPT_FilePath::Create(m_FileRoot, file_path)));
95 return NPT_SUCCESS;
97 failure:
98 response.SetStatus(404, "File Not Found");
99 return NPT_SUCCESS;
102 /*----------------------------------------------------------------------
103 | PLT_FileMediaServerDelegate::ServeFile
104 +---------------------------------------------------------------------*/
105 NPT_Result
106 PLT_FileMediaServerDelegate::ServeFile(const NPT_HttpRequest& request,
107 const NPT_HttpRequestContext& context,
108 NPT_HttpResponse& response,
109 const NPT_String& file_path)
111 NPT_CHECK_WARNING(PLT_HttpServer::ServeFile(request, context, response, file_path));
112 return NPT_SUCCESS;
115 /*----------------------------------------------------------------------
116 | PLT_FileMediaServerDelegate::OnBrowseMetadata
117 +---------------------------------------------------------------------*/
118 NPT_Result
119 PLT_FileMediaServerDelegate::OnBrowseMetadata(PLT_ActionReference& action,
120 const char* object_id,
121 const char* filter,
122 NPT_UInt32 starting_index,
123 NPT_UInt32 requested_count,
124 const char* sort_criteria,
125 const PLT_HttpRequestContext& context)
127 NPT_COMPILER_UNUSED(sort_criteria);
128 NPT_COMPILER_UNUSED(requested_count);
129 NPT_COMPILER_UNUSED(starting_index);
131 NPT_String didl;
132 PLT_MediaObjectReference item;
134 /* locate the file from the object ID */
135 NPT_String filepath;
136 if (NPT_FAILED(GetFilePath(object_id, filepath))) {
137 /* error */
138 NPT_LOG_WARNING("PLT_FileMediaServerDelegate::OnBrowse - ObjectID not found.");
139 action->SetError(701, "No Such Object.");
140 return NPT_FAILURE;
143 /* build the object didl */
144 item = BuildFromFilePath(filepath, context, true, false, (NPT_String(filter).Find("ALLIP")!=-1));
145 if (item.IsNull()) return NPT_FAILURE;
146 NPT_String tmp;
147 NPT_CHECK_SEVERE(PLT_Didl::ToDidl(*item.AsPointer(), filter, tmp));
149 /* add didl header and footer */
150 didl = didl_header + tmp + didl_footer;
152 NPT_CHECK_SEVERE(action->SetArgumentValue("Result", didl));
153 NPT_CHECK_SEVERE(action->SetArgumentValue("NumberReturned", "1"));
154 NPT_CHECK_SEVERE(action->SetArgumentValue("TotalMatches", "1"));
156 /* update ID may be wrong here, it should be the one of the container?
157 TODO: We need to keep track of the overall updateID of the CDS */
158 NPT_CHECK_SEVERE(action->SetArgumentValue("UpdateId", "1"));
160 return NPT_SUCCESS;
163 /*----------------------------------------------------------------------
164 | PLT_FileMediaServerDelegate::OnBrowseDirectChildren
165 +---------------------------------------------------------------------*/
166 NPT_Result
167 PLT_FileMediaServerDelegate::OnBrowseDirectChildren(PLT_ActionReference& action,
168 const char* object_id,
169 const char* filter,
170 NPT_UInt32 starting_index,
171 NPT_UInt32 requested_count,
172 const char* sort_criteria,
173 const PLT_HttpRequestContext& context)
175 NPT_COMPILER_UNUSED(sort_criteria);
177 /* locate the file from the object ID */
178 NPT_String dir;
179 NPT_FileInfo info;
180 if (NPT_FAILED(GetFilePath(object_id, dir)) || NPT_FAILED(NPT_File::GetInfo(dir, &info)) || info.m_Type != NPT_FileInfo::FILE_TYPE_DIRECTORY) {
181 /* error */
182 NPT_LOG_WARNING_1("ObjectID \'%s\' not found or not allowed", object_id);
183 action->SetError(701, "No such Object");
184 NPT_CHECK_WARNING(NPT_FAILURE);
187 /* get uuid from device via action reference */
188 NPT_String uuid = action->GetActionDesc().GetService()->GetDevice()->GetUUID();
190 /* Try to get list from cache if allowed */
191 NPT_Result res;
192 NPT_Reference<NPT_List<NPT_String> > entries;
193 NPT_TimeStamp cached_entries_time;
194 if (!m_UseCache || NPT_FAILED(m_DirCache.Get(uuid, dir, entries, &cached_entries_time)) || cached_entries_time < info.m_ModificationTime) {
195 /* if not found in cache or if current dir has newer modified time fetch fresh new list from source */
196 entries = new NPT_List<NPT_String>();
197 res = NPT_File::ListDir(dir, *entries);
198 if (NPT_FAILED(res)) {
199 NPT_LOG_WARNING_1("PLT_FileMediaServerDelegate::OnBrowseDirectChildren - failed to open dir %s", (const char*) dir);
200 NPT_CHECK_WARNING(res);
203 /* sort results according to modification date */
204 res = entries->Sort(NPT_FileDateComparator(dir));
205 if (NPT_FAILED(res)) {
206 NPT_LOG_WARNING_1("PLT_FileMediaServerDelegate::OnBrowseDirectChildren - failed to open sort dir %s", (const char*) dir);
207 NPT_CHECK_WARNING(res);
210 /* add new list to cache */
211 if (m_UseCache) {
212 m_DirCache.Put(uuid, dir, entries, &info.m_ModificationTime);
216 unsigned long cur_index = 0;
217 unsigned long num_returned = 0;
218 unsigned long total_matches = 0;
219 NPT_String didl = didl_header;
220 bool allip = (NPT_String(filter).Find("ALLIP") != -1);
222 PLT_MediaObjectReference item;
223 for (NPT_List<NPT_String>::Iterator it = entries->GetFirstItem();
225 ++it) {
226 NPT_String filepath = NPT_FilePath::Create(dir, *it);
228 /* verify we want to process this file first */
229 if (!ProcessFile(filepath, filter)) continue;
231 /* build item object from file path */
232 item = BuildFromFilePath(filepath,
233 context,
234 true,
235 false,
236 allip);
238 /* generate didl if within range requested */
239 if (!item.IsNull()) {
240 if ((cur_index >= starting_index) &&
241 ((num_returned < requested_count) || (requested_count == 0))) {
242 NPT_String tmp;
243 NPT_CHECK_SEVERE(PLT_Didl::ToDidl(*item.AsPointer(), filter, tmp));
245 didl += tmp;
246 ++num_returned;
248 ++cur_index;
249 ++total_matches;
253 didl += didl_footer;
255 NPT_LOG_FINE_6("BrowseDirectChildren from %s returning %d-%lu/%lu objects (%lu out of %d requested)",
256 (const char*)context.GetLocalAddress().GetIpAddress().ToString(),
257 starting_index, starting_index+num_returned, total_matches, num_returned, requested_count);
259 NPT_CHECK_SEVERE(action->SetArgumentValue("Result", didl));
260 NPT_CHECK_SEVERE(action->SetArgumentValue("NumberReturned", NPT_String::FromInteger(num_returned)));
261 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!!
262 NPT_CHECK_SEVERE(action->SetArgumentValue("UpdateId", "1"));
264 return NPT_SUCCESS;
267 /*----------------------------------------------------------------------
268 | PLT_FileMediaServerDelegate::OnSearchContainer
269 +---------------------------------------------------------------------*/
270 NPT_Result
271 PLT_FileMediaServerDelegate::OnSearchContainer(PLT_ActionReference& action,
272 const char* object_id,
273 const char* search_criteria,
274 const char* /* filter */,
275 NPT_UInt32 /* starting_index */,
276 NPT_UInt32 /* requested_count */,
277 const char* /* sort_criteria */,
278 const PLT_HttpRequestContext& /* context */)
280 /* parse search criteria */
282 /* TODO: HACK TO PASS DLNA */
283 if (search_criteria && NPT_StringsEqual(search_criteria, "Unknownfieldname")) {
284 /* error */
285 NPT_LOG_WARNING_1("Unsupported or invalid search criteria %s", search_criteria);
286 action->SetError(708, "Unsupported or invalid search criteria");
287 return NPT_FAILURE;
290 /* locate the file from the object ID */
291 NPT_String dir;
292 if (NPT_FAILED(GetFilePath(object_id, dir))) {
293 /* error */
294 NPT_LOG_WARNING("ObjectID not found.");
295 action->SetError(710, "No Such Container.");
296 return NPT_FAILURE;
299 /* retrieve the item type */
300 NPT_FileInfo info;
301 NPT_Result res = NPT_File::GetInfo(dir, &info);
302 if (NPT_FAILED(res) || (info.m_Type != NPT_FileInfo::FILE_TYPE_DIRECTORY)) {
303 /* error */
304 NPT_LOG_WARNING("No such container");
305 action->SetError(710, "No such container");
306 return NPT_FAILURE;
309 return NPT_ERROR_NOT_IMPLEMENTED;
312 /*----------------------------------------------------------------------
313 | PLT_FileMediaServerDelegate::GetFilePath
314 +---------------------------------------------------------------------*/
315 NPT_Result
316 PLT_FileMediaServerDelegate::GetFilePath(const char* object_id,
317 NPT_String& filepath)
319 if (!object_id) return NPT_ERROR_INVALID_PARAMETERS;
321 filepath = m_FileRoot;
323 /* object id is formatted as 0/<filepath> */
324 if (NPT_StringLength(object_id) >= 1) {
325 filepath += (object_id + (object_id[0]=='0'?1:0));
328 return NPT_SUCCESS;
331 /*----------------------------------------------------------------------
332 | PLT_FileMediaServerDelegate::BuildSafeResourceUri
333 +---------------------------------------------------------------------*/
334 NPT_String
335 PLT_FileMediaServerDelegate::BuildSafeResourceUri(const NPT_HttpUrl& base_uri,
336 const char* host,
337 const char* file_path)
339 NPT_String result;
340 NPT_HttpUrl uri = base_uri;
342 if (host) uri.SetHost(host);
344 NPT_String uri_path = uri.GetPath();
345 if (!uri_path.EndsWith("/")) uri_path += "/";
347 /* some controllers (like WMP) will call us with an already urldecoded version.
348 We're intentionally prepending a known urlencoded string
349 to detect it when we receive the request urlencoded or already decoded to avoid double decoding*/
350 uri_path += "%/";
351 uri_path += file_path;
353 /* set path */
354 uri.SetPath(uri_path);
356 /* 360 hack: force inclusion of port in case it's 80 */
357 return uri.ToStringWithDefaultPort(0);
360 /*----------------------------------------------------------------------
361 | PLT_FileMediaServerDelegate::ExtractResourcePath
362 +---------------------------------------------------------------------*/
363 NPT_Result
364 PLT_FileMediaServerDelegate::ExtractResourcePath(const NPT_HttpUrl& url,
365 NPT_String& file_path)
367 /* Extract non decoded path, we need to autodetect urlencoding */
368 NPT_String uri_path = url.GetPath();
369 NPT_String url_root_encode = NPT_Uri::PercentEncode(m_UrlRoot, NPT_Uri::PathCharsToEncode);
371 NPT_Ordinal skip = 0;
372 if (uri_path.StartsWith(m_UrlRoot)) {
373 skip = m_UrlRoot.GetLength();
374 } else if (uri_path.StartsWith(url_root_encode)) {
375 skip = url_root_encode.GetLength();
376 } else {
377 return NPT_FAILURE;
380 /* account for extra slash */
381 skip += ((m_UrlRoot=="/")?0:1);
382 file_path = uri_path.SubString(skip);
384 /* detect if client such as WMP sent a non urlencoded url */
385 if (file_path.StartsWith("%/")) {
386 NPT_LOG_FINE("Received a urldecoded version of our url!");
387 file_path.Erase(0, 2);
388 } else {
389 /* remove our prepended string we used to detect urldecoded version */
390 if (file_path.StartsWith("%25/")) file_path.Erase(0, 4);
392 /* ok to urldecode */
393 file_path = NPT_Uri::PercentDecode(file_path);
396 return NPT_SUCCESS;
399 /*----------------------------------------------------------------------
400 | PLT_FileMediaServerDelegate::BuildResourceUri
401 +---------------------------------------------------------------------*/
402 NPT_String
403 PLT_FileMediaServerDelegate::BuildResourceUri(const NPT_HttpUrl& base_uri,
404 const char* host,
405 const char* file_path)
407 return BuildSafeResourceUri(base_uri, host, file_path);
410 /*----------------------------------------------------------------------
411 | PLT_FileMediaServerDelegate::BuildFromFilePath
412 +---------------------------------------------------------------------*/
413 PLT_MediaObject*
414 PLT_FileMediaServerDelegate::BuildFromFilePath(const NPT_String& filepath,
415 const PLT_HttpRequestContext& context,
416 bool with_count /* = true */,
417 bool keep_extension_in_title /* = false */,
418 bool allip /* = false */)
420 NPT_String root = m_FileRoot;
421 PLT_MediaItemResource resource;
422 PLT_MediaObject* object = NULL;
424 NPT_LOG_FINEST_1("Building didl for file '%s'", (const char*)filepath);
426 /* retrieve the entry type (directory or file) */
427 NPT_FileInfo info;
428 NPT_CHECK_LABEL_FATAL(NPT_File::GetInfo(filepath, &info), failure);
430 if (info.m_Type == NPT_FileInfo::FILE_TYPE_REGULAR) {
431 object = new PLT_MediaItem();
433 /* Set the title using the filename for now */
434 object->m_Title = NPT_FilePath::BaseName(filepath, keep_extension_in_title);
435 if (object->m_Title.GetLength() == 0) goto failure;
437 /* make sure we return something with a valid mimetype */
438 if (m_FilterUnknownOut &&
439 NPT_StringsEqual(PLT_MimeType::GetMimeType(filepath, &context),
440 "application/octet-stream")) {
441 goto failure;
444 /* Set the protocol Info from the extension */
445 resource.m_ProtocolInfo = PLT_ProtocolInfo::GetProtocolInfo(filepath, true, &context);
446 if (!resource.m_ProtocolInfo.IsValid()) goto failure;
448 /* Set the resource file size */
449 resource.m_Size = info.m_Size;
451 /* format the resource URI */
452 NPT_String url = filepath.SubString(root.GetLength()+1);
454 // get list of ip addresses
455 NPT_List<NPT_IpAddress> ips;
456 NPT_CHECK_LABEL_SEVERE(PLT_UPnPMessageHelper::GetIPAddresses(ips), failure);
458 /* if we're passed an interface where we received the request from
459 move the ip to the top so that it is used for the first resource */
460 if (context.GetLocalAddress().GetIpAddress().ToString() != "0.0.0.0") {
461 ips.Remove(context.GetLocalAddress().GetIpAddress());
462 ips.Insert(ips.GetFirstItem(), context.GetLocalAddress().GetIpAddress());
463 } else if (!allip) {
464 NPT_LOG_WARNING("Couldn't determine local interface IP so we might return an unreachable IP");
466 object->m_ObjectClass.type = PLT_MediaItem::GetUPnPClass(filepath, &context);
468 /* add as many resources as we have interfaces s*/
469 NPT_HttpUrl base_uri("127.0.0.1", context.GetLocalAddress().GetPort(), NPT_HttpUrl::PercentEncode(m_UrlRoot, NPT_Uri::PathCharsToEncode));
470 NPT_List<NPT_IpAddress>::Iterator ip = ips.GetFirstItem();
471 while (ip) {
472 resource.m_Uri = BuildResourceUri(base_uri, ip->ToString(), url);
473 object->m_Resources.Add(resource);
474 ++ip;
476 /* if we only want the one resource reachable by client */
477 if (!allip) break;
479 } else {
480 object = new PLT_MediaContainer;
482 /* Assign a title for this container */
483 if (filepath.Compare(root, true) == 0) {
484 object->m_Title = "Root";
485 } else {
486 object->m_Title = NPT_FilePath::BaseName(filepath, keep_extension_in_title);
487 if (object->m_Title.GetLength() == 0) goto failure;
490 /* Get the number of children for this container */
491 NPT_LargeSize count = 0;
492 if (with_count && NPT_SUCCEEDED(NPT_File::GetSize(filepath, count))) {
493 ((PLT_MediaContainer*)object)->m_ChildrenCount = (NPT_Int32)count;
496 object->m_ObjectClass.type = "object.container.storageFolder";
499 /* is it the root? */
500 if (filepath.Compare(root, true) == 0) {
501 object->m_ParentID = "-1";
502 object->m_ObjectID = "0";
503 } else {
504 NPT_String directory = NPT_FilePath::DirName(filepath);
505 /* is the parent path the root? */
506 if (directory.GetLength() == root.GetLength()) {
507 object->m_ParentID = "0";
508 } else {
509 object->m_ParentID = "0" + filepath.SubString(root.GetLength(), directory.GetLength() - root.GetLength());
511 object->m_ObjectID = "0" + filepath.SubString(root.GetLength());
514 return object;
516 failure:
517 delete object;
518 return NULL;