1 /*****************************************************************
3 | Platinum - File Media Server Delegate
5 | Copyright (c) 2004-2010, Plutinosoft, LLC.
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 /*----------------------------------------------------------------------
37 +---------------------------------------------------------------------*/
39 #include "PltFileMediaServer.h"
40 #include "PltMediaItem.h"
41 #include "PltService.h"
42 #include "PltTaskManager.h"
43 #include "PltHttpServer.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
,
57 m_FileRoot(file_root
),
58 m_FilterUnknownOut(false),
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 +---------------------------------------------------------------------*/
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");
89 /* Extract file path from url */
91 NPT_CHECK_LABEL_WARNING(ExtractResourcePath(request
.GetUrl(), file_path
), failure
);
94 NPT_CHECK_WARNING(ServeFile(request
, context
, response
, NPT_FilePath::Create(m_FileRoot
, file_path
)));
98 response
.SetStatus(404, "File Not Found");
102 /*----------------------------------------------------------------------
103 | PLT_FileMediaServerDelegate::ServeFile
104 +---------------------------------------------------------------------*/
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
));
115 /*----------------------------------------------------------------------
116 | PLT_FileMediaServerDelegate::OnBrowseMetadata
117 +---------------------------------------------------------------------*/
119 PLT_FileMediaServerDelegate::OnBrowseMetadata(PLT_ActionReference
& action
,
120 const char* object_id
,
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
);
132 PLT_MediaObjectReference item
;
134 /* locate the file from the object ID */
136 if (NPT_FAILED(GetFilePath(object_id
, filepath
))) {
138 NPT_LOG_WARNING("PLT_FileMediaServerDelegate::OnBrowse - ObjectID not found.");
139 action
->SetError(701, "No Such Object.");
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
;
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"));
163 /*----------------------------------------------------------------------
164 | PLT_FileMediaServerDelegate::OnBrowseDirectChildren
165 +---------------------------------------------------------------------*/
167 PLT_FileMediaServerDelegate::OnBrowseDirectChildren(PLT_ActionReference
& action
,
168 const char* object_id
,
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 */
180 if (NPT_FAILED(GetFilePath(object_id
, dir
)) || NPT_FAILED(NPT_File::GetInfo(dir
, &info
)) || info
.m_Type
!= NPT_FileInfo::FILE_TYPE_DIRECTORY
) {
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 */
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 */
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();
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
,
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))) {
243 NPT_CHECK_SEVERE(PLT_Didl::ToDidl(*item
.AsPointer(), filter
, tmp
));
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"));
267 /*----------------------------------------------------------------------
268 | PLT_FileMediaServerDelegate::OnSearchContainer
269 +---------------------------------------------------------------------*/
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")) {
285 NPT_LOG_WARNING_1("Unsupported or invalid search criteria %s", search_criteria
);
286 action
->SetError(708, "Unsupported or invalid search criteria");
290 /* locate the file from the object ID */
292 if (NPT_FAILED(GetFilePath(object_id
, dir
))) {
294 NPT_LOG_WARNING("ObjectID not found.");
295 action
->SetError(710, "No Such Container.");
299 /* retrieve the item type */
301 NPT_Result res
= NPT_File::GetInfo(dir
, &info
);
302 if (NPT_FAILED(res
) || (info
.m_Type
!= NPT_FileInfo::FILE_TYPE_DIRECTORY
)) {
304 NPT_LOG_WARNING("No such container");
305 action
->SetError(710, "No such container");
309 return NPT_ERROR_NOT_IMPLEMENTED
;
312 /*----------------------------------------------------------------------
313 | PLT_FileMediaServerDelegate::GetFilePath
314 +---------------------------------------------------------------------*/
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));
331 /*----------------------------------------------------------------------
332 | PLT_FileMediaServerDelegate::BuildSafeResourceUri
333 +---------------------------------------------------------------------*/
335 PLT_FileMediaServerDelegate::BuildSafeResourceUri(const NPT_HttpUrl
& base_uri
,
337 const char* file_path
)
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*/
351 uri_path
+= file_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 +---------------------------------------------------------------------*/
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();
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);
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
);
399 /*----------------------------------------------------------------------
400 | PLT_FileMediaServerDelegate::BuildResourceUri
401 +---------------------------------------------------------------------*/
403 PLT_FileMediaServerDelegate::BuildResourceUri(const NPT_HttpUrl
& base_uri
,
405 const char* file_path
)
407 return BuildSafeResourceUri(base_uri
, host
, file_path
);
410 /*----------------------------------------------------------------------
411 | PLT_FileMediaServerDelegate::BuildFromFilePath
412 +---------------------------------------------------------------------*/
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) */
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")) {
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());
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();
472 resource
.m_Uri
= BuildResourceUri(base_uri
, ip
->ToString(), url
);
473 object
->m_Resources
.Add(resource
);
476 /* if we only want the one resource reachable by client */
480 object
= new PLT_MediaContainer
;
482 /* Assign a title for this container */
483 if (filepath
.Compare(root
, true) == 0) {
484 object
->m_Title
= "Root";
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";
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";
509 object
->m_ParentID
= "0" + filepath
.SubString(root
.GetLength(), directory
.GetLength() - root
.GetLength());
511 object
->m_ObjectID
= "0" + filepath
.SubString(root
.GetLength());