root/modules/platinum/GPACFileMediaServer.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. NPT_SET_LOCAL_LOGGER
  2. AddSharedDirectory
  3. OnBrowseDirectChildren
  4. ServeFile
  5. GetFilePath
  6. BuildFromFilePath
  7. BuildFromFilePathAndHost
  8. GetResourceURI
  9. ShareVirtualResource
  10. ServeVirtualFile

/*
 *                      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;
}


/* [<][>][^][v][top][bottom][index][help] */