| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2009-2012 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / Platinum UPnP module | |
| * | |
| * GPAC is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC 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 Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| * | |
| * ---------------------------------------------------------------------------------- | |
| * PLATINUM IS LICENSED UNDER GPL or commercial agreement - cf platinum license | |
| * ---------------------------------------------------------------------------------- | |
| * | |
| */ | |
| #include "PltUPnP.h" | |
| #include "GPACFileMediaServer.h" | |
| #include "PltMediaItem.h" | |
| #include "PltService.h" | |
| #include "PltTaskManager.h" | |
| #include "PltHttpServer.h" | |
| #include "PltDidl.h" | |
| #include "PltMetadataHandler.h" | |
| #include "PltVersion.h" | |
| #include <gpac/network.h> | |
| NPT_SET_LOCAL_LOGGER("gpac.media.server.file") | |
| GPAC_FileMediaServer::GPAC_FileMediaServer(const char* friendly_name, | |
| bool show_ip, | |
| const char* uuid, | |
| NPT_UInt16 port) : PLT_FileMediaServer("", friendly_name, show_ip, uuid, port) | |
| { | |
| } | |
| void GPAC_FileMediaServer::AddSharedDirectory(const char *path, const char *alias, Bool is_hidden) | |
| { | |
| u8 buf[10]; | |
| if (!alias) { | |
| sprintf((char*)buf, "%08X", gf_crc_32((char*) path, (u32) strlen(path))); | |
| alias = (const char *)buf; | |
| } | |
| m_Directories.Add(GPAC_MediaDirectory(alias, path, is_hidden)); | |
| } | |
| NPT_Result | |
| GPAC_FileMediaServer::OnBrowseDirectChildren(PLT_ActionReference& action, | |
| const char* object_id, | |
| const char* filter, | |
| NPT_UInt32 start_index, | |
| NPT_UInt32 req_count, | |
| const char* sort_criteria, | |
| const PLT_HttpRequestContext& context) | |
| { | |
| /*not the root of our server*/ | |
| if (strcmp(object_id, "/") && strcmp(object_id, "\\") && strcmp(object_id, "0")) { | |
| return PLT_MediaServer::OnBrowseDirectChildren(action, object_id, filter, start_index, req_count, sort_criteria, context); | |
| } | |
| unsigned long cur_index = 0; | |
| unsigned long num_returned = 0; | |
| unsigned long total_matches = 0; | |
| NPT_String didl = didl_header; | |
| PLT_MediaObjectReference item; | |
| for (u32 i=0; i<m_Directories.GetItemCount(); i++) { | |
| GPAC_MediaDirectory *alias; | |
| m_Directories.Get(i, alias); | |
| if (alias->m_Hide) continue; | |
| item = BuildFromFilePath(alias->m_Path, context, true, false); | |
| if (!item.IsNull()) { | |
| if ((cur_index >= start_index) && ((num_returned < req_count) || (req_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_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; | |
| } | |
| NPT_Result | |
| GPAC_FileMediaServer::ServeFile(const NPT_HttpRequest& request, | |
| const NPT_HttpRequestContext& context, | |
| NPT_HttpResponse& response, | |
| const NPT_String& _file_path) | |
| { | |
| NPT_COMPILER_UNUSED(context); | |
| NPT_String file_path = _file_path; | |
| NPT_String uri_path = NPT_Uri::PercentDecode(request.GetUrl().GetPath()); | |
| NPT_String query = request.GetUrl().GetQuery(); | |
| if (! query.IsEmpty()) { | |
| uri_path += "?"; | |
| uri_path += query; | |
| } | |
| // prevent hackers from accessing files outside of our root | |
| if ((file_path.Find("/..") >= 0) || (file_path.Find("\\..") >= 0)) { | |
| return NPT_FAILURE; | |
| } | |
| NPT_String file_id = (const char *) file_path + ((file_path[0]=='0') ? 2:1); | |
| for (u32 i=0; i<m_Directories.GetItemCount(); i++) { | |
| GPAC_MediaDirectory *dir; | |
| m_Directories.Get(i, dir); | |
| if (file_id.StartsWith(dir->m_Alias)) { | |
| file_path = dir->m_Path + ((const char *) file_id + strlen(dir->m_Alias)+1); | |
| break; | |
| } | |
| } | |
| //Virtual File request | |
| for (u32 i=0; i<m_VirtualFiles.GetItemCount(); i++) { | |
| GPAC_VirtualFile vfile; | |
| m_VirtualFiles.Get(i, vfile); | |
| NPT_String path = uri_path; | |
| path.Replace('/', NPT_FilePath::Separator); | |
| if (path == vfile.m_URI) { | |
| NPT_Result res; | |
| NPT_Position start, end; | |
| #if 0 | |
| PLT_HttpHelper::GetRange(request, start, end); | |
| #else | |
| start = end = -1; | |
| #endif | |
| res = ServeVirtualFile(response, &vfile, start, end, !request.GetMethod().Compare("HEAD")); | |
| if (vfile.m_temporary) { | |
| m_VirtualFiles.Remove(vfile); | |
| } | |
| return res; | |
| } | |
| } | |
| // File requested | |
| NPT_String path = m_FileRoot; | |
| if (path.Compare(uri_path.Left(path.GetLength()), true) == 0) { | |
| #if 0 | |
| NPT_Position start, end; | |
| PLT_HttpHelper::GetRange(request, start, end); | |
| return PLT_HttpServer::ServeFile(response, | |
| file_path, | |
| start, | |
| end, | |
| !request.GetMethod().Compare("HEAD")); | |
| #else | |
| return PLT_HttpServer::ServeFile(request, context, response, NPT_FilePath::Create(m_FileRoot, file_path) ); | |
| #endif | |
| } | |
| return NPT_FAILURE; | |
| } | |
| NPT_Result | |
| GPAC_FileMediaServer::GetFilePath(const char* object_id, | |
| NPT_String& filepath) | |
| { | |
| if (!object_id) return NPT_ERROR_INVALID_PARAMETERS; | |
| NPT_String obj_id = object_id + (object_id[0]=='0' ? 2 : 1); | |
| filepath = ""; | |
| for (u32 i=0; i<m_Directories.GetItemCount(); i++) { | |
| GPAC_MediaDirectory *dir; | |
| m_Directories.Get(i, dir); | |
| if (obj_id.StartsWith(dir->m_Alias)) { | |
| filepath = dir->m_Path; | |
| object_id += (object_id[0]=='0' ? 2 : 1); | |
| if (!strcmp(object_id, dir->m_Alias)) { | |
| object_id = ""; | |
| } else { | |
| object_id += strlen(dir->m_Alias) + 1; | |
| } | |
| break; | |
| } | |
| } | |
| if (NPT_StringLength(object_id) > 2 || object_id[0]!='0') { | |
| filepath += (const char*)object_id + (object_id[0]=='0'?1:0); | |
| } | |
| return NPT_SUCCESS; | |
| } | |
| PLT_MediaObject* | |
| GPAC_FileMediaServer::BuildFromFilePath(const NPT_String& filepath, | |
| const PLT_HttpRequestContext &context, | |
| bool with_count, | |
| bool keep_extension_in_title, | |
| bool allip) | |
| { | |
| return BuildFromFilePathAndHost(filepath, &context, with_count, keep_extension_in_title, NULL); | |
| } | |
| PLT_MediaObject* | |
| GPAC_FileMediaServer::BuildFromFilePathAndHost(const NPT_String& __filepath, | |
| const PLT_HttpRequestContext *context, | |
| bool with_count /* = true */, | |
| bool keep_extension_in_title /* = false */, | |
| const char *host) | |
| { | |
| PLT_MediaItemResource resource; | |
| PLT_MediaObject* object = NULL; | |
| NPT_String filepath = __filepath; | |
| unsigned int len; | |
| /* retrieve the entry type (directory or file) */ | |
| NPT_FileInfo info; | |
| NPT_CHECK_LABEL_FATAL(NPT_File::GetInfo(__filepath, &info), failure); | |
| len = 0; | |
| for (u32 i=0; i<m_Directories.GetItemCount(); i++) { | |
| GPAC_MediaDirectory *dir; | |
| m_Directories.Get(i, dir); | |
| if (__filepath.StartsWith(dir->m_Path) && (dir->m_Path.GetLength() > len) ) { | |
| char *fp = __filepath; | |
| filepath = NPT_FilePath::Separator + dir->m_Alias + NPT_String( fp + strlen(dir->m_Path) - 1); | |
| len = dir->m_Path.GetLength(); | |
| } | |
| } | |
| if (info.m_Type == NPT_FileInfo::FILE_TYPE_REGULAR) { | |
| NPT_IpAddress ip; | |
| 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; | |
| /* Set the protocol Info from the extension */ | |
| resource.m_ProtocolInfo = PLT_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 = "0" + filepath; | |
| if (host) { | |
| ip.Parse(host); | |
| NPT_List<NPT_NetworkInterface*> if_list; | |
| NPT_List<NPT_NetworkInterface*>::Iterator net_if; | |
| NPT_List<NPT_NetworkInterfaceAddress>::Iterator net_if_addr; | |
| NPT_CHECK_LABEL_SEVERE(PLT_UPnPMessageHelper::GetNetworkInterfaces(if_list, true), failure); | |
| for (net_if = if_list.GetFirstItem(); net_if; net_if++) { | |
| if ( (*net_if)->IsAddressInNetwork(ip) ) { | |
| ip = (*net_if)->GetAddresses().GetFirstItem()->GetPrimaryAddress(); | |
| break; | |
| } | |
| } | |
| } else if (context) { | |
| ip = context->GetLocalAddress().GetIpAddress(); | |
| } else { | |
| // get list of ip addresses | |
| NPT_List<NPT_IpAddress> ips; | |
| NPT_CHECK_LABEL_SEVERE(PLT_UPnPMessageHelper::GetIPAddresses(ips), failure); | |
| // iterate through list and build list of resources | |
| NPT_List<NPT_IpAddress>::Iterator ipi = ips.GetFirstItem(); | |
| ip = *ipi; | |
| } | |
| #if 0 | |
| /* Look to see if a metadatahandler exists for this extension */ | |
| PLT_MetadataHandler* handler = NULL; | |
| NPT_Result res = NPT_ContainerFind( | |
| m_MetadataHandlers, | |
| PLT_MetadataHandlerFinder(NPT_FilePath::FileExtension(filepath)), | |
| handler); | |
| if (NPT_SUCCEEDED(res) && handler) { | |
| /* if it failed loading data, reset the metadatahandler so we don't use it */ | |
| if (NPT_SUCCEEDED(handler->Load(filepath))) { | |
| /* replace the title with the one from the Metadata */ | |
| NPT_String newTitle; | |
| if (handler->GetTitle(newTitle) != NULL) { | |
| object->m_Title = newTitle; | |
| } | |
| /* assign description */ | |
| handler->GetDescription(object->m_Description.long_description); | |
| /* assign album art uri if we haven't yet */ | |
| /* prepend the album art base URI and url encode it */ | |
| if (object->m_ExtraInfo.album_art_uri.GetLength() == 0) { | |
| object->m_ExtraInfo.album_art_uri = | |
| NPT_Uri::PercentEncode(BuildResourceUri(m_AlbumArtBaseUri, ip.ToString(), url), | |
| NPT_Uri::UnsafeCharsToEncode); | |
| } | |
| /* duration */ | |
| handler->GetDuration(resource.m_Duration); | |
| /* protection */ | |
| handler->GetProtection(resource.m_Protection); | |
| } | |
| } | |
| #endif | |
| object->m_ObjectClass.type = PLT_MediaItem::GetUPnPClass(filepath, context); | |
| NPT_HttpUrl base_uri(m_FileRoot); | |
| resource.m_Uri = BuildResourceUri(base_uri/*m_FileBaseUri*/, ip.ToString(), url); | |
| object->m_Resources.Add(resource); | |
| } else { | |
| object = new PLT_MediaContainer; | |
| /* Assign a title for this container */ | |
| if ((filepath.Compare("/", true) == 0) || (filepath.Compare("\\", true) == 0)) { | |
| object->m_Title = "Root"; | |
| } else { | |
| filepath.TrimRight("/\\"); | |
| object->m_Title = NPT_FilePath::BaseName(filepath, keep_extension_in_title); | |
| if (object->m_Title.GetLength() == 0) goto failure; | |
| } | |
| #ifndef _WIN32_WCE | |
| /* Get the number of children for this container */ | |
| NPT_Cardinal count = 0; | |
| // reset output params | |
| count = 0; | |
| if (with_count ) { | |
| NPT_List<NPT_String> entries; | |
| NPT_File::ListDir(__filepath, entries); | |
| count = entries.GetItemCount(); | |
| ((PLT_MediaContainer*)object)->m_ChildrenCount = count; | |
| } | |
| #endif //_WIN32_WCE | |
| object->m_ObjectClass.type = "object.container"; | |
| } | |
| /* is it the root? */ | |
| if ((filepath.Compare("/", true) == 0) || (filepath.Compare("\\", 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.Compare("/", true) == 0) || (directory.Compare("\\", true) == 0)) { | |
| object->m_ParentID = "0"; | |
| } else { | |
| object->m_ParentID = "0" + directory; | |
| } | |
| object->m_ObjectID = "0" + filepath; | |
| } | |
| return object; | |
| failure: | |
| delete object; | |
| return NULL; | |
| } | |
| NPT_String GPAC_FileMediaServer::GetResourceURI(const char *url, const char *for_host) | |
| { | |
| char *abs_url; | |
| NPT_String res = url, path; | |
| abs_url = NULL; | |
| for (u32 i=0; i<m_Directories.GetItemCount(); i++) { | |
| GPAC_MediaDirectory *dir; | |
| m_Directories.Get(i, dir); | |
| abs_url = gf_url_concatenate(dir->m_Path, url); | |
| FILE *f = gf_fopen(abs_url, "rt"); | |
| if (f) { | |
| gf_fclose(f); | |
| break; | |
| } | |
| gf_free((void*)abs_url); | |
| abs_url = NULL; | |
| } | |
| if (!abs_url ) return res; | |
| /*url was absolute, add its root directory*/ | |
| if (!strcmp(abs_url, url)) { | |
| Bool found = GF_FALSE; | |
| NPT_String newdir; | |
| /*if the path is /my/example/path/test.ext, we want to share the parent directory | |
| /my/example/, otherwise we will loose the ability to browse for resource in the parent dir | |
| */ | |
| u32 nb_sep=0; | |
| u32 len = (u32) strlen(abs_url); | |
| u32 i=0; | |
| while (i<=len) { | |
| if ((abs_url[len-i]=='\\') || (abs_url[len-i]=='/') ) { | |
| nb_sep++; | |
| if (nb_sep==2) break; | |
| } | |
| i++; | |
| } | |
| /*if no parent directory, we don't allow sharing of the resource*/ | |
| if (nb_sep!=2) return ""; | |
| char sep = abs_url[len-i]; | |
| abs_url[len-i] = 0; | |
| newdir = abs_url; | |
| abs_url[len-i] = sep; | |
| newdir.Replace('/', NPT_FilePath::Separator); | |
| newdir += NPT_FilePath::Separator; | |
| for (u32 i=0; i<m_Directories.GetItemCount(); i++) { | |
| GPAC_MediaDirectory *dir; | |
| m_Directories.Get(i, dir); | |
| if (!strcmp(newdir, dir->m_Path)) { | |
| found = GF_TRUE; | |
| } | |
| } | |
| if (!found) | |
| AddSharedDirectory(newdir, NULL, GF_TRUE); | |
| } | |
| path = abs_url; | |
| /*replace all '/' with neptune file path separator otherwise file functions get screwed up ...*/ | |
| path.Replace('/', NPT_FilePath::Separator); | |
| gf_free((void*)abs_url); | |
| PLT_MediaObject*mobj = BuildFromFilePathAndHost(path, NULL, true, false, for_host); | |
| if (mobj) { | |
| res = mobj->m_Resources[0].m_Uri; | |
| delete mobj; | |
| } | |
| return res; | |
| } | |
| void GPAC_FileMediaServer::ShareVirtualResource(const char *res_uri, const char *res_val, const char *res_mime, Bool temporary) | |
| { | |
| NPT_String the_uri; | |
| char *uri = (char *)res_uri; | |
| char *sep = (char *) strstr(res_uri, "://"); | |
| if (sep) { | |
| sep = strchr(sep+3, '/'); | |
| if (sep) uri = sep; | |
| else uri = (char*)"/"; | |
| } | |
| the_uri = ""; | |
| while (1) { | |
| sep = strchr(uri, '%'); | |
| if (!sep) break; | |
| if (!strnicmp(sep, "%3f", 3) || !strnicmp(sep, "%5c", 3) || !strnicmp(sep, "%2f", 3)) { | |
| sep[0]=0; | |
| the_uri += uri; | |
| sep[0]='%'; | |
| if (!strnicmp(sep, "%3f", 3)) | |
| the_uri += "?"; | |
| else if (!strnicmp(sep, "%5c", 3)) | |
| the_uri += "/"; | |
| else | |
| the_uri += " "; | |
| uri= sep+3; | |
| continue; | |
| } else { | |
| the_uri += "%"; | |
| uri= sep+1; | |
| } | |
| } | |
| the_uri += uri; | |
| the_uri.Replace('/', NPT_FilePath::Separator); | |
| GPAC_VirtualFile vres(the_uri, res_val, res_mime, temporary); | |
| m_VirtualFiles.Add(vres); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("[UPnP] sharing virtual file %s as %s\n", res_uri, (const char *)the_uri)); | |
| } | |
| NPT_Result | |
| GPAC_FileMediaServer::ServeVirtualFile(NPT_HttpResponse& response, | |
| GPAC_VirtualFile *vfile, | |
| NPT_Position start, | |
| NPT_Position end, | |
| bool request_is_head) | |
| { | |
| NPT_LargeSize total_len; | |
| NPT_Result result; | |
| NPT_MemoryStream* memory_stream = new NPT_MemoryStream(vfile->m_Content.GetChars(), vfile->m_Content.GetLength() ); | |
| NPT_InputStreamReference stream(memory_stream); | |
| if (NPT_FAILED(result = stream->GetSize(total_len)) ) { | |
| response.SetStatus(404, "File Not Found"); | |
| return NPT_SUCCESS; | |
| } | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("[UPnP] Serving virtual file %s\n", vfile->m_Content.GetChars())); | |
| NPT_HttpEntity* entity = new NPT_HttpEntity(); | |
| entity->SetContentLength(total_len); | |
| response.SetEntity(entity); | |
| entity->SetContentType(vfile->m_MIME); | |
| // request is HEAD, returns without setting a body | |
| if (request_is_head) return NPT_SUCCESS; | |
| // see if it was a byte range request | |
| if (start != (NPT_Position)-1 || end != (NPT_Position)-1) { | |
| // we can only support a range from an offset to the end of the resource for now | |
| // due to the fact we can't limit how much to read from a stream yet | |
| NPT_Position start_offset = (NPT_Position)-1, end_offset = total_len - 1, len; | |
| if (start == (NPT_Position)-1 && end != (NPT_Position)-1) { | |
| // we are asked for the last N=end bytes | |
| // adjust according to total length | |
| if (end >= total_len) { | |
| start_offset = 0; | |
| } else { | |
| start_offset = total_len-end; | |
| } | |
| } else if (start != (NPT_Position)-1) { | |
| start_offset = start; | |
| // if the end is specified but incorrect | |
| // set the end_offset in order to generate a bad response | |
| if (end != (NPT_Position)-1 && end < start) { | |
| end_offset = (NPT_Position)-1; | |
| } | |
| } | |
| // in case the range request was invalid or we can't seek then respond appropriately | |
| if (start_offset == (NPT_Position)-1 || end_offset == (NPT_Position)-1 || | |
| start_offset > end_offset || NPT_FAILED(stream->Seek(start_offset))) { | |
| response.SetStatus(416, "Requested range not satisfiable"); | |
| } else { | |
| len = end_offset - start_offset + 1; | |
| response.SetStatus(206, "Partial Content"); | |
| entity->SetInputStream(stream); | |
| entity->SetContentLength(len); | |
| } | |
| } else { | |
| entity->SetInputStream(stream); | |
| } | |
| return NPT_SUCCESS; | |
| } | |