diff src/cds.c @ 125:e413158cae13

Add ushare project files.
author naoyan@johnstown.minaminoshima.org
date Sun, 03 Oct 2010 11:35:19 +0900
parents
children 2a9ac5ce2c7e
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cds.c	Sun Oct 03 11:35:19 2010 +0900
@@ -0,0 +1,872 @@
+/*
+ * cds.c : GeeXboX uShare Content Directory Service
+ * Originally developped for the GeeXboX project.
+ * Parts of the code are originated from GMediaServer from Oskar Liljeblad.
+ * Copyright (C) 2005-2007 Benjamin Zores <ben@geexbox.org>
+ *
+ * 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.
+ *
+ * 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <stdlib.h>
+#include <upnp/upnp.h>
+#include <upnp/upnptools.h>
+
+#include "ushare.h"
+#include "services.h"
+#include "ushare.h"
+#include "services.h"
+#include "metadata.h"
+#include "mime.h"
+#include "buffer.h"
+#include "minmax.h"
+
+/* Represent the CDS GetSearchCapabilities action. */
+#define SERVICE_CDS_ACTION_SEARCH_CAPS "GetSearchCapabilities"
+
+/* Represent the CDS GetSortCapabilities action. */
+#define SERVICE_CDS_ACTION_SORT_CAPS "GetSortCapabilities"
+
+/* Represent the CDS GetSystemUpdateID action. */
+#define SERVICE_CDS_ACTION_UPDATE_ID "GetSystemUpdateID"
+
+/* Represent the CDS Browse action. */
+#define SERVICE_CDS_ACTION_BROWSE "Browse"
+
+/* Represent the CDS Search action. */
+#define SERVICE_CDS_ACTION_SEARCH "Search"
+
+/* Represent the CDS SearchCaps argument. */
+#define SERVICE_CDS_ARG_SEARCH_CAPS "SearchCaps"
+
+/* Represent the CDS SortCaps argument. */
+#define SERVICE_CDS_ARG_SORT_CAPS "SortCaps"
+
+/* Represent the CDS UpdateId argument. */
+#define SERVICE_CDS_ARG_UPDATE_ID "Id"
+
+/* Represent the CDS StartingIndex argument. */
+#define SERVICE_CDS_ARG_START_INDEX "StartingIndex"
+
+/* Represent the CDS RequestedCount argument. */
+#define SERVICE_CDS_ARG_REQUEST_COUNT "RequestedCount"
+
+/* Represent the CDS ObjectID argument. */
+#define SERVICE_CDS_ARG_OBJECT_ID "ObjectID"
+
+/* Represent the CDS Filter argument. */
+#define SERVICE_CDS_ARG_FILTER "Filter"
+
+/* Represent the CDS BrowseFlag argument. */
+#define SERVICE_CDS_ARG_BROWSE_FLAG "BrowseFlag"
+
+/* Represent the CDS SortCriteria argument. */
+#define SERVICE_CDS_ARG_SORT_CRIT "SortCriteria"
+
+/* Represent the CDS SearchCriteria argument. */
+#define SERVICE_CDS_ARG_SEARCH_CRIT "SearchCriteria"
+
+/* Represent the CDS Root Object ID argument. */
+#define SERVICE_CDS_ROOT_OBJECT_ID "0"
+
+/* Represent the CDS DIDL Message Metadata Browse flag argument. */
+#define SERVICE_CDS_BROWSE_METADATA "BrowseMetadata"
+
+/* Represent the CDS DIDL Message DirectChildren Browse flag argument. */
+#define SERVICE_CDS_BROWSE_CHILDREN "BrowseDirectChildren"
+
+/* Represent the CDS DIDL Message Result argument. */
+#define SERVICE_CDS_DIDL_RESULT "Result"
+
+/* Represent the CDS DIDL Message NumberReturned argument. */
+#define SERVICE_CDS_DIDL_NUM_RETURNED "NumberReturned"
+
+/* Represent the CDS DIDL Message TotalMatches argument. */
+#define SERVICE_CDS_DIDL_TOTAL_MATCH "TotalMatches"
+
+/* Represent the CDS DIDL Message UpdateID argument. */
+#define SERVICE_CDS_DIDL_UPDATE_ID "UpdateID"
+
+/* DIDL parameters */
+/* Represent the CDS DIDL Message Header Namespace. */
+#define DIDL_NAMESPACE \
+    "xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" " \
+    "xmlns:dc=\"http://purl.org/dc/elements/1.1/\" " \
+    "xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\""
+
+/* Represent the CDS DIDL Message Header Tag. */
+#define DIDL_LITE "DIDL-Lite"
+
+/* Represent the CDS DIDL Message Item value. */
+#define DIDL_ITEM "item"
+
+/* Represent the CDS DIDL Message Item ID value. */
+#define DIDL_ITEM_ID "id"
+
+/* Represent the CDS DIDL Message Item Parent ID value. */
+#define DIDL_ITEM_PARENT_ID "parentID"
+
+/* Represent the CDS DIDL Message Item Restricted value. */
+#define DIDL_ITEM_RESTRICTED "restricted"
+
+/* Represent the CDS DIDL Message Item UPnP Class value. */
+#define DIDL_ITEM_CLASS "upnp:class"
+
+/* Represent the CDS DIDL Message Item Title value. */
+#define DIDL_ITEM_TITLE "dc:title"
+
+/* Represent the CDS DIDL Message Item Resource value. */
+#define DIDL_RES "res"
+
+/* Represent the CDS DIDL Message Item Protocol Info value. */
+#define DIDL_RES_INFO "protocolInfo"
+
+/* Represent the CDS DIDL Message Item Resource Size value. */
+#define DIDL_RES_SIZE "size"
+
+/* Represent the CDS DIDL Message Container value. */
+#define DIDL_CONTAINER "container"
+
+/* Represent the CDS DIDL Message Container ID value. */
+#define DIDL_CONTAINER_ID "id"
+
+/* Represent the CDS DIDL Message Container Parent ID value. */
+#define DIDL_CONTAINER_PARENT_ID "parentID"
+
+/* Represent the CDS DIDL Message Container number of children value. */
+#define DIDL_CONTAINER_CHILDS "childCount"
+
+/* Represent the CDS DIDL Message Container Restricted value. */
+#define DIDL_CONTAINER_RESTRICTED "restricted"
+
+/* Represent the CDS DIDL Message Container Searchable value. */
+#define DIDL_CONTAINER_SEARCH "searchable"
+
+/* Represent the CDS DIDL Message Container UPnP Class value. */
+#define DIDL_CONTAINER_CLASS "upnp:class"
+
+/* Represent the CDS DIDL Message Container Title value. */
+#define DIDL_CONTAINER_TITLE "dc:title"
+
+/* Represent the "upnp:class" reserved keyword for Search action */
+#define SEARCH_CLASS_MATCH_KEYWORD "(upnp:class = \""
+
+/* Represent the "upnp:class derived from" reserved keyword */
+#define SEARCH_CLASS_DERIVED_KEYWORD "(upnp:class derivedfrom \""
+
+/* Represent the "res@protocolInfo contains" reserved keyword */
+#define SEARCH_PROTOCOL_CONTAINS_KEYWORD "(res@protocolInfo contains \""
+
+/* Represent the Search default keyword */
+#define SEARCH_OBJECT_KEYWORD "object"
+
+/* Represent the Search 'AND' connector keyword */
+#define SEARCH_AND ") and ("
+
+static bool
+filter_has_val (const char *filter, const char *val)
+{
+  char *x = NULL, *token = NULL;
+  char *m_buffer = NULL, *buffer;
+  int len = strlen (val);
+  bool ret = false;
+
+  if (!strcmp (filter, "*"))
+    return true;
+
+  x = strdup (filter);
+  if (x)
+  {
+    m_buffer = (char*) malloc (strlen (x));
+    if (m_buffer)
+    {
+      buffer = m_buffer;
+      token = strtok_r (x, ",", &buffer);
+      while (token)
+      {
+        if (*val == '@')
+          token = strchr (token, '@');
+        if (token && !strncmp (token, val, len))
+        {
+          ret = true;
+          break;
+        }
+        token = strtok_r (NULL, ",", &buffer);
+      }
+      free (m_buffer);
+    }
+    free (x);
+  }
+  return ret;
+}
+
+/* UPnP ContentDirectory Service actions */
+static bool
+cds_get_search_capabilities (struct action_event_t *event)
+{
+  upnp_add_response (event, SERVICE_CDS_ARG_SEARCH_CAPS, "");
+
+  return event->status;
+}
+
+static bool
+cds_get_sort_capabilities (struct action_event_t *event)
+{
+  upnp_add_response (event, SERVICE_CDS_ARG_SORT_CAPS, "");
+
+  return event->status;
+}
+
+static bool
+cds_get_system_update_id (struct action_event_t *event)
+{
+  upnp_add_response (event, SERVICE_CDS_ARG_UPDATE_ID,
+                     SERVICE_CDS_ROOT_OBJECT_ID);
+
+  return event->status;
+}
+
+static void
+didl_add_header (struct buffer_t *out)
+{
+  buffer_appendf (out, "<%s %s>", DIDL_LITE, DIDL_NAMESPACE);
+}
+
+static void
+didl_add_footer (struct buffer_t *out)
+{
+  buffer_appendf (out, "</%s>", DIDL_LITE);
+}
+
+static void
+didl_add_tag (struct buffer_t *out, char *tag, char *value)
+{
+  if (value)
+    buffer_appendf (out, "<%s>%s</%s>", tag, value, tag);
+}
+
+static void
+didl_add_param (struct buffer_t *out, char *param, char *value)
+{
+  if (value)
+    buffer_appendf (out, " %s=\"%s\"", param, value);
+}
+
+static void
+didl_add_value (struct buffer_t *out, char *param, off_t value)
+{
+  buffer_appendf (out, " %s=\"%lld\"", param, value);
+}
+
+static void
+didl_add_item (struct buffer_t *out, int item_id,
+               int parent_id, char *restricted, char *class, char *title,
+               char *protocol_info, off_t size, char *url, char *filter)
+{
+  buffer_appendf (out, "<%s", DIDL_ITEM);
+  didl_add_value (out, DIDL_ITEM_ID, item_id);
+  didl_add_value (out, DIDL_ITEM_PARENT_ID, parent_id);
+  didl_add_param (out, DIDL_ITEM_RESTRICTED, restricted);
+  buffer_append (out, ">");
+
+  didl_add_tag (out, DIDL_ITEM_CLASS, class);
+  didl_add_tag (out, DIDL_ITEM_TITLE, title);
+
+  if (filter_has_val (filter, DIDL_RES))
+  {
+    buffer_appendf (out, "<%s", DIDL_RES);
+    // protocolInfo is required :
+    didl_add_param (out, DIDL_RES_INFO, protocol_info);
+    if (filter_has_val (filter, "@"DIDL_RES_SIZE))
+      didl_add_value (out, DIDL_RES_SIZE, size);
+    buffer_append (out, ">");
+    if (url)
+    {
+      extern struct ushare_t *ut;
+      buffer_appendf (out, "http://%s:%d%s/%s",
+                      UpnpGetServerIpAddress (), ut->port, VIRTUAL_DIR, url);
+    }
+    buffer_appendf (out, "</%s>", DIDL_RES);
+  }
+  buffer_appendf (out, "</%s>", DIDL_ITEM);
+}
+
+static void
+didl_add_container (struct buffer_t *out, int id, int parent_id,
+                    int child_count, char *restricted, char *searchable,
+                    char *title, char *class)
+{
+  buffer_appendf (out, "<%s", DIDL_CONTAINER);
+
+  didl_add_value (out, DIDL_CONTAINER_ID, id);
+  didl_add_value (out, DIDL_CONTAINER_PARENT_ID, parent_id);
+  if (child_count >= 0)
+    didl_add_value (out, DIDL_CONTAINER_CHILDS, child_count);
+  didl_add_param (out, DIDL_CONTAINER_RESTRICTED, restricted);
+  didl_add_param (out, DIDL_CONTAINER_SEARCH, searchable);
+  buffer_append (out, ">");
+
+  didl_add_tag (out, DIDL_CONTAINER_CLASS, class);
+  didl_add_tag (out, DIDL_CONTAINER_TITLE, title);
+
+  buffer_appendf (out, "</%s>", DIDL_CONTAINER);
+}
+
+static int
+cds_browse_metadata (struct action_event_t *event, struct buffer_t *out,
+                     int index, int count, struct upnp_entry_t *entry,
+                     char *filter)
+{
+  int result_count = 0, c = 0;
+
+  if (!entry)
+    return -1;
+
+  if (entry->child_count == -1) /* item : file */
+  {
+#ifdef HAVE_DLNA
+    extern struct ushare_t *ut;
+#endif /* HAVE_DLNA */
+    
+    char *protocol =
+#ifdef HAVE_DLNA
+      entry->dlna_profile ?
+      dlna_write_protocol_info (DLNA_PROTOCOL_INFO_TYPE_HTTP,
+                                DLNA_ORG_PLAY_SPEED_NORMAL,
+                                DLNA_ORG_CONVERSION_NONE,
+                                DLNA_ORG_OPERATION_RANGE,
+                                ut->dlna_flags, entry->dlna_profile) :
+#endif /* HAVE_DLNA */
+      mime_get_protocol (entry->mime_type);
+    
+    didl_add_header (out);
+#ifdef HAVE_DLNA
+    entry->dlna_profile ?
+      didl_add_item (out, entry->id, entry->parent
+                     ? entry->parent->id : -1, "false",
+                     dlna_profile_upnp_object_item (entry->dlna_profile),
+                     entry->title,
+                     protocol, entry->size,
+                     entry->url, filter) :
+#endif /* HAVE_DLNA */
+      didl_add_item (out, entry->id, entry->parent
+                     ? entry->parent->id : -1, "false",
+                     entry->mime_type->mime_class, entry->title,
+                     protocol, entry->size,
+                     entry->url, filter);
+    
+    didl_add_footer (out);
+    free (protocol);
+    
+    for (c = index; c < MIN (index + count, entry->child_count); c++)
+      result_count++;
+  }
+  else  /* container : directory */
+  {
+    didl_add_header (out);
+    didl_add_container (out, entry->id, entry->parent
+                        ? entry->parent->id : -1, entry->child_count,
+                        "true", "true", entry->title,
+                        entry->mime_type->mime_class);
+    didl_add_footer (out);
+
+    result_count = 1;
+  }
+
+  upnp_add_response (event, SERVICE_CDS_DIDL_RESULT, out->buf);
+  upnp_add_response (event, SERVICE_CDS_DIDL_NUM_RETURNED, "1");
+  upnp_add_response (event, SERVICE_CDS_DIDL_TOTAL_MATCH, "1");
+
+  return result_count;
+}
+
+static int
+cds_browse_directchildren (struct action_event_t *event,
+                           struct buffer_t *out, int index,
+                           int count, struct upnp_entry_t *entry, char *filter)
+{
+  struct upnp_entry_t **childs;
+  int s, result_count = 0;
+  char tmp[32];
+
+  if (entry->child_count == -1) /* item : file */
+    return -1;
+
+  didl_add_header (out);
+
+  /* go to the child pointed out by index */
+  childs = entry->childs;
+  for (s = 0; s < index; s++)
+    if (*childs)
+      childs++;
+
+  /* UPnP CDS compliance : If starting index = 0 and requested count = 0
+     then all children must be returned */
+  if (index == 0 && count == 0)
+    count = entry->child_count;
+
+  for (; *childs; childs++)
+  {
+    if (count == 0 || result_count < count)
+      /* only fetch the requested count number or all entries if count = 0 */
+    {
+      if ((*childs)->child_count >= 0) /* container */
+        didl_add_container (out, (*childs)->id, (*childs)->parent ?
+                            (*childs)->parent->id : -1,
+                            (*childs)->child_count, "true", NULL,
+                            (*childs)->title,
+                            (*childs)->mime_type->mime_class);
+      else /* item */
+      {
+#ifdef HAVE_DLNA
+        extern struct ushare_t *ut;
+#endif /* HAVE_DLNA */
+
+        char *protocol =
+#ifdef HAVE_DLNA
+          (*childs)->dlna_profile ?
+          dlna_write_protocol_info (DLNA_PROTOCOL_INFO_TYPE_HTTP,
+                                    DLNA_ORG_PLAY_SPEED_NORMAL,
+                                    DLNA_ORG_CONVERSION_NONE,
+                                    DLNA_ORG_OPERATION_RANGE,
+                                    ut->dlna_flags, (*childs)->dlna_profile) :
+#endif /* HAVE_DLNA */
+          mime_get_protocol ((*childs)->mime_type);
+
+#ifdef HAVE_DLNA
+        (*childs)->dlna_profile ?
+          didl_add_item (out, (*childs)->id,
+                         (*childs)->parent ? (*childs)->parent->id : -1,
+                         "true", dlna_profile_upnp_object_item ((*childs)->dlna_profile),
+                         (*childs)->title, protocol,
+                         (*childs)->size, (*childs)->url, filter) :
+#endif /* HAVE_DLNA */
+          didl_add_item (out, (*childs)->id,
+                         (*childs)->parent ? (*childs)->parent->id : -1,
+                         "true", (*childs)->mime_type->mime_class,
+                         (*childs)->title, protocol,
+                         (*childs)->size, (*childs)->url, filter);
+
+        free (protocol);
+      }
+      result_count++;
+    }
+  }
+
+  didl_add_footer (out);
+
+  upnp_add_response (event, SERVICE_CDS_DIDL_RESULT, out->buf);
+  sprintf (tmp, "%d", result_count);
+  upnp_add_response (event, SERVICE_CDS_DIDL_NUM_RETURNED, tmp);
+  sprintf (tmp, "%d", entry->child_count);
+  upnp_add_response (event, SERVICE_CDS_DIDL_TOTAL_MATCH, tmp);
+
+  return result_count;
+}
+
+static bool
+cds_browse (struct action_event_t *event)
+{
+  extern struct ushare_t *ut;
+  struct upnp_entry_t *entry = NULL;
+  int result_count = 0, index, count, id, sort_criteria;
+  char *flag = NULL;
+  char *filter = NULL;
+  struct buffer_t *out = NULL;
+  bool metadata;
+
+  if (!event)
+    return false;
+
+  /* Check for status */
+  if (!event->status)
+    return false;
+
+  /* check if metadatas have been well inited */
+  if (!ut->init)
+    return false;
+
+  /* Retrieve Browse arguments */
+  index = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_START_INDEX);
+  count = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_REQUEST_COUNT);
+  id = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_OBJECT_ID);
+  flag = upnp_get_string (event->request, SERVICE_CDS_ARG_BROWSE_FLAG);
+  filter = upnp_get_string (event->request, SERVICE_CDS_ARG_FILTER);
+  sort_criteria = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_SORT_CRIT);
+
+  if (!flag || !filter)
+    return false;
+
+  /* Check arguments validity */
+  if (!strcmp (flag, SERVICE_CDS_BROWSE_METADATA))
+    {
+      if (index)
+      {
+        free (flag);
+        return false;
+      }
+      metadata = true;
+    }
+  else if (!strcmp (flag, SERVICE_CDS_BROWSE_CHILDREN))
+    metadata = false;
+  else
+  {
+    free (flag);
+    return false;
+  }
+  free (flag);
+
+  entry = upnp_get_entry (ut, id);
+  if (!entry && (id < ut->starting_id))
+    entry = upnp_get_entry (ut, ut->starting_id);
+
+  if (!entry)
+  {
+    free (filter);
+    return false;
+  }
+
+  out = buffer_new ();
+  if (!out)
+  {
+    free (filter);
+    return false;
+  }
+
+  if (metadata)
+    result_count =
+      cds_browse_metadata (event, out, index, count, entry, filter);
+  else
+    result_count =
+      cds_browse_directchildren (event, out, index, count, entry, filter);
+  free (filter);
+
+  if (result_count < 0)
+  {
+    buffer_free (out);
+    return false;
+  }
+
+  buffer_free (out);
+  upnp_add_response (event, SERVICE_CDS_DIDL_UPDATE_ID,
+                     SERVICE_CDS_ROOT_OBJECT_ID);
+
+  return event->status;
+}
+
+static bool
+matches_search (char *search_criteria, struct upnp_entry_t *entry)
+{
+  char keyword[256] = SEARCH_OBJECT_KEYWORD;
+  bool derived_from = false, protocol_contains = false, result = false;
+  char *quote_closed = NULL, *and_clause = NULL;
+#ifdef HAVE_DLNA
+  extern struct ushare_t *ut;
+#endif /* HAVE_DLNA */
+  char *protocol =
+#ifdef HAVE_DLNA
+    entry->dlna_profile ?
+    dlna_write_protocol_info (DLNA_PROTOCOL_INFO_TYPE_HTTP,
+                              DLNA_ORG_PLAY_SPEED_NORMAL,
+                              DLNA_ORG_CONVERSION_NONE,
+                              DLNA_ORG_OPERATION_RANGE,
+                              ut->dlna_flags, entry->dlna_profile) :
+#endif /* HAVE_DLNA */
+    mime_get_protocol (entry->mime_type);
+  
+  if (!strncmp (search_criteria, SEARCH_CLASS_MATCH_KEYWORD,
+                strlen (SEARCH_CLASS_MATCH_KEYWORD)))
+  {
+    strncpy (keyword, search_criteria
+             + strlen (SEARCH_CLASS_MATCH_KEYWORD), sizeof (keyword));
+    quote_closed = strchr (keyword, '"');
+
+    if (quote_closed)
+      *quote_closed = '\0';
+  }
+  else if (!strncmp (search_criteria, SEARCH_CLASS_DERIVED_KEYWORD,
+                     strlen (SEARCH_CLASS_DERIVED_KEYWORD)))
+  {
+    derived_from = true;
+    strncpy (keyword, search_criteria
+             + strlen (SEARCH_CLASS_DERIVED_KEYWORD), sizeof (keyword));
+    quote_closed = strchr (keyword, '"');
+
+    if (quote_closed)
+      *quote_closed = '\0';
+  }
+  else if (!strncmp (search_criteria, SEARCH_PROTOCOL_CONTAINS_KEYWORD,
+                     strlen (SEARCH_PROTOCOL_CONTAINS_KEYWORD)))
+  {
+    protocol_contains = true;
+    strncpy (keyword, search_criteria
+             + strlen (SEARCH_PROTOCOL_CONTAINS_KEYWORD), sizeof (keyword));
+    quote_closed = strchr (keyword, '"');
+
+    if (quote_closed)
+      *quote_closed = '\0';
+  }
+
+  if (derived_from && entry->mime_type
+      && !strncmp (entry->mime_type->mime_class, keyword, strlen (keyword)))
+    result = true;
+  else if (protocol_contains && strstr (protocol, keyword))
+    result = true;
+  else if (entry->mime_type &&
+           !strcmp (entry->mime_type->mime_class, keyword))
+    result = true;
+  free (protocol);
+  
+  and_clause = strstr (search_criteria, SEARCH_AND);
+  if (and_clause)
+    return (result &&
+            matches_search (and_clause + strlen (SEARCH_AND) -1, entry));
+
+  return true;
+}
+
+static int
+cds_search_directchildren_recursive (struct buffer_t *out, int count,
+                                     struct upnp_entry_t *entry, char *filter,
+                                     char *search_criteria)
+{
+  struct upnp_entry_t **childs;
+  int result_count = 0;
+
+  if (entry->child_count == -1) /* item : file */
+    return -1;
+
+  /* go to the first child */
+  childs = entry->childs;
+
+  for (; *childs; childs++)
+  {
+    if (count == 0 || result_count < count)
+      /* only fetch the requested count number or all entries if count = 0 */
+    {
+      if ((*childs)->child_count >= 0) /* container */
+      {
+        int new_count;
+        new_count = cds_search_directchildren_recursive
+          (out, (count == 0) ? 0 : (count - result_count),
+           (*childs), filter, search_criteria);
+        result_count += new_count;
+      }
+      else /* item */
+      {
+        if (matches_search (search_criteria, *childs))
+        {
+#ifdef HAVE_DLNA
+          extern struct ushare_t *ut;
+#endif /* HAVE_DLNA */
+          char *protocol =
+#ifdef HAVE_DLNA
+            (*childs)->dlna_profile ?
+            dlna_write_protocol_info(DLNA_PROTOCOL_INFO_TYPE_HTTP,
+                                     DLNA_ORG_PLAY_SPEED_NORMAL,
+                                     DLNA_ORG_CONVERSION_NONE,
+                                     DLNA_ORG_OPERATION_RANGE,
+                                     ut->dlna_flags, (*childs)->dlna_profile):
+#endif /* HAVE_DLNA */
+            mime_get_protocol ((*childs)->mime_type);
+
+#ifdef HAVE_DLNA
+          (*childs)->dlna_profile ?
+            didl_add_item (out, (*childs)->id,
+                           (*childs)->parent ? (*childs)->parent->id : -1,
+                           "true", dlna_profile_upnp_object_item ((*childs)->dlna_profile),
+                           (*childs)->title, protocol,
+                           (*childs)->size, (*childs)->url, filter) :
+#endif /* HAVE_DLNA */
+            didl_add_item (out, (*childs)->id,
+                           (*childs)->parent ? (*childs)->parent->id : -1,
+                           "true", (*childs)->mime_type->mime_class,
+                           (*childs)->title, protocol,
+                           (*childs)->size, (*childs)->url, filter);
+          free (protocol);
+          result_count++;
+        }
+      }
+    }
+  }
+
+  return result_count;
+}
+
+static int
+cds_search_directchildren (struct action_event_t *event,
+                           struct buffer_t *out, int index,
+                           int count, struct upnp_entry_t *entry,
+                           char *filter, char *search_criteria)
+{
+  struct upnp_entry_t **childs;
+  int s, result_count = 0;
+  char tmp[32];
+
+  index = 0;
+
+  if (entry->child_count == -1) /* item : file */
+    return -1;
+
+  didl_add_header (out);
+
+  /* go to the child pointed out by index */
+  childs = entry->childs;
+  for (s = 0; s < index; s++)
+    if (*childs)
+      childs++;
+
+  /* UPnP CDS compliance : If starting index = 0 and requested count = 0
+     then all children must be returned */
+  if (index == 0 && count == 0)
+    count = entry->child_count;
+
+  for (; *childs; childs++)
+  {
+    if (count == 0 || result_count < count)
+      /* only fetch the requested count number or all entries if count = 0 */
+    {
+      if ((*childs)->child_count >= 0) /* container */
+      {
+        int new_count;
+        new_count = cds_search_directchildren_recursive
+          (out, (count == 0) ? 0 : (count - result_count),
+           (*childs), filter, search_criteria);
+        result_count += new_count;
+      }
+      else /* item */
+      {
+        if (matches_search (search_criteria, *childs))
+        {
+#ifdef HAVE_DLNA
+          extern struct ushare_t *ut;
+#endif /* HAVE_DLNA */
+          char *protocol =
+#ifdef HAVE_DLNA
+            (*childs)->dlna_profile ?
+            dlna_write_protocol_info(DLNA_PROTOCOL_INFO_TYPE_HTTP,
+                                     DLNA_ORG_PLAY_SPEED_NORMAL,
+                                     DLNA_ORG_CONVERSION_NONE,
+                                     DLNA_ORG_OPERATION_RANGE,
+                                     ut->dlna_flags, (*childs)->dlna_profile):
+#endif /* HAVE_DLNA */
+            mime_get_protocol ((*childs)->mime_type);
+
+#ifdef HAVE_DLNA
+          (*childs)->dlna_profile ?
+            didl_add_item (out, (*childs)->id,
+                           (*childs)->parent ? (*childs)->parent->id : -1,
+                           "true", dlna_profile_upnp_object_item ((*childs)->dlna_profile),
+                           (*childs)->title, protocol,
+                           (*childs)->size, (*childs)->url, filter) :
+#endif /* HAVE_DLNA */
+            didl_add_item (out, (*childs)->id,
+                           (*childs)->parent ? (*childs)->parent->id : -1,
+                           "true", (*childs)->mime_type->mime_class,
+                           (*childs)->title, protocol,
+                           (*childs)->size, (*childs)->url, filter);
+          free (protocol);
+          result_count++;
+        }
+      }
+    }
+  }
+
+  didl_add_footer (out);
+
+  upnp_add_response (event, SERVICE_CDS_DIDL_RESULT, out->buf);
+
+  sprintf (tmp, "%d", result_count);
+  upnp_add_response (event, SERVICE_CDS_DIDL_NUM_RETURNED, tmp);
+  sprintf (tmp, "%d", result_count);
+  upnp_add_response (event, SERVICE_CDS_DIDL_TOTAL_MATCH, tmp);
+
+  return result_count;
+}
+
+static bool
+cds_search (struct action_event_t *event)
+{
+  extern struct ushare_t *ut;
+  struct upnp_entry_t *entry = NULL;
+  int result_count = 0, index, count, id, sort_criteria;
+  char *search_criteria = NULL;
+  char *filter = NULL;
+  struct buffer_t *out = NULL;
+
+  if (!event)
+    return false;
+
+  /* Check for status */
+  if (!event->status)
+    return false;
+
+  /* check if metadatas have been well inited */
+  if (!ut->init)
+    return false;
+
+  /* Retrieve Browse arguments */
+  index = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_START_INDEX);
+  count = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_REQUEST_COUNT);
+  id = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_OBJECT_ID);
+
+  search_criteria = upnp_get_string (event->request,
+                                     SERVICE_CDS_ARG_SEARCH_CRIT);
+  filter = upnp_get_string (event->request, SERVICE_CDS_ARG_FILTER);
+  sort_criteria = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_SORT_CRIT);
+
+  if (!search_criteria || !filter)
+    return false;
+
+  entry = upnp_get_entry (ut, id);
+
+  if (!entry && (id < ut->starting_id))
+    entry = upnp_get_entry (ut, ut->starting_id);
+
+  if (!entry)
+    return false;
+
+  out = buffer_new ();
+  if (!out)
+    return false;
+
+  result_count =
+    cds_search_directchildren (event, out, index, count, entry,
+                               filter, search_criteria);
+
+  if (result_count < 0)
+  {
+    buffer_free (out);
+    return false;
+  }
+
+  buffer_free (out);
+  upnp_add_response (event, SERVICE_CDS_DIDL_UPDATE_ID,
+                     SERVICE_CDS_ROOT_OBJECT_ID);
+
+  free (search_criteria);
+  free (filter);
+
+  return event->status;
+}
+
+/* List of UPnP ContentDirectory Service actions */
+struct service_action_t cds_service_actions[] = {
+  { SERVICE_CDS_ACTION_SEARCH_CAPS, cds_get_search_capabilities },
+  { SERVICE_CDS_ACTION_SORT_CAPS, cds_get_sort_capabilities },
+  { SERVICE_CDS_ACTION_UPDATE_ID, cds_get_system_update_id },
+  { SERVICE_CDS_ACTION_BROWSE, cds_browse },
+  { SERVICE_CDS_ACTION_SEARCH, cds_search },
+  { NULL, NULL }
+};