[phodav: 7/18] wip: move propfind method



commit af9989a6486c330efd4362a5934889fbfc81c7b3
Author: Marc-André Lureau <marcandre lureau gmail com>
Date:   Thu Apr 10 17:52:12 2014 +0200

    wip: move propfind method

 Makefile.am                        |    2 +
 libphodav/phodav-lock.c            |   59 +++
 libphodav/phodav-lock.h            |    3 +
 libphodav/phodav-method-propfind.c |  746 +++++++++++++++++++++++++++++
 libphodav/phodav-method-propfind.h |   31 ++
 libphodav/phodav-priv.h            |   17 +
 libphodav/phodav-server.c          |  904 ++----------------------------------
 libphodav/phodav-utils.c           |   90 +++-
 libphodav/phodav-utils.h           |   11 +-
 9 files changed, 973 insertions(+), 890 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 5b083ac..2b13f5e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -35,6 +35,8 @@ libphodav_1_0_la_SOURCES =                    \
        libphodav/phodav-lock.h                 \
        libphodav/phodav-method-get.c           \
        libphodav/phodav-method-get.h           \
+       libphodav/phodav-method-propfind.c      \
+       libphodav/phodav-method-propfind.h      \
        libphodav/phodav-multistatus.c          \
        libphodav/phodav-multistatus.h          \
        libphodav/phodav-server.c               \
diff --git a/libphodav/phodav-lock.c b/libphodav/phodav-lock.c
index 21f0214..5fea312 100644
--- a/libphodav/phodav-lock.c
+++ b/libphodav/phodav-lock.c
@@ -15,7 +15,9 @@
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
  */
+
 #include "phodav-lock.h"
+#include "phodav-utils.h"
 #include "phodav-path.h"
 
 void
@@ -65,3 +67,60 @@ dav_lock_free (DAVLock *lock)
 
   g_slice_free (DAVLock, lock);
 }
+
+static const gchar *
+locktype_to_string (LockType type)
+{
+  if (type == LOCK_WRITE)
+    return "write";
+
+  g_return_val_if_reached (NULL);
+}
+
+static const gchar *
+lockscope_to_string (LockScopeType type)
+{
+  if (type == LOCK_SCOPE_EXCLUSIVE)
+    return "exclusive";
+  else if (type == LOCK_SCOPE_SHARED)
+    return "shared";
+
+  g_return_val_if_reached (NULL);
+}
+
+xmlNodePtr
+dav_lock_get_activelock_node (const DAVLock *lock,
+                              xmlNsPtr       ns)
+{
+  xmlNodePtr node, active;
+
+  active = xmlNewNode (ns, BAD_CAST "activelock");
+
+  node = xmlNewChild (active, ns, BAD_CAST "locktype", NULL);
+  xmlNewChild (node, ns, BAD_CAST locktype_to_string (lock->type), NULL);
+  node = xmlNewChild (active, ns, BAD_CAST "lockscope", NULL);
+  xmlNewChild (node, ns, BAD_CAST lockscope_to_string (lock->scope), NULL);
+  node = xmlNewChild (active, ns, BAD_CAST "depth", NULL);
+  xmlAddChild (node, xmlNewText (BAD_CAST depth_to_string (lock->depth)));
+
+  if (lock->owner)
+    xmlAddChild (active, xmlCopyNode (lock->owner, 1));
+
+  node = xmlNewChild (active, ns, BAD_CAST "locktoken", NULL);
+  node = xmlNewChild (node, ns, BAD_CAST "href", NULL);
+  xmlAddChild (node, xmlNewText (BAD_CAST lock->token));
+
+  node = xmlNewChild (active, ns, BAD_CAST "lockroot", NULL);
+  node = xmlNewChild (node, ns, BAD_CAST "href", NULL);
+  xmlAddChild (node, xmlNewText (BAD_CAST lock->path->path));
+  if (lock->timeout)
+    {
+      gchar *tmp = g_strdup_printf ("Second-%" G_GINT64_FORMAT, lock->timeout -
+                                    g_get_monotonic_time () / G_USEC_PER_SEC);
+      node = xmlNewChild (active, ns, BAD_CAST "timeout", NULL);
+      xmlAddChild (node, xmlNewText (BAD_CAST tmp));
+      g_free (tmp);
+    }
+
+  return active;
+}
diff --git a/libphodav/phodav-lock.h b/libphodav/phodav-lock.h
index fb09903..6d9928f 100644
--- a/libphodav/phodav-lock.h
+++ b/libphodav/phodav-lock.h
@@ -31,6 +31,9 @@ void             dav_lock_free            (DAVLock *lock);
 
 void             dav_lock_refresh_timeout (DAVLock *lock, guint timeout);
 
+xmlNodePtr       dav_lock_get_activelock_node (const DAVLock *lock,
+                                               xmlNsPtr       ns);
+
 G_END_DECLS
 
 #endif /* __PHODAV_LOCK_H__ */
diff --git a/libphodav/phodav-method-propfind.c b/libphodav/phodav-method-propfind.c
new file mode 100644
index 0000000..c56977e
--- /dev/null
+++ b/libphodav/phodav-method-propfind.c
@@ -0,0 +1,746 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * This library 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.1 of the License, or (at your option) any later version.
+ *
+ * This library 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; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "phodav-utils.h"
+#include "phodav-multistatus.h"
+#include "phodav-lock.h"
+#include "phodav-method-propfind.h"
+
+typedef struct _PropFind
+{
+  PropFindType type;
+  GHashTable  *props;
+} PropFind;
+
+static PropFind*
+propfind_new (void)
+{
+  PropFind *pf = g_slice_new0 (PropFind);
+
+  // TODO: a better hash for Node (name, ns)
+  pf->props = g_hash_table_new (g_direct_hash, g_direct_equal);
+  return pf;
+}
+
+static void
+propfind_free (PropFind *pf)
+{
+  if (!pf)
+    return;
+
+  g_hash_table_unref (pf->props);
+  g_slice_free (PropFind, pf);
+}
+
+#define PROP_SET_STATUS(Node, Status) G_STMT_START {    \
+    (Node)->_private = GINT_TO_POINTER (Status);        \
+  } G_STMT_END
+
+static xmlNodePtr
+prop_resourcetype (PathHandler *handler, PropFind *pf,
+                   const gchar *path, GFileInfo *info, xmlNsPtr ns)
+{
+  gint status = SOUP_STATUS_OK;
+  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "resourcetype");
+
+  if (pf->type == PROPFIND_PROPNAME)
+    goto end;
+
+  if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+    xmlNewChild (node, ns, BAD_CAST "collection", NULL);
+  else if (!g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR)
+  {
+    g_warn_if_reached ();
+    status = SOUP_STATUS_NOT_FOUND;
+  }
+
+ end:
+  PROP_SET_STATUS (node, status);
+  return node;
+}
+
+static xmlNodePtr
+prop_supportedlock (PathHandler *handler, PropFind *pf,
+                    const gchar *path, GFileInfo *info, xmlNsPtr ns)
+{
+  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "supportedlock");
+
+  if (pf->type == PROPFIND_PROPNAME)
+    goto end;
+
+  {
+    xmlNodePtr entry = xmlNewChild (node, NULL, BAD_CAST "lockentry", NULL);
+    xmlNodePtr scope = xmlNewChild (entry, NULL, BAD_CAST "lockscope", NULL);
+    xmlNewChild (scope, NULL, BAD_CAST "exclusive", NULL);
+    xmlNodePtr type = xmlNewChild (entry, NULL, BAD_CAST "locktype", NULL);
+    xmlNewChild (type, NULL, BAD_CAST "write", NULL);
+  }
+
+  {
+    xmlNodePtr entry = xmlNewChild (node, NULL, BAD_CAST "lockentry", NULL);
+    xmlNodePtr scope = xmlNewChild (entry, NULL, BAD_CAST "lockscope", NULL);
+    xmlNewChild (scope, NULL, BAD_CAST "shared", NULL);
+    xmlNodePtr type = xmlNewChild (entry, NULL, BAD_CAST "locktype", NULL);
+    xmlNewChild (type, NULL, BAD_CAST "write", NULL);
+  }
+
+end:
+  PROP_SET_STATUS (node, SOUP_STATUS_OK);
+  return node;
+}
+
+static gboolean
+lockdiscovery_cb (const gchar *key, Path *path, gpointer data)
+{
+  xmlNodePtr node = data;
+  GList *l;
+
+  g_return_val_if_fail (key, FALSE);
+  g_return_val_if_fail (path, FALSE);
+
+  for (l = path->locks; l != NULL; l = l->next)
+    xmlAddChild (node, dav_lock_get_activelock_node (l->data, NULL));
+
+  return TRUE;
+}
+
+static xmlNodePtr
+prop_lockdiscovery (PathHandler *handler, PropFind *pf,
+                    const gchar *path, GFileInfo *info, xmlNsPtr ns)
+{
+  PhodavServer *server = handler_get_server (handler);
+  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "lockdiscovery");
+
+  if (pf->type == PROPFIND_PROPNAME)
+    goto end;
+
+  server_foreach_parent_path (server, path, lockdiscovery_cb, node);
+
+end:
+  PROP_SET_STATUS (node, SOUP_STATUS_OK);
+  return node;
+}
+
+static void
+node_add_time (xmlNodePtr node, guint64 time, SoupDateFormat format)
+{
+  SoupDate *date;
+  gchar *text;
+
+  g_warn_if_fail (time != 0);
+  date = soup_date_new_from_time_t (time);
+  text = soup_date_to_string (date, format);
+  xmlAddChild (node, xmlNewText (BAD_CAST text));
+  g_free (text);
+  soup_date_free (date);
+}
+
+static xmlNodePtr
+prop_creationdate (PathHandler *handler, PropFind *pf,
+                   const gchar *path, GFileInfo *info, xmlNsPtr ns)
+{
+  gint status = SOUP_STATUS_OK;
+  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "creationdate");
+  guint64 time;
+
+  if (pf->type == PROPFIND_PROPNAME)
+    goto end;
+
+  time = g_file_info_get_attribute_uint64 (info,
+                                           G_FILE_ATTRIBUTE_TIME_CREATED);
+
+  /* windows seems to want this, even apache returns modified time */
+  if (time == 0)
+    time = g_file_info_get_attribute_uint64 (info,
+                                             G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
+  if (time == 0)
+    status = SOUP_STATUS_NOT_FOUND;
+  else
+    node_add_time (node, time, SOUP_DATE_HTTP);
+
+end:
+  PROP_SET_STATUS (node, status);
+  return node;
+}
+
+static xmlNodePtr
+prop_getlastmodified (PathHandler *handler, PropFind *pf,
+                      const gchar *path, GFileInfo *info, xmlNsPtr ns)
+{
+  gint status = SOUP_STATUS_OK;
+  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "getlastmodified");
+  guint64 time;
+
+  if (pf->type == PROPFIND_PROPNAME)
+    goto end;
+
+  time = g_file_info_get_attribute_uint64 (info,
+                                           G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
+  if (time == 0)
+    status = SOUP_STATUS_NOT_FOUND;
+  else
+    node_add_time (node, time, SOUP_DATE_ISO8601);
+
+end:
+  PROP_SET_STATUS (node, status);
+  return node;
+}
+
+static xmlNodePtr
+prop_getcontentlength (PathHandler *handler, PropFind *pf,
+                       const gchar *path, GFileInfo *info, xmlNsPtr ns)
+{
+  gint status = SOUP_STATUS_OK;
+  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "getcontentlength");
+  guint64 size;
+  gchar *text;
+
+  if (pf->type == PROPFIND_PROPNAME)
+    goto end;
+
+  size = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
+  text = g_strdup_printf ("%" G_GUINT64_FORMAT, size);
+  xmlAddChild (node, xmlNewText (BAD_CAST text));
+  g_free (text);
+
+end:
+  PROP_SET_STATUS (node, status);
+  return node;
+}
+
+static xmlNodePtr
+prop_getcontenttype (PathHandler *handler, PropFind *pf,
+                     const gchar *path, GFileInfo *info, xmlNsPtr ns)
+{
+  gint status = SOUP_STATUS_OK;
+  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "getcontenttype");
+  const gchar *type;
+
+  if (pf->type == PROPFIND_PROPNAME)
+    goto end;
+
+  type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
+  if (type == NULL)
+    status = SOUP_STATUS_NOT_FOUND;
+  else
+    xmlAddChild (node, xmlNewText (BAD_CAST type));
+
+end:
+  PROP_SET_STATUS (node, status);
+  return node;
+}
+
+static xmlNodePtr
+prop_displayname (PathHandler *handler, PropFind *pf,
+                  const gchar *path, GFileInfo *info, xmlNsPtr ns)
+{
+  gint status = SOUP_STATUS_OK;
+  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "displayname");
+  const gchar *name;
+
+  if (pf->type == PROPFIND_PROPNAME)
+    goto end;
+
+  name = g_file_info_get_display_name (info);
+  if (name == NULL)
+    status = SOUP_STATUS_NOT_FOUND;
+  else
+    xmlAddChild (node, xmlNewText (BAD_CAST name));
+
+end:
+  PROP_SET_STATUS (node, status);
+  return node;
+}
+
+static xmlNodePtr
+prop_getetag (PathHandler *handler, PropFind *pf,
+              const gchar *path, GFileInfo *info, xmlNsPtr ns)
+{
+  gint status = SOUP_STATUS_OK;
+  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "getetag");
+  const gchar *etag;
+
+  if (pf->type == PROPFIND_PROPNAME)
+    goto end;
+
+  etag = g_file_info_get_etag (info);
+  if (etag)
+    {
+      gchar *tmp = g_strdup_printf ("\"%s\"", etag);
+      xmlAddChild (node, xmlNewText (BAD_CAST tmp));
+      g_free (tmp);
+    }
+  else
+    {
+      status = SOUP_STATUS_NOT_FOUND;
+    }
+
+end:
+  PROP_SET_STATUS (node, status);
+  return node;
+}
+
+static xmlNodePtr
+prop_executable (PathHandler *handler, PropFind *pf,
+                 const gchar *path, GFileInfo *info, xmlNsPtr ns)
+{
+  gint status = SOUP_STATUS_OK;
+  xmlNodePtr node = xmlNewNode (NULL, BAD_CAST "executable");
+  gboolean exec;
+
+  xmlNewNs (node, BAD_CAST "http://apache.org/dav/props/";, NULL);
+
+  if (pf->type == PROPFIND_PROPNAME)
+    goto end;
+
+  exec = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE);
+  if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+    exec = FALSE;
+
+  xmlAddChild (node, xmlNewText (exec ?  BAD_CAST "T" : BAD_CAST "F"));
+
+end:
+  PROP_SET_STATUS (node, status);
+  return node;
+}
+
+static xmlNodePtr
+prop_quota_available (PathHandler *handler, PropFind *pf,
+                      const gchar *path, GFileInfo *_info, xmlNsPtr ns)
+{
+  GFileInfo *info = NULL;
+  gint status = SOUP_STATUS_OK;
+  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "quota-available-bytes");
+  GCancellable *cancellable = handler_get_cancellable(handler);
+  GError *error = NULL;
+  gchar *tmp = NULL;
+  guint64 size;
+
+  if (pf->type == PROPFIND_PROPNAME)
+    goto end;
+
+  info = g_file_query_filesystem_info (handler_get_file (handler), "filesystem::*",
+                                       cancellable, &error);
+  if (error)
+    {
+      g_warning ("Filesystem info error: %s", error->message);
+      status = SOUP_STATUS_INTERNAL_SERVER_ERROR;
+      goto end;
+    }
+
+  size = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
+
+  tmp = g_strdup_printf ("%" G_GUINT64_FORMAT, size);
+  xmlAddChild (node, xmlNewText (BAD_CAST tmp));
+
+end:
+  if (info)
+    g_object_unref (info);
+
+  g_free (tmp);
+  PROP_SET_STATUS (node, status);
+  return node;
+}
+
+#if GLIB_CHECK_VERSION (2, 38, 0)
+static xmlNodePtr
+prop_quota_used (PathHandler *handler, PropFind *pf,
+                 const gchar *path, GFileInfo *info, xmlNsPtr ns)
+{
+  gint status = SOUP_STATUS_OK;
+  GCancellable *cancellable = handler_get_cancellable(handler);
+  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "quota-used-bytes");
+  guint64 disk_usage = 0;
+  GError *error = NULL;
+  gchar *tmp = NULL;
+
+  if (pf->type == PROPFIND_PROPNAME)
+    goto end;
+
+  if (!g_file_measure_disk_usage (handler_get_file (handler),
+                                  G_FILE_MEASURE_NONE,
+                                  cancellable,
+                                  NULL, NULL,
+                                  &disk_usage, NULL, NULL,
+                                  &error))
+    {
+      g_warning ("Filesystem info error: %s", error->message);
+      status = SOUP_STATUS_INTERNAL_SERVER_ERROR;
+      goto end;
+    }
+
+  tmp = g_strdup_printf ("%" G_GUINT64_FORMAT, disk_usage);
+  xmlAddChild (node, xmlNewText (BAD_CAST tmp));
+
+end:
+  g_free (tmp);
+  PROP_SET_STATUS (node, status);
+  return node;
+}
+#endif
+
+static gint
+node_compare_int (xmlNodePtr a,
+                  xmlNodePtr b)
+{
+  return GPOINTER_TO_INT (a->_private) - GPOINTER_TO_INT (b->_private);
+}
+
+static void
+prop_add (GList **stat, xmlNodePtr node)
+{
+  *stat = g_list_insert_sorted (*stat, node, (GCompareFunc) node_compare_int);
+}
+
+#define PROP(Name, Info) { G_STRINGIFY (Name), G_PASTE (prop_, Name), Info }
+static const struct _PropList
+{
+  const gchar *name;
+  xmlNodePtr (*func) (PathHandler *, PropFind *, const gchar *, GFileInfo *, xmlNsPtr);
+  gboolean need_info;
+  gboolean slow;
+
+} prop_list[] = {
+  PROP (resourcetype, 1),
+  PROP (creationdate, 1),
+  PROP (getlastmodified, 1),
+  PROP (getcontentlength, 1),
+  PROP (getcontenttype, 1),
+  PROP (displayname, 1),
+  PROP (getetag, 1),
+  PROP (executable, 1),
+  PROP (supportedlock, 0),
+  PROP (lockdiscovery, 0),
+  { "quota-available-bytes", prop_quota_available, },
+#if GLIB_CHECK_VERSION (2, 38, 0)
+  { "quota-used-bytes", prop_quota_used, FALSE, TRUE, }
+#endif
+};
+
+static xmlNodePtr
+prop_xattr (gchar *xattr)
+{
+  xmlNodePtr node;
+  gchar *ns = xattr + 7;
+  gchar *name = g_utf8_strchr (ns, -1, '#');
+
+  if (name)
+    {
+      *name = '\0';
+      name = name + 1;
+    }
+  else
+    {
+      name = ns;
+      ns = NULL;
+    }
+
+  node = xmlNewNode (NULL, BAD_CAST name);
+  if (ns)
+    xmlNewNs (node, BAD_CAST ns, NULL);
+
+  PROP_SET_STATUS (node, SOUP_STATUS_OK);
+  return node;
+}
+
+#define FILE_QUERY "standard::*,time::*,access::*,etag::*,xattr::*"
+static GList*
+propfind_populate (PathHandler *handler, const gchar *path,
+                   PropFind *pf, GFileInfo *info,
+                   xmlNsPtr ns)
+{
+  GHashTableIter iter;
+  xmlNodePtr node;
+  GList *stat = NULL;
+  int i;
+
+  if (pf->type == PROPFIND_ALLPROP || pf->type == PROPFIND_PROPNAME)
+    {
+      for (i = 0; i < G_N_ELEMENTS (prop_list); i++)
+        {
+          if (pf->type != PROPFIND_PROPNAME)
+            {
+              if (prop_list[i].need_info && !info)
+                continue;
+              if (prop_list[i].slow)
+                continue;
+            }
+
+          /* perhaps not include the 404? */
+          prop_add (&stat, prop_list[i].func (handler, pf, path, info, ns));
+        }
+
+      if (info)
+        {
+          gchar **attrs = g_file_info_list_attributes (info, "xattr");
+
+          for (i = 0; attrs[i]; i++)
+            {
+              node = prop_xattr(attrs[i]);
+              prop_add (&stat, node);
+            }
+
+          g_strfreev (attrs);
+        }
+
+      goto end;
+    }
+
+  g_hash_table_iter_init (&iter, pf->props);
+  while (g_hash_table_iter_next (&iter, (gpointer *) &node, NULL))
+    {
+      for (i = 0; i < G_N_ELEMENTS (prop_list); i++)
+        {
+          if (xml_node_has_name (node, prop_list[i].name)) {
+            node = prop_list[i].func (handler, pf, path, info, ns);
+            break;
+          }
+        }
+
+      if (i == G_N_ELEMENTS (prop_list))
+        {
+          gchar *xattr = xml_node_get_xattr_name (node, "xattr::");
+          node = xmlCopyNode (node, 2);
+          const gchar *val = NULL;
+
+          if (xattr)
+            {
+              val = g_file_info_get_attribute_string (info, xattr);
+              g_free (xattr);
+            }
+
+          if (val)
+            {
+              xmlAddChild (node, xmlNewText (BAD_CAST val));
+              PROP_SET_STATUS (node, SOUP_STATUS_OK);
+            }
+          else
+            {
+              xml_node_debug (node);
+              PROP_SET_STATUS (node, SOUP_STATUS_NOT_FOUND);
+            }
+        }
+
+      prop_add (&stat, node);
+    }
+
+end:
+  return stat;
+}
+
+static gint
+propfind_query_zero (PathHandler *handler, PropFind *pf,
+                     const gchar *path, GHashTable *path_resp,
+                     xmlNsPtr     ns)
+{
+  GCancellable *cancellable = handler_get_cancellable(handler);
+  GError *err = NULL;
+  GFileInfo *info = NULL;
+  GFile *file;
+  GList *stat = NULL;
+  gint status = SOUP_STATUS_OK;
+
+  file = g_file_get_child (handler_get_file (handler), path + 1);
+  info = g_file_query_info (file, FILE_QUERY,
+                            G_FILE_QUERY_INFO_NONE, cancellable, &err);
+  g_object_unref (file);
+  if (err)
+    {
+      if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        g_warning ("queryinfo: %s", err->message);
+      g_clear_error (&err);
+      return SOUP_STATUS_NOT_FOUND;
+    }
+
+  stat = propfind_populate (handler, path, pf, info, ns);
+  g_hash_table_insert (path_resp, g_strdup (path),
+                       response_new (stat, 0));
+  g_clear_object (&info);
+
+  return status;
+}
+
+static gint
+propfind_query_one (PathHandler *handler, PropFind *pf,
+                    const gchar *path, GHashTable *path_resp,
+                    xmlNsPtr     ns)
+{
+  GCancellable *cancellable = handler_get_cancellable(handler);
+  GError *err = NULL;
+  GFile *file;
+  GFileEnumerator *e;
+  gint status;
+
+  status = propfind_query_zero (handler, pf, path, path_resp, ns);
+  if (status != SOUP_STATUS_OK)
+    return status;
+
+  file = g_file_get_child (handler_get_file (handler), path + 1);
+  e = g_file_enumerate_children (file, FILE_QUERY, G_FILE_QUERY_INFO_NONE,
+                                 cancellable, &err);
+  g_object_unref (file);
+  if (!e)
+    goto end;
+
+  while (1)
+    {
+      GList *stat;
+      GFileInfo *info = g_file_enumerator_next_file (e, cancellable, &err);
+      if (!info)
+        break;
+
+      gchar *escape = g_markup_escape_text (g_file_info_get_name (info), -1);
+      stat = propfind_populate (handler, path, pf, info, ns);
+      g_hash_table_insert (path_resp, g_build_path ("/", path, escape, NULL),
+                           response_new (stat, 0));
+      g_free (escape);
+      g_object_unref (info);
+    }
+
+  g_file_enumerator_close (e, cancellable, NULL);
+  g_clear_object (&e);
+
+end:
+  if (err)
+    {
+      if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY))
+        g_warning ("query: %s", err->message);
+      g_clear_error (&err);
+    }
+
+  return status;
+}
+
+static gboolean
+parse_prop (xmlNodePtr node, GHashTable *props)
+{
+  for (node = node->children; node; node = node->next)
+    {
+      if (!xml_node_is_element (node))
+        continue;
+
+      // only interested in ns&name
+      g_hash_table_add (props, node);
+    }
+
+  return TRUE;
+}
+
+static PropFind*
+parse_propfind (xmlNodePtr xml)
+{
+  PropFind *pf = propfind_new ();
+  xmlNodePtr node;
+
+  for (node = xml->children; node; node = node->next)
+    {
+      if (!xml_node_is_element (node))
+        continue;
+
+      if (xml_node_has_name (node, "allprop"))
+        {
+          pf->type = PROPFIND_ALLPROP;
+          goto end;
+        }
+      else if (xml_node_has_name (node, "propname"))
+        {
+          pf->type = PROPFIND_PROPNAME;
+          goto end;
+        }
+      else if (xml_node_has_name (node, "prop"))
+        {
+          pf->type = PROPFIND_PROP;
+          parse_prop (node, pf->props);
+          goto end;
+        }
+    }
+
+  g_warn_if_reached ();
+  g_clear_pointer (&pf, propfind_free);
+
+end:
+  return pf;
+}
+
+gint
+phodav_method_propfind (PathHandler *handler, SoupMessage *msg,
+                        const char *path, GError **err)
+{
+  PropFind *pf = NULL;
+  DepthType depth;
+  GHashTable *mstatus = NULL;   // path -> statlist
+  DavDoc doc = {0, };
+  gint status = SOUP_STATUS_NOT_FOUND;
+  xmlNsPtr ns = NULL;
+
+  depth = depth_from_string (soup_message_headers_get_one (msg->request_headers, "Depth"));
+  if (!msg->request_body || !msg->request_body->length)
+    {
+      /* Win kludge: http://code.google.com/p/sabredav/wiki/Windows */
+      pf = propfind_new ();
+      pf->type = PROPFIND_ALLPROP;
+    }
+  else
+    {
+      if (!davdoc_parse (&doc, msg, msg->request_body, "propfind"))
+        {
+          status = SOUP_STATUS_BAD_REQUEST;
+          goto end;
+        }
+
+      pf = parse_propfind (doc.root);
+      if (!pf)
+        goto end;
+    }
+
+  ns = xmlNewNs (NULL, BAD_CAST "DAV:", BAD_CAST "D");
+  mstatus = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+                                   (GDestroyNotify) response_free);
+  if (pf->type == PROPFIND_PROP ||
+      pf->type == PROPFIND_ALLPROP ||
+      pf->type == PROPFIND_PROPNAME)
+    {
+      if (depth == DEPTH_ZERO)
+        status = propfind_query_zero (handler, pf, path, mstatus, ns);
+      else if (depth == DEPTH_ONE)
+        status = propfind_query_one (handler, pf, path, mstatus, ns);
+      else
+        {
+          status = SOUP_STATUS_FORBIDDEN;
+          g_warn_if_reached ();
+        }
+    }
+  else
+    g_warn_if_reached ();
+
+  if (status != SOUP_STATUS_OK)
+    goto end;
+
+  status = set_response_multistatus (msg, mstatus);
+
+end:
+  davdoc_free (&doc);
+  propfind_free (pf);
+  if (mstatus)
+    g_hash_table_unref (mstatus);
+  if (ns)
+    xmlFreeNs(ns);
+  return status;
+}
diff --git a/libphodav/phodav-method-propfind.h b/libphodav/phodav-method-propfind.h
new file mode 100644
index 0000000..9d6c10c
--- /dev/null
+++ b/libphodav/phodav-method-propfind.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * This library 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.1 of the License, or (at your option) any later version.
+ *
+ * This library 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; if not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __PHODAV_METHOD_PROPFIND_H__
+#define __PHODAV_METHOD_PROPFIND_H__
+
+#include <libsoup/soup.h>
+#include "phodav-priv.h"
+
+G_BEGIN_DECLS
+
+gint                  phodav_method_propfind    (PathHandler *handler, SoupMessage *msg,
+                                                 const char *path, GError **err);
+
+G_END_DECLS
+
+#endif /* __PHODAV_METHOD_PROPFIND_H__ */
diff --git a/libphodav/phodav-priv.h b/libphodav/phodav-priv.h
index aa7d598..7ee92cb 100644
--- a/libphodav/phodav-priv.h
+++ b/libphodav/phodav-priv.h
@@ -28,6 +28,7 @@
 #include <libxml/tree.h>
 #include <libxml/xpath.h>
 #include <libxml/xpathInternals.h>
+#include "phodav-server.h"
 
 G_BEGIN_DECLS
 
@@ -52,6 +53,12 @@ typedef enum _DepthType {
   DEPTH_INFINITY
 } DepthType;
 
+typedef enum _PropFindType {
+  PROPFIND_ALLPROP,
+  PROPFIND_PROPNAME,
+  PROPFIND_PROP
+} PropFindType;
+
 struct _DAVLock
 {
   Path         *path;
@@ -70,8 +77,18 @@ struct _Path
   guint32        refs;
 };
 
+typedef gboolean (* PathCb) (const gchar *key,
+                             Path        *path,
+                             gpointer     data);
+
+
 GFile *                 handler_get_file                     (PathHandler *handler);
 GCancellable *          handler_get_cancellable              (PathHandler *handler);
+PhodavServer *          handler_get_server                   (PathHandler *handler);
+
+gboolean                server_foreach_parent_path           (PhodavServer *server,
+                                                              const gchar *path,
+                                                              PathCb cb, gpointer data);
 
 G_END_DECLS
 
diff --git a/libphodav/phodav-server.c b/libphodav/phodav-server.c
index 83c665b..27d0249 100644
--- a/libphodav/phodav-server.c
+++ b/libphodav/phodav-server.c
@@ -29,7 +29,9 @@
 #include "phodav-path.h"
 #include "phodav-lock.h"
 #include "phodav-utils.h"
+
 #include "phodav-method-get.h"
+#include "phodav-method-propfind.h"
 
 /**
  * SECTION:phodav-server
@@ -118,6 +120,12 @@ struct _PathHandler
   GFile       *file;
 };
 
+PhodavServer * G_GNUC_PURE
+handler_get_server (PathHandler *handler)
+{
+  return handler->self;
+}
+
 GFile * G_GNUC_PURE
 handler_get_file (PathHandler *handler)
 {
@@ -304,60 +312,13 @@ phodav_server_class_init (PhodavServerClass *klass)
                           G_PARAM_STATIC_STRINGS));
 }
 
-static void
-node_debug (xmlNodePtr node)
-{
-  g_debug ("%s ns:%s", node->name, node->ns ? (gchar *) node->ns->href : "");
-}
-
-static gboolean
-node_has_ns (xmlNodePtr node, const char *ns_href)
-{
-  return node->ns && node->ns->href &&
-         !g_strcmp0 ((gchar *) node->ns->href, ns_href);
-
-}
-
-static gboolean
-node_has_name_ns (xmlNodePtr node, const char *name, const char *ns_href)
-{
-  gboolean has_name;
-  gboolean has_ns;
-
-  g_return_val_if_fail (node != NULL, FALSE);
-
-  has_name = has_ns = TRUE;
-
-  if (name)
-    has_name = !g_strcmp0 ((gchar *) node->name, name);
-
-  if (ns_href)
-    has_ns = node_has_ns (node, ns_href);
-
-  return has_name && has_ns;
-}
-
-static gboolean
-node_has_name (xmlNodePtr node, const char *name)
-{
-  g_return_val_if_fail (node != NULL, FALSE);
-
-  return node_has_name_ns (node, name, "DAV:");
-}
-
-static gboolean
-node_is_element (xmlNodePtr node)
-{
-  return node->type == XML_ELEMENT_NODE && node->name != NULL;
-}
-
 static LockScopeType
 parse_lockscope (xmlNodePtr rt)
 {
   xmlNodePtr node;
 
   for (node = rt->children; node; node = node->next)
-    if (node_is_element (node))
+    if (xml_node_is_element (node))
       break;
 
   if (node == NULL)
@@ -371,24 +332,13 @@ parse_lockscope (xmlNodePtr rt)
     return LOCK_SCOPE_NONE;
 }
 
-static const gchar *
-lockscope_to_string (LockScopeType type)
-{
-  if (type == LOCK_SCOPE_EXCLUSIVE)
-    return "exclusive";
-  else if (type == LOCK_SCOPE_SHARED)
-    return "shared";
-
-  g_return_val_if_reached (NULL);
-}
-
 static LockType
 parse_locktype (xmlNodePtr rt)
 {
   xmlNodePtr node;
 
   for (node = rt->children; node; node = node->next)
-    if (node_is_element (node))
+    if (xml_node_is_element (node))
       break;
 
   if (node == NULL)
@@ -400,204 +350,8 @@ parse_locktype (xmlNodePtr rt)
     return LOCK_NONE;
 }
 
-static const gchar *
-locktype_to_string (LockType type)
-{
-  if (type == LOCK_WRITE)
-    return "write";
-
-  g_return_val_if_reached (NULL);
-}
-
-typedef enum _PropFindType {
-  PROPFIND_ALLPROP,
-  PROPFIND_PROPNAME,
-  PROPFIND_PROP
-} PropFindType;
-
-typedef struct _PropFind
-{
-  PropFindType type;
-  GHashTable  *props;
-} PropFind;
-
-static PropFind*
-propfind_new (void)
-{
-  PropFind *pf = g_slice_new0 (PropFind);
-
-  // TODO: a better hash for Node (name, ns)
-  pf->props = g_hash_table_new (g_direct_hash, g_direct_equal);
-  return pf;
-}
-
-static void
-propfind_free (PropFind *pf)
-{
-  if (!pf)
-    return;
-
-  g_hash_table_unref (pf->props);
-  g_slice_free (PropFind, pf);
-}
-
-static gboolean
-parse_prop (xmlNodePtr node, GHashTable *props)
-{
-  for (node = node->children; node; node = node->next)
-    {
-      if (!node_is_element (node))
-        continue;
-
-      // only interested in ns&name
-      g_hash_table_add (props, node);
-    }
-
-  return TRUE;
-}
-
-static PropFind*
-parse_propfind (xmlNodePtr xml)
-{
-  PropFind *pf = propfind_new ();
-  xmlNodePtr node;
-
-  for (node = xml->children; node; node = node->next)
-    {
-      if (!node_is_element (node))
-        continue;
-
-      if (node_has_name (node, "allprop"))
-        {
-          pf->type = PROPFIND_ALLPROP;
-          goto end;
-        }
-      else if (node_has_name (node, "propname"))
-        {
-          pf->type = PROPFIND_PROPNAME;
-          goto end;
-        }
-      else if (node_has_name (node, "prop"))
-        {
-          pf->type = PROPFIND_PROP;
-          parse_prop (node, pf->props);
-          goto end;
-        }
-    }
-
-  g_warn_if_reached ();
-  g_clear_pointer (&pf, propfind_free);
-
-end:
-  return pf;
-}
-
-#define PROP_SET_STATUS(Node, Status) G_STMT_START {     \
-    (Node)->_private = GINT_TO_POINTER (Status);        \
-} G_STMT_END
-
-static xmlNodePtr
-prop_resourcetype (PathHandler *handler, PropFind *pf,
-                   const gchar *path, GFileInfo *info, xmlNsPtr ns)
-{
-  gint status = SOUP_STATUS_OK;
-  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "resourcetype");
-
-  if (pf->type == PROPFIND_PROPNAME)
-    goto end;
-
-  if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
-    xmlNewChild (node, ns, BAD_CAST "collection", NULL);
-  else if (!g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR)
-    {
-      g_warn_if_reached ();
-      status = SOUP_STATUS_NOT_FOUND;
-    }
-
-end:
-  PROP_SET_STATUS (node, status);
-  return node;
-}
-
-static gint
-node_compare_int (xmlNodePtr a,
-                  xmlNodePtr b)
-{
-  return GPOINTER_TO_INT (a->_private) - GPOINTER_TO_INT (b->_private);
-}
-
-static void
-node_add_time (xmlNodePtr node, guint64 time, SoupDateFormat format)
-{
-  SoupDate *date;
-  gchar *text;
-
-  g_warn_if_fail (time != 0);
-  date = soup_date_new_from_time_t (time);
-  text = soup_date_to_string (date, format);
-  xmlAddChild (node, xmlNewText (BAD_CAST text));
-  g_free (text);
-  soup_date_free (date);
-}
-
-static gchar *
-node_get_xattr_name (xmlNodePtr node, const gchar *prefix)
-{
-  const gchar *ns = node->ns ? (gchar *) node->ns->href : NULL;
-  const gchar *name = (gchar *) node->name;
-
-  if (!name)
-    return NULL;
-
-  if (ns)
-    return g_strdup_printf ("%s%s#%s", prefix, ns, name);
-  else
-    return g_strdup_printf ("%s%s", prefix, name);
-}
-
-static xmlNodePtr
-get_activelock_node (const DAVLock *lock,
-                     xmlNsPtr       ns)
-{
-  xmlNodePtr node, active;
-
-  active = xmlNewNode (ns, BAD_CAST "activelock");
-
-  node = xmlNewChild (active, ns, BAD_CAST "locktype", NULL);
-  xmlNewChild (node, ns, BAD_CAST locktype_to_string (lock->type), NULL);
-  node = xmlNewChild (active, ns, BAD_CAST "lockscope", NULL);
-  xmlNewChild (node, ns, BAD_CAST lockscope_to_string (lock->scope), NULL);
-  node = xmlNewChild (active, ns, BAD_CAST "depth", NULL);
-  xmlAddChild (node, xmlNewText (BAD_CAST depth_to_string (lock->depth)));
-
-  if (lock->owner)
-    xmlAddChild (active, xmlCopyNode (lock->owner, 1));
-
-  node = xmlNewChild (active, ns, BAD_CAST "locktoken", NULL);
-  node = xmlNewChild (node, ns, BAD_CAST "href", NULL);
-  xmlAddChild (node, xmlNewText (BAD_CAST lock->token));
-
-  node = xmlNewChild (active, ns, BAD_CAST "lockroot", NULL);
-  node = xmlNewChild (node, ns, BAD_CAST "href", NULL);
-  xmlAddChild (node, xmlNewText (BAD_CAST lock->path->path));
-  if (lock->timeout)
-    {
-      gchar *tmp = g_strdup_printf ("Second-%" G_GINT64_FORMAT, lock->timeout -
-                                    g_get_monotonic_time () / G_USEC_PER_SEC);
-      node = xmlNewChild (active, ns, BAD_CAST "timeout", NULL);
-      xmlAddChild (node, xmlNewText (BAD_CAST tmp));
-      g_free (tmp);
-    }
-
-  return active;
-}
-
-typedef gboolean (* PathCb) (const gchar *key,
-                             Path        *path,
-                             gpointer     data);
-
-static gboolean
-foreach_parent_path (PhodavServer *self, const gchar *path, PathCb cb, gpointer data)
+gboolean
+server_foreach_parent_path (PhodavServer *self, const gchar *path, PathCb cb, gpointer data)
 {
   gchar **pathv, *partial = g_strdup ("/");
   gboolean ret = FALSE;
@@ -663,8 +417,8 @@ static DAVLock *
 path_get_lock (PhodavServer *self, const gchar *path, const gchar *token)
 {
   PathGetLock p = { .token = token };
-  gboolean success = !foreach_parent_path (self, path,
-                                           _path_get_lock, (gpointer) &p);
+  gboolean success = !server_foreach_parent_path (self, path,
+                                                  _path_get_lock, (gpointer) &p);
 
   if (!success)
     g_message ("Invalid lock token %s for %s", token, path);
@@ -672,604 +426,6 @@ path_get_lock (PhodavServer *self, const gchar *path, const gchar *token)
   return p.lock;
 }
 
-static xmlNodePtr
-prop_supportedlock (PathHandler *handler, PropFind *pf,
-                    const gchar *path, GFileInfo *info, xmlNsPtr ns)
-{
-  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "supportedlock");
-
-  if (pf->type == PROPFIND_PROPNAME)
-    goto end;
-
-  {
-    xmlNodePtr entry = xmlNewChild (node, NULL, BAD_CAST "lockentry", NULL);
-    xmlNodePtr scope = xmlNewChild (entry, NULL, BAD_CAST "lockscope", NULL);
-    xmlNewChild (scope, NULL, BAD_CAST "exclusive", NULL);
-    xmlNodePtr type = xmlNewChild (entry, NULL, BAD_CAST "locktype", NULL);
-    xmlNewChild (type, NULL, BAD_CAST "write", NULL);
-  }
-
-  {
-    xmlNodePtr entry = xmlNewChild (node, NULL, BAD_CAST "lockentry", NULL);
-    xmlNodePtr scope = xmlNewChild (entry, NULL, BAD_CAST "lockscope", NULL);
-    xmlNewChild (scope, NULL, BAD_CAST "shared", NULL);
-    xmlNodePtr type = xmlNewChild (entry, NULL, BAD_CAST "locktype", NULL);
-    xmlNewChild (type, NULL, BAD_CAST "write", NULL);
-  }
-
-end:
-  PROP_SET_STATUS (node, SOUP_STATUS_OK);
-  return node;
-}
-
-static gboolean
-lockdiscovery_cb (const gchar *key, Path *path, gpointer data)
-{
-  xmlNodePtr node = data;
-  GList *l;
-
-  g_return_val_if_fail (key, FALSE);
-  g_return_val_if_fail (path, FALSE);
-
-  for (l = path->locks; l != NULL; l = l->next)
-    xmlAddChild (node, get_activelock_node (l->data, NULL));
-
-  return TRUE;
-}
-
-static xmlNodePtr
-prop_lockdiscovery (PathHandler *handler, PropFind *pf,
-                    const gchar *path, GFileInfo *info, xmlNsPtr ns)
-{
-  PhodavServer *self = handler->self;
-  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "lockdiscovery");
-
-  if (pf->type == PROPFIND_PROPNAME)
-    goto end;
-
-  foreach_parent_path (self, path, lockdiscovery_cb, node);
-
-end:
-  PROP_SET_STATUS (node, SOUP_STATUS_OK);
-  return node;
-}
-
-static xmlNodePtr
-prop_creationdate (PathHandler *handler, PropFind *pf,
-                   const gchar *path, GFileInfo *info, xmlNsPtr ns)
-{
-  gint status = SOUP_STATUS_OK;
-  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "creationdate");
-  guint64 time;
-
-  if (pf->type == PROPFIND_PROPNAME)
-    goto end;
-
-  time = g_file_info_get_attribute_uint64 (info,
-                                           G_FILE_ATTRIBUTE_TIME_CREATED);
-
-  /* windows seems to want this, even apache returns modified time */
-  if (time == 0)
-    time = g_file_info_get_attribute_uint64 (info,
-                                             G_FILE_ATTRIBUTE_TIME_MODIFIED);
-
-  if (time == 0)
-    status = SOUP_STATUS_NOT_FOUND;
-  else
-    node_add_time (node, time, SOUP_DATE_HTTP);
-
-end:
-  PROP_SET_STATUS (node, status);
-  return node;
-}
-
-static xmlNodePtr
-prop_getlastmodified (PathHandler *handler, PropFind *pf,
-                      const gchar *path, GFileInfo *info, xmlNsPtr ns)
-{
-  gint status = SOUP_STATUS_OK;
-  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "getlastmodified");
-  guint64 time;
-
-  if (pf->type == PROPFIND_PROPNAME)
-    goto end;
-
-  time = g_file_info_get_attribute_uint64 (info,
-                                           G_FILE_ATTRIBUTE_TIME_MODIFIED);
-
-  if (time == 0)
-    status = SOUP_STATUS_NOT_FOUND;
-  else
-    node_add_time (node, time, SOUP_DATE_ISO8601);
-
-end:
-  PROP_SET_STATUS (node, status);
-  return node;
-}
-
-static xmlNodePtr
-prop_getcontentlength (PathHandler *handler, PropFind *pf,
-                       const gchar *path, GFileInfo *info, xmlNsPtr ns)
-{
-  gint status = SOUP_STATUS_OK;
-  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "getcontentlength");
-  guint64 size;
-  gchar *text;
-
-  if (pf->type == PROPFIND_PROPNAME)
-    goto end;
-
-  size = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
-  text = g_strdup_printf ("%" G_GUINT64_FORMAT, size);
-  xmlAddChild (node, xmlNewText (BAD_CAST text));
-  g_free (text);
-
-end:
-  PROP_SET_STATUS (node, status);
-  return node;
-}
-
-static xmlNodePtr
-prop_getcontenttype (PathHandler *handler, PropFind *pf,
-                     const gchar *path, GFileInfo *info, xmlNsPtr ns)
-{
-  gint status = SOUP_STATUS_OK;
-  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "getcontenttype");
-  const gchar *type;
-
-  if (pf->type == PROPFIND_PROPNAME)
-    goto end;
-
-  type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
-  if (type == NULL)
-    status = SOUP_STATUS_NOT_FOUND;
-  else
-    xmlAddChild (node, xmlNewText (BAD_CAST type));
-
-end:
-  PROP_SET_STATUS (node, status);
-  return node;
-}
-
-static xmlNodePtr
-prop_displayname (PathHandler *handler, PropFind *pf,
-                  const gchar *path, GFileInfo *info, xmlNsPtr ns)
-{
-  gint status = SOUP_STATUS_OK;
-  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "displayname");
-  const gchar *name;
-
-  if (pf->type == PROPFIND_PROPNAME)
-    goto end;
-
-  name = g_file_info_get_display_name (info);
-  if (name == NULL)
-    status = SOUP_STATUS_NOT_FOUND;
-  else
-    xmlAddChild (node, xmlNewText (BAD_CAST name));
-
-end:
-  PROP_SET_STATUS (node, status);
-  return node;
-}
-
-static xmlNodePtr
-prop_getetag (PathHandler *handler, PropFind *pf,
-              const gchar *path, GFileInfo *info, xmlNsPtr ns)
-{
-  gint status = SOUP_STATUS_OK;
-  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "getetag");
-  const gchar *etag;
-
-  if (pf->type == PROPFIND_PROPNAME)
-    goto end;
-
-  etag = g_file_info_get_etag (info);
-  if (etag)
-    {
-      gchar *tmp = g_strdup_printf ("\"%s\"", etag);
-      xmlAddChild (node, xmlNewText (BAD_CAST tmp));
-      g_free (tmp);
-    }
-  else
-    {
-      status = SOUP_STATUS_NOT_FOUND;
-    }
-
-end:
-  PROP_SET_STATUS (node, status);
-  return node;
-}
-
-static xmlNodePtr
-prop_executable (PathHandler *handler, PropFind *pf,
-                 const gchar *path, GFileInfo *info, xmlNsPtr ns)
-{
-  gint status = SOUP_STATUS_OK;
-  xmlNodePtr node = xmlNewNode (NULL, BAD_CAST "executable");
-  gboolean exec;
-
-  xmlNewNs (node, BAD_CAST "http://apache.org/dav/props/";, NULL);
-
-  if (pf->type == PROPFIND_PROPNAME)
-    goto end;
-
-  exec = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE);
-  if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
-    exec = FALSE;
-
-  xmlAddChild (node, xmlNewText (exec ?  BAD_CAST "T" : BAD_CAST "F"));
-
-end:
-  PROP_SET_STATUS (node, status);
-  return node;
-}
-
-static xmlNodePtr
-prop_quota_available (PathHandler *handler, PropFind *pf,
-                      const gchar *path, GFileInfo *_info, xmlNsPtr ns)
-{
-  GFileInfo *info = NULL;
-  gint status = SOUP_STATUS_OK;
-  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "quota-available-bytes");
-  PhodavServer *self = handler->self;
-  GError *error = NULL;
-  gchar *tmp = NULL;
-  guint64 size;
-
-  if (pf->type == PROPFIND_PROPNAME)
-    goto end;
-
-  info = g_file_query_filesystem_info (handler->file, "filesystem::*",
-                                       self->cancellable, &error);
-  if (error)
-    {
-      g_warning ("Filesystem info error: %s", error->message);
-      status = SOUP_STATUS_INTERNAL_SERVER_ERROR;
-      goto end;
-    }
-
-  size = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
-
-  tmp = g_strdup_printf ("%" G_GUINT64_FORMAT, size);
-  xmlAddChild (node, xmlNewText (BAD_CAST tmp));
-
-end:
-  if (info)
-    g_object_unref (info);
-
-  g_free (tmp);
-  PROP_SET_STATUS (node, status);
-  return node;
-}
-
-#if GLIB_CHECK_VERSION (2, 38, 0)
-static xmlNodePtr
-prop_quota_used (PathHandler *handler, PropFind *pf,
-                 const gchar *path, GFileInfo *info, xmlNsPtr ns)
-{
-  gint status = SOUP_STATUS_OK;
-  PhodavServer *self = handler->self;
-  xmlNodePtr node = xmlNewNode (ns, BAD_CAST "quota-used-bytes");
-  guint64 disk_usage = 0;
-  GError *error = NULL;
-  gchar *tmp = NULL;
-
-  if (pf->type == PROPFIND_PROPNAME)
-    goto end;
-
-  if (!g_file_measure_disk_usage (handler->file,
-                                  G_FILE_MEASURE_NONE,
-                                  self->cancellable,
-                                  NULL, NULL,
-                                  &disk_usage, NULL, NULL,
-                                  &error))
-    {
-      g_warning ("Filesystem info error: %s", error->message);
-      status = SOUP_STATUS_INTERNAL_SERVER_ERROR;
-      goto end;
-    }
-
-  tmp = g_strdup_printf ("%" G_GUINT64_FORMAT, disk_usage);
-  xmlAddChild (node, xmlNewText (BAD_CAST tmp));
-
-end:
-  g_free (tmp);
-  PROP_SET_STATUS (node, status);
-  return node;
-}
-#endif
-
-static void
-prop_add (GList **stat, xmlNodePtr node)
-{
-  *stat = g_list_insert_sorted (*stat, node, (GCompareFunc) node_compare_int);
-}
-
-#define PROP(Name, Info) { G_STRINGIFY (Name), G_PASTE (prop_, Name), Info }
-static const struct _PropList
-{
-  const gchar *name;
-  xmlNodePtr (*func) (PathHandler *, PropFind *, const gchar *, GFileInfo *, xmlNsPtr);
-  gboolean need_info;
-  gboolean slow;
-
-} prop_list[] = {
-  PROP (resourcetype, 1),
-  PROP (creationdate, 1),
-  PROP (getlastmodified, 1),
-  PROP (getcontentlength, 1),
-  PROP (getcontenttype, 1),
-  PROP (displayname, 1),
-  PROP (getetag, 1),
-  PROP (executable, 1),
-  PROP (supportedlock, 0),
-  PROP (lockdiscovery, 0),
-  { "quota-available-bytes", prop_quota_available, },
-#if GLIB_CHECK_VERSION (2, 38, 0)
-  { "quota-used-bytes", prop_quota_used, FALSE, TRUE, }
-#endif
-};
-
-static xmlNodePtr
-prop_xattr (gchar *xattr)
-{
-  xmlNodePtr node;
-  gchar *ns = xattr + 7;
-  gchar *name = g_utf8_strchr (ns, -1, '#');
-
-  if (name)
-    {
-      *name = '\0';
-      name = name + 1;
-    }
-  else
-    {
-      name = ns;
-      ns = NULL;
-    }
-
-  node = xmlNewNode (NULL, BAD_CAST name);
-  if (ns)
-    xmlNewNs (node, BAD_CAST ns, NULL);
-
-  PROP_SET_STATUS (node, SOUP_STATUS_OK);
-  return node;
-}
-
-#define FILE_QUERY "standard::*,time::*,access::*,etag::*,xattr::*"
-static GList*
-propfind_populate (PathHandler *handler, const gchar *path,
-                   PropFind *pf, GFileInfo *info,
-                   xmlNsPtr ns)
-{
-  GHashTableIter iter;
-  xmlNodePtr node;
-  GList *stat = NULL;
-  int i;
-
-  if (pf->type == PROPFIND_ALLPROP || pf->type == PROPFIND_PROPNAME)
-    {
-      for (i = 0; i < G_N_ELEMENTS (prop_list); i++)
-        {
-          if (pf->type != PROPFIND_PROPNAME)
-            {
-              if (prop_list[i].need_info && !info)
-                continue;
-              if (prop_list[i].slow)
-                continue;
-            }
-
-          /* perhaps not include the 404? */
-          prop_add (&stat, prop_list[i].func (handler, pf, path, info, ns));
-        }
-
-      if (info)
-        {
-          gchar **attrs = g_file_info_list_attributes (info, "xattr");
-
-          for (i = 0; attrs[i]; i++)
-            {
-              node = prop_xattr(attrs[i]);
-              prop_add (&stat, node);
-            }
-
-          g_strfreev (attrs);
-        }
-
-      goto end;
-    }
-
-  g_hash_table_iter_init (&iter, pf->props);
-  while (g_hash_table_iter_next (&iter, (gpointer *) &node, NULL))
-    {
-      for (i = 0; i < G_N_ELEMENTS (prop_list); i++)
-        {
-          if (node_has_name (node, prop_list[i].name)) {
-            node = prop_list[i].func (handler, pf, path, info, ns);
-            break;
-          }
-        }
-
-      if (i == G_N_ELEMENTS (prop_list))
-        {
-          gchar *xattr = node_get_xattr_name (node, "xattr::");
-          node = xmlCopyNode (node, 2);
-          const gchar *val = NULL;
-
-          if (xattr)
-            {
-              val = g_file_info_get_attribute_string (info, xattr);
-              g_free (xattr);
-            }
-
-          if (val)
-            {
-              xmlAddChild (node, xmlNewText (BAD_CAST val));
-              PROP_SET_STATUS (node, SOUP_STATUS_OK);
-            }
-          else
-            {
-              node_debug (node);
-              PROP_SET_STATUS (node, SOUP_STATUS_NOT_FOUND);
-            }
-        }
-
-      prop_add (&stat, node);
-    }
-
-end:
-  return stat;
-}
-
-static gint
-propfind_query_zero (PathHandler *handler, PropFind *pf,
-                     const gchar *path, GHashTable *path_resp,
-                     xmlNsPtr     ns)
-{
-  PhodavServer *self = handler->self;
-  GError *err = NULL;
-  GFileInfo *info = NULL;
-  GFile *file;
-  GList *stat = NULL;
-  gint status = SOUP_STATUS_OK;
-
-  file = g_file_get_child (handler->file, path + 1);
-  info = g_file_query_info (file, FILE_QUERY,
-                            G_FILE_QUERY_INFO_NONE, self->cancellable, &err);
-  g_object_unref (file);
-  if (err)
-    {
-      if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
-        g_warning ("queryinfo: %s", err->message);
-      g_clear_error (&err);
-      return SOUP_STATUS_NOT_FOUND;
-    }
-
-  stat = propfind_populate (handler, path, pf, info, ns);
-  g_hash_table_insert (path_resp, g_strdup (path),
-                       response_new (stat, 0));
-  g_clear_object (&info);
-
-  return status;
-}
-
-static gint
-propfind_query_one (PathHandler *handler, PropFind *pf,
-                    const gchar *path, GHashTable *path_resp,
-                    xmlNsPtr     ns)
-{
-  PhodavServer *self = handler->self;
-  GError *err = NULL;
-  GFile *file;
-  GFileEnumerator *e;
-  gint status;
-
-  status = propfind_query_zero (handler, pf, path, path_resp, ns);
-  if (status != SOUP_STATUS_OK)
-    return status;
-
-  file = g_file_get_child (handler->file, path + 1);
-  e = g_file_enumerate_children (file, FILE_QUERY, G_FILE_QUERY_INFO_NONE,
-                                 self->cancellable, &err);
-  g_object_unref (file);
-  if (!e)
-    goto end;
-
-  while (1)
-    {
-      GList *stat;
-      GFileInfo *info = g_file_enumerator_next_file (e, self->cancellable, &err);
-      if (!info)
-        break;
-
-      gchar *escape = g_markup_escape_text (g_file_info_get_name (info), -1);
-      stat = propfind_populate (handler, path, pf, info, ns);
-      g_hash_table_insert (path_resp, g_build_path ("/", path, escape, NULL),
-                           response_new (stat, 0));
-      g_free (escape);
-      g_object_unref (info);
-    }
-
-  g_file_enumerator_close (e, self->cancellable, NULL);
-  g_clear_object (&e);
-
-end:
-  if (err)
-    {
-      if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY))
-        g_warning ("query: %s", err->message);
-      g_clear_error (&err);
-    }
-
-  return status;
-}
-
-static gint
-method_propfind (PathHandler *handler, SoupMessage *msg,
-                 const char *path, GError **err)
-{
-  PropFind *pf = NULL;
-  DepthType depth;
-  GHashTable *mstatus = NULL;   // path -> statlist
-  DavDoc doc = {0, };
-  gint status = SOUP_STATUS_NOT_FOUND;
-  xmlNsPtr ns = NULL;
-
-  depth = depth_from_string (soup_message_headers_get_one (msg->request_headers, "Depth"));
-  if (!msg->request_body || !msg->request_body->length)
-    {
-      /* Win kludge: http://code.google.com/p/sabredav/wiki/Windows */
-      pf = propfind_new ();
-      pf->type = PROPFIND_ALLPROP;
-    }
-  else
-    {
-      if (!davdoc_parse (&doc, msg, msg->request_body, "propfind"))
-        {
-          status = SOUP_STATUS_BAD_REQUEST;
-          goto end;
-        }
-
-      pf = parse_propfind (doc.root);
-      if (!pf)
-        goto end;
-    }
-
-  ns = xmlNewNs (NULL, BAD_CAST "DAV:", BAD_CAST "D");
-  mstatus = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
-                                   (GDestroyNotify) response_free);
-  if (pf->type == PROPFIND_PROP ||
-      pf->type == PROPFIND_ALLPROP ||
-      pf->type == PROPFIND_PROPNAME)
-    {
-      if (depth == DEPTH_ZERO)
-        status = propfind_query_zero (handler, pf, path, mstatus, ns);
-      else if (depth == DEPTH_ONE)
-        status = propfind_query_one (handler, pf, path, mstatus, ns);
-      else
-        {
-          status = SOUP_STATUS_FORBIDDEN;
-          g_warn_if_reached ();
-        }
-    }
-  else
-    g_warn_if_reached ();
-
-  if (status != SOUP_STATUS_OK)
-    goto end;
-
-  status = set_response_multistatus (msg, mstatus);
-
-end:
-  davdoc_free (&doc);
-  propfind_free (pf);
-  if (mstatus)
-    g_hash_table_unref (mstatus);
-  if (ns)
-    xmlFreeNs(ns);
-  return status;
-}
 
 static gint
 set_attr (PhodavServer *self, GFile *file, xmlNodePtr attrnode,
@@ -1281,7 +437,7 @@ set_attr (PhodavServer *self, GFile *file, xmlNodePtr attrnode,
 
   if (type == G_FILE_ATTRIBUTE_TYPE_INVALID)
     {
-      attrname = node_get_xattr_name (attrnode, "user.");
+      attrname = xml_node_get_xattr_name (attrnode, "user.");
       g_return_val_if_fail (attrname, SOUP_STATUS_BAD_REQUEST);
 
       /* https://bugzilla.gnome.org/show_bug.cgi?id=720506 */
@@ -1295,7 +451,7 @@ set_attr (PhodavServer *self, GFile *file, xmlNodePtr attrnode,
     }
   else
     {
-      attrname = node_get_xattr_name (attrnode, "xattr::");
+      attrname = xml_node_get_xattr_name (attrnode, "xattr::");
       g_return_val_if_fail (attrname, SOUP_STATUS_BAD_REQUEST);
 
       g_file_set_attribute (file, attrname, type, mem,
@@ -1336,15 +492,15 @@ prop_set (PhodavServer *self, SoupMessage *msg,
 
   for (node = parent->children; node; node = node->next)
     {
-      if (!node_is_element (node))
+      if (!xml_node_is_element (node))
         continue;
 
-      if (node_has_name (node, "prop"))
+      if (xml_node_has_name (node, "prop"))
         {
           xmlBufferPtr buf = NULL;
 
           attrnode = node->children;
-          if (!node_is_element (attrnode))
+          if (!xml_node_is_element (attrnode))
             continue;
 
           if (!remove)
@@ -1468,7 +624,7 @@ other_lock_exists (const gchar *key, Path *path, gpointer data)
 static gboolean
 path_has_other_locks (PhodavServer *self, const gchar *path, GList *locks)
 {
-  return !foreach_parent_path (self, path, other_lock_exists, locks);
+  return !server_foreach_parent_path (self, path, other_lock_exists, locks);
 }
 
 typedef struct _IfState
@@ -1781,12 +937,12 @@ method_proppatch (PathHandler *handler, SoupMessage *msg,
   node = doc.root;
   for (node = node->children; node; node = node->next)
     {
-      if (!node_is_element (node))
+      if (!xml_node_is_element (node))
         continue;
 
-      if (node_has_name (node, "set"))
+      if (xml_node_has_name (node, "set"))
         status = prop_set (self, msg, file, node, &attr, FALSE);
-      else if (node_has_name (node, "remove"))
+      else if (xml_node_has_name (node, "remove"))
         status = prop_set (self, msg, file, node, &attr, TRUE);
       else
         g_warn_if_reached ();
@@ -2161,7 +1317,7 @@ try_add_lock (PhodavServer *self, const gchar *path, DAVLock *lock)
 {
   Path *p;
 
-  if (!foreach_parent_path (self, path, check_lock, lock))
+  if (!server_foreach_parent_path (self, path, check_lock, lock))
     return FALSE;
 
   p = get_path (self, path);
@@ -2250,22 +1406,22 @@ method_lock (PathHandler *handler, SoupMessage *msg,
   node = doc.root;
   for (node = node->children; node; node = node->next)
     {
-      if (!node_is_element (node))
+      if (!xml_node_is_element (node))
         continue;
 
-      if (node_has_name (node, "lockscope"))
+      if (xml_node_has_name (node, "lockscope"))
         {
           scope = parse_lockscope (node);
           if (scope == LOCK_SCOPE_NONE)
             break;
         }
-      else if (node_has_name (node, "locktype"))
+      else if (xml_node_has_name (node, "locktype"))
         {
           type = parse_locktype (node);
           if (type == LOCK_NONE)
             break;
         }
-      else if (node_has_name (node, "owner"))
+      else if (xml_node_has_name (node, "owner"))
         {
           if (owner == NULL)
             owner = node;
@@ -2304,7 +1460,7 @@ body:
   xmlSetNs (root, ns);
 
   node = xmlNewChild (root, ns, BAD_CAST "lockdiscovery", NULL);
-  xmlAddChild (node, get_activelock_node (lock, ns));
+  xmlAddChild (node, dav_lock_get_activelock_node (lock, ns));
 
   xml_node_to_string (root, &mem, &size);
   soup_message_set_response (msg, "application/xml",
@@ -2501,7 +1657,7 @@ server_callback (SoupServer *server, SoupMessage *msg,
            msg->method == SOUP_METHOD_HEAD)
     status = phodav_method_get (handler, msg, path, &err);
   else if (msg->method == SOUP_METHOD_PROPFIND)
-    status = method_propfind (handler, msg, path, &err);
+    status = phodav_method_propfind (handler, msg, path, &err);
   else if (msg->method == SOUP_METHOD_PROPPATCH)
     status = method_proppatch (handler, msg, path, &err);
   else if (msg->method == SOUP_METHOD_MKCOL)
diff --git a/libphodav/phodav-utils.c b/libphodav/phodav-utils.c
index 4cd295d..7033440 100644
--- a/libphodav/phodav-utils.c
+++ b/libphodav/phodav-utils.c
@@ -18,20 +18,6 @@
 
 #include "phodav-utils.h"
 
-void
-xml_node_to_string (xmlNodePtr root, xmlChar **mem, int *size)
-{
-  xmlDocPtr doc;
-
-  doc = xmlNewDoc (BAD_CAST "1.0");
-  xmlDocSetRootElement (doc, root);
-  // xmlReconciliateNs
-  xmlDocDumpMemoryEnc (doc, mem, size, "utf-8");
-  /* FIXME: validate document? */
-  /*FIXME, pretty print?*/
-  xmlFreeDoc (doc);
-}
-
 static xmlDocPtr
 parse_xml (const gchar  *data,
            const goffset len,
@@ -151,3 +137,79 @@ depth_to_string (DepthType depth)
 
   g_return_val_if_reached (NULL);
 }
+
+void
+xml_node_to_string (xmlNodePtr root, xmlChar **mem, int *size)
+{
+  xmlDocPtr doc;
+
+  doc = xmlNewDoc (BAD_CAST "1.0");
+  xmlDocSetRootElement (doc, root);
+  // xmlReconciliateNs
+  xmlDocDumpMemoryEnc (doc, mem, size, "utf-8");
+  /* FIXME: validate document? */
+  /*FIXME, pretty print?*/
+  xmlFreeDoc (doc);
+}
+
+void
+xml_node_debug (xmlNodePtr node)
+{
+  g_debug ("%s ns:%s", node->name, node->ns ? (gchar *) node->ns->href : "");
+}
+
+gboolean
+xml_node_has_ns (xmlNodePtr node, const char *ns_href)
+{
+  return node->ns && node->ns->href &&
+    !g_strcmp0 ((gchar *) node->ns->href, ns_href);
+
+}
+
+gboolean
+xml_node_has_name_ns (xmlNodePtr node, const char *name, const char *ns_href)
+{
+  gboolean has_name;
+  gboolean has_ns;
+
+  g_return_val_if_fail (node != NULL, FALSE);
+
+  has_name = has_ns = TRUE;
+
+  if (name)
+    has_name = !g_strcmp0 ((gchar *) node->name, name);
+
+  if (ns_href)
+    has_ns = xml_node_has_ns (node, ns_href);
+
+  return has_name && has_ns;
+}
+
+gboolean
+xml_node_has_name (xmlNodePtr node, const char *name)
+{
+  g_return_val_if_fail (node != NULL, FALSE);
+
+  return xml_node_has_name_ns (node, name, "DAV:");
+}
+
+gboolean
+xml_node_is_element (xmlNodePtr node)
+{
+  return node->type == XML_ELEMENT_NODE && node->name != NULL;
+}
+
+gchar *
+xml_node_get_xattr_name (xmlNodePtr node, const gchar *prefix)
+{
+  const gchar *ns = node->ns ? (gchar *) node->ns->href : NULL;
+  const gchar *name = (gchar *) node->name;
+
+  if (!name)
+    return NULL;
+
+  if (ns)
+    return g_strdup_printf ("%s%s#%s", prefix, ns, name);
+  else
+    return g_strdup_printf ("%s%s", prefix, name);
+}
diff --git a/libphodav/phodav-utils.h b/libphodav/phodav-utils.h
index 18780fc..1367a9e 100644
--- a/libphodav/phodav-utils.h
+++ b/libphodav/phodav-utils.h
@@ -27,8 +27,6 @@ DepthType        depth_from_string               (const gchar *depth);
 const gchar *    depth_to_string                 (DepthType depth);
 guint            timeout_from_string             (const gchar *timeout);
 
-void             xml_node_to_string              (xmlNodePtr root, xmlChar **mem, int *size);
-
 typedef struct _DavDoc     DavDoc;
 
 struct _DavDoc
@@ -45,6 +43,15 @@ gboolean         davdoc_parse                    (DavDoc *dd, SoupMessage *msg,
                                                   const gchar *name);
 void             davdoc_free                     (DavDoc *dd);
 
+void             xml_node_to_string              (xmlNodePtr root, xmlChar **mem, int *size);
+gboolean         xml_node_is_element             (xmlNodePtr node);
+gboolean         xml_node_has_name               (xmlNodePtr node, const char *name);
+gboolean         xml_node_has_name_ns            (xmlNodePtr node, const char *name,
+                                                  const char *ns_href);
+gboolean         xml_node_has_ns                 (xmlNodePtr node, const char *ns_href);
+void             xml_node_debug                  (xmlNodePtr node);
+gchar *          xml_node_get_xattr_name         (xmlNodePtr node, const gchar *prefix);
+
 G_END_DECLS
 
 #endif /* __PHODAV_UTILS_H__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]