[evolution-data-server/wip/offline-cache] Show detailed errors from EWebDAVSession, when provided by the server



commit e452ec1f6a6abffa5ea8efa3ca2bbf7d3d862d27
Author: Milan Crha <mcrha redhat com>
Date:   Thu Mar 30 19:26:59 2017 +0200

    Show detailed errors from EWebDAVSession, when provided by the server

 src/libedataserver/e-soup-session.c   |   10 +-
 src/libedataserver/e-webdav-session.c |  321 +++++++++++++++++++++++++--------
 src/libedataserver/e-webdav-session.h |   12 ++
 src/libedataserver/e-xml-utils.c      |    2 +-
 src/libedataserver/e-xml-utils.h      |    2 +-
 5 files changed, 269 insertions(+), 78 deletions(-)
---
diff --git a/src/libedataserver/e-soup-session.c b/src/libedataserver/e-soup-session.c
index 0867481..fe32cfc 100644
--- a/src/libedataserver/e-soup-session.c
+++ b/src/libedataserver/e-soup-session.c
@@ -619,8 +619,14 @@ e_soup_session_check_result (ESoupSession *session,
                        e_webdav_session_extract_ssl_data (session, message);
 
                if (read_bytes && bytes_length > 0) {
-                       soup_message_body_truncate (message->response_body);
+                       SoupBuffer *buffer;
+
                        soup_message_body_append (message->response_body, SOUP_MEMORY_COPY, read_bytes, 
bytes_length);
+
+                       /* This writes data to message->response_body->data */
+                       buffer = soup_message_body_flatten (message->response_body);
+                       if (buffer)
+                               soup_buffer_free (buffer);
                }
        }
 
@@ -792,7 +798,7 @@ e_soup_session_send_request_simple_sync (ESoupSession *session,
  * corresponding to @status_code. In case neither that can be found a localized
  * "Unknown error" message is returned.
  *
- * Returns: (transfer none): Error text base don given arguments. The returned
+ * Returns: (transfer none): Error text based on given arguments. The returned
  *    value is valid as long as @reason_phrase is not freed.
  *
  * Since: 3.26
diff --git a/src/libedataserver/e-webdav-session.c b/src/libedataserver/e-webdav-session.c
index f16ee13..8ac5efd 100644
--- a/src/libedataserver/e-webdav-session.c
+++ b/src/libedataserver/e-webdav-session.c
@@ -306,7 +306,20 @@ e_webdav_session_new (ESource *source)
                NULL);
 }
 
-static SoupRequestHTTP *
+/**
+ * e_webdav_session_options_sync:
+ * @webdav: an #EWebDAVSession
+ * @method: an HTTP method
+ * @uri: (nullable): URI to create the request for, or %NULL to read from #ESource
+ * @error: return location for a #GError, or %NULL
+ *
+ * Returns: (transfer full): A new #SoupRequestHTTP for the given @uri, or, when %NULL,
+ *    for the URI stored in the associated #ESource. Free the returned structure
+ *    with g_object_unref(), when no longer needed.
+ *
+ * Since: 3.26
+ **/
+SoupRequestHTTP *
 e_webdav_session_new_request (EWebDAVSession *webdav,
                              const gchar *method,
                              const gchar *uri,
@@ -339,6 +352,212 @@ e_webdav_session_new_request (EWebDAVSession *webdav,
        return request;
 }
 
+static gboolean
+e_webdav_session_extract_multistatus_error_cb (EWebDAVSession *webdav,
+                                              xmlXPathContextPtr xpath_ctx,
+                                              const gchar *xpath_prop_prefix,
+                                              const SoupURI *request_uri,
+                                              guint status_code,
+                                              gpointer user_data)
+{
+       GError **error = user_data;
+
+       g_return_val_if_fail (error != NULL, FALSE);
+
+       if (!xpath_prop_prefix)
+               return TRUE;
+
+       if (status_code != SOUP_STATUS_OK && (
+           status_code != SOUP_STATUS_FAILED_DEPENDENCY ||
+           !*error)) {
+               gchar *description;
+
+               description = e_xml_xpath_eval_as_string (xpath_ctx, "%s/../D:responsedescription", 
xpath_prop_prefix);
+               if (!description || !*description) {
+                       g_free (description);
+
+                       description = e_xml_xpath_eval_as_string (xpath_ctx, 
"%s/../../D:responsedescription", xpath_prop_prefix);
+               }
+
+               g_clear_error (error);
+               g_set_error_literal (error, SOUP_HTTP_ERROR, status_code,
+                       e_soup_session_util_status_to_string (status_code, description));
+
+               g_free (description);
+       }
+
+       return TRUE;
+}
+
+/**
+ * e_webdav_session_replace_with_detailed_error:
+ * @webdav: an #EWebDAVSession
+ * @request: a #SoupRequestHTTP
+ * @response_data: (nullable): received response data, or %NULL
+ * @ignore_multistatus: whether to ignore multistatus responses
+ * @prefix: (nullable): error message prefix, used when replacing, or %NULL
+ * @inout_error: (inout) (nullable) (transfer full): a #GError variable to replace content to, or %NULL
+ *
+ * Tries to read detailed error information from @response_data,
+ * if not provided, then from @request's response_body. If the detailed
+ * error cannot be found, then does nothing, otherwise frees the content
+ * of @inout_error, if any, and then populates it with an error message
+ * prefixed with @prefix.
+ *
+ * The @prefix might be of form "Failed to something", because the resulting
+ * error message will be:
+ * "Failed to something: HTTP error code XXX (reason_phrase): detailed_error".
+ * When @prefix is %NULL, the error message will be:
+ * "Failed with HTTP error code XXX (reason phrase): detailed_error".
+ *
+ * As the caller might not be interested in errors, also the @inout_error
+ * can be %NULL, in which case the function does nothing.
+ *
+ * Returns: Whether any detailed error had been recognized.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_webdav_session_replace_with_detailed_error (EWebDAVSession *webdav,
+                                             SoupRequestHTTP *request,
+                                             const GByteArray *response_data,
+                                             gboolean ignore_multistatus,
+                                             const gchar *prefix,
+                                             GError **inout_error)
+{
+       SoupMessage *message;
+       GByteArray byte_array = { 0 };
+       const gchar *content_type;
+       gchar *detail_text = NULL;
+       gboolean error_set = FALSE;
+       GError *local_error = NULL;
+
+       g_return_val_if_fail (E_IS_WEBDAV_SESSION (webdav), FALSE);
+       g_return_val_if_fail (SOUP_IS_REQUEST_HTTP (request), FALSE);
+
+       message = soup_request_http_get_message (request);
+       if (!message)
+               return FALSE;
+
+       byte_array.data = NULL;
+       byte_array.len = 0;
+
+       if (response_data && response_data->len) {
+               byte_array.data = (gpointer) response_data->data;
+               byte_array.len = response_data->len;
+       } else if (message->response_body && message->response_body->length) {
+               byte_array.data = (gpointer) message->response_body->data;
+               byte_array.len = message->response_body->length;
+       }
+
+       if (!byte_array.data || !byte_array.len)
+               goto out;
+
+       if (message->status_code == SOUP_STATUS_MULTI_STATUS &&
+           !ignore_multistatus &&
+           !e_webdav_session_traverse_multistatus_response (webdav, message, &byte_array,
+               e_webdav_session_extract_multistatus_error_cb, &local_error, NULL)) {
+               g_clear_error (&local_error);
+       }
+
+       if (local_error) {
+               if (prefix)
+                       g_prefix_error (&local_error, "%s: ", prefix);
+               g_propagate_error (inout_error, local_error);
+
+               g_object_unref (message);
+
+               return TRUE;
+       }
+
+       content_type = soup_message_headers_get_content_type (message->response_headers, NULL);
+       if (content_type && (
+           (g_ascii_strcasecmp (content_type, "application/xml") == 0 ||
+            g_ascii_strcasecmp (content_type, "text/xml") == 0))) {
+               xmlDocPtr doc;
+
+               if (message->status_code == SOUP_STATUS_MULTI_STATUS && ignore_multistatus)
+                       doc = NULL;
+               else
+                       doc = e_xml_parse_data (byte_array.data, byte_array.len);
+
+               if (doc) {
+                       xmlXPathContextPtr xpath_ctx;
+
+                       xpath_ctx = e_xml_new_xpath_context_with_namespaces (doc,
+                               "D", E_WEBDAV_NS_DAV,
+                               NULL);
+
+                       if (xpath_ctx &&
+                           e_xml_xpath_eval_exists (xpath_ctx, "/D:error")) {
+                               detail_text = e_xml_xpath_eval_as_string (xpath_ctx, "/D:error");
+                       }
+
+                       if (xpath_ctx)
+                               xmlXPathFreeContext (xpath_ctx);
+                       xmlFreeDoc (doc);
+               }
+       } else if (content_type &&
+            g_ascii_strcasecmp (content_type, "text/plain") == 0) {
+               detail_text = g_strndup ((const gchar *) byte_array.data, byte_array.len);
+       }
+
+ out:
+       if (detail_text)
+               g_strstrip (detail_text);
+
+       if (detail_text && *detail_text) {
+               error_set = TRUE;
+
+               g_clear_error (inout_error);
+
+               if (prefix) {
+                       g_set_error (inout_error, SOUP_HTTP_ERROR, message->status_code,
+                               /* Translators: The first '%s' is replaced with error prefix, as provided
+                                  by the caller, which can be in a form: "Failed with something".
+                                  The '%d' is replaced with actual HTTP status code.
+                                  The second '%s' is replaced with a reason phrase of the error (user 
readable text).
+                                  The last '%s' is replaced with detailed error text, as returned by the 
server. */
+                               _("%s: HTTP error code %d (%s): %s"), prefix, message->status_code,
+                               e_soup_session_util_status_to_string (message->status_code, 
message->reason_phrase),
+                               detail_text);
+               } else {
+                       g_set_error (inout_error, SOUP_HTTP_ERROR, message->status_code,
+                               /* Translators: The '%d' is replaced with actual HTTP status code.
+                                  The '%s' is replaced with a reason phrase of the error (user readable 
text).
+                                  The last '%s' is replaced with detailed error text, as returned by the 
server. */
+                               _("Failed with HTTP error code %d (%s): %s"), message->status_code,
+                               e_soup_session_util_status_to_string (message->status_code, 
message->reason_phrase),
+                               detail_text);
+               }
+       } else if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
+               error_set = TRUE;
+
+               g_clear_error (inout_error);
+
+               if (prefix) {
+                       g_set_error (inout_error, SOUP_HTTP_ERROR, message->status_code,
+                               /* Translators: The first '%s' is replaced with error prefix, as provided
+                                  by the caller, which can be in a form: "Failed with something".
+                                  The '%d' is replaced with actual HTTP status code.
+                                  The second '%s' is replaced with a reason phrase of the error (user 
readable text). */
+                               _("%s: HTTP error code %d (%s)"), prefix, message->status_code,
+                               e_soup_session_util_status_to_string (message->status_code, 
message->reason_phrase));
+               } else {
+                       g_set_error (inout_error, SOUP_HTTP_ERROR, message->status_code,
+                               /* Translators: The '%d' is replaced with actual HTTP status code.
+                                  The '%s' is replaced with a reason phrase of the error (user readable 
text). */
+                               _("Failed with HTTP error code %d (%s)"), message->status_code,
+                               e_soup_session_util_status_to_string (message->status_code, 
message->reason_phrase));
+               }
+       }
+
+       g_object_unref (message);
+       g_free (detail_text);
+
+       return error_set;
+}
+
 static GHashTable *
 e_webdav_session_comma_header_to_hashtable (SoupMessageHeaders *headers,
                                            const gchar *header_name)
@@ -531,9 +750,8 @@ e_webdav_session_propfind_sync (EWebDAVSession *webdav,
 
        bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, 
error);
 
-       g_object_unref (request);
-
-       success = bytes != NULL;
+       success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, TRUE, _("Failed to 
get properties"), error) &&
+               bytes != NULL;
 
        if (success)
                success = e_webdav_session_traverse_multistatus_response (webdav, message, bytes, func, 
func_user_data, error);
@@ -541,47 +759,11 @@ e_webdav_session_propfind_sync (EWebDAVSession *webdav,
        if (bytes)
                g_byte_array_free (bytes, TRUE);
        g_object_unref (message);
+       g_object_unref (request);
 
        return success;
 }
 
-static gboolean
-e_webdav_session_extract_multistatus_error_cb (EWebDAVSession *webdav,
-                                              xmlXPathContextPtr xpath_ctx,
-                                              const gchar *xpath_prop_prefix,
-                                              const SoupURI *request_uri,
-                                              guint status_code,
-                                              gpointer user_data)
-{
-       GError **error = user_data;
-
-       g_return_val_if_fail (error != NULL, FALSE);
-
-       if (!xpath_prop_prefix)
-               return TRUE;
-
-       if (status_code != SOUP_STATUS_OK && (
-           status_code != SOUP_STATUS_FAILED_DEPENDENCY ||
-           !*error)) {
-               gchar *description;
-
-               description = e_xml_xpath_eval_as_string (xpath_ctx, "%s/../D:responsedescription", 
xpath_prop_prefix);
-               if (!description || !*description) {
-                       g_free (description);
-
-                       description = e_xml_xpath_eval_as_string (xpath_ctx, 
"%s/../../D:responsedescription", xpath_prop_prefix);
-               }
-
-               g_clear_error (error);
-               g_set_error (error, SOUP_HTTP_ERROR, status_code, _("Failed to update properties: %s"),
-                       e_soup_session_util_status_to_string (status_code, description));
-
-               g_free (description);
-       }
-
-       return TRUE;
-}
-
 /**
  * e_webdav_session_proppatch_sync:
  * @webdav: an #EWebDAVSession
@@ -642,27 +824,13 @@ e_webdav_session_proppatch_sync (EWebDAVSession *webdav,
 
        bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, 
error);
 
-       g_object_unref (request);
-
-       success = bytes != NULL;
-
-       if (success) {
-               GError *local_error = NULL;
-
-               success = e_webdav_session_traverse_multistatus_response (webdav, message, bytes,
-                       e_webdav_session_extract_multistatus_error_cb, &local_error, error);
-
-               if (success && local_error) {
-                       g_propagate_error (error, local_error);
-                       success = FALSE;
-               } else if (local_error) {
-                       g_clear_error (&local_error);
-               }
-       }
+       success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, FALSE, _("Failed to 
update properties"), error) &&
+               bytes != NULL;
 
        if (bytes)
                g_byte_array_free (bytes, TRUE);
        g_object_unref (message);
+       g_object_unref (request);
 
        return success;
 }
@@ -699,7 +867,8 @@ e_webdav_session_mkcol_sync (EWebDAVSession *webdav,
 
        bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, 
error);
 
-       success = bytes != NULL;
+       success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, TRUE, _("Failed to 
create collection"), error) &&
+               bytes != NULL;
 
        if (bytes)
                g_byte_array_free (bytes, TRUE);
@@ -1132,7 +1301,8 @@ e_webdav_session_put_sync (EWebDAVSession *webdav,
        g_signal_handler_disconnect (message, wrote_headers_id);
        g_signal_handler_disconnect (message, wrote_chunk_id);
 
-       success = bytes != NULL;
+       success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, TRUE, _("Failed to 
put data"), error) &&
+               bytes != NULL;
 
        if (cwd.error) {
                g_clear_error (error);
@@ -1317,7 +1487,8 @@ e_webdav_session_delete_sync (EWebDAVSession *webdav,
 
        bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, 
error);
 
-       success = bytes != NULL;
+       success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, TRUE, _("Failed to 
delete resource"), error) &&
+               bytes != NULL;
 
        if (bytes)
                g_byte_array_free (bytes, TRUE);
@@ -1383,7 +1554,8 @@ e_webdav_session_copy_sync (EWebDAVSession *webdav,
 
        bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, 
error);
 
-       success = bytes != NULL;
+       success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, TRUE, _("Failed to 
copy resource"), error) &&
+               bytes != NULL;
 
        if (bytes)
                g_byte_array_free (bytes, TRUE);
@@ -1444,7 +1616,8 @@ e_webdav_session_move_sync (EWebDAVSession *webdav,
 
        bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, 
error);
 
-       success = bytes != NULL;
+       success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, TRUE, _("Failed to 
move resource"), error) &&
+               bytes != NULL;
 
        if (bytes)
                g_byte_array_free (bytes, TRUE);
@@ -1548,9 +1721,8 @@ e_webdav_session_lock_sync (EWebDAVSession *webdav,
 
        bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, 
error);
 
-       g_object_unref (request);
-
-       success = bytes != NULL;
+       success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, TRUE, _("Failed to 
lock resource"), error) &&
+               bytes != NULL;
 
        if (success && out_xml_response) {
                const gchar *content_type;
@@ -1575,7 +1747,7 @@ e_webdav_session_lock_sync (EWebDAVSession *webdav,
                if (success) {
                        xmlDocPtr doc;
 
-                       doc = e_xml_parse_data ((const gchar *) bytes->data, bytes->len);
+                       doc = e_xml_parse_data (bytes->data, bytes->len);
                        if (!doc) {
                                g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
                                        _("Failed to parse XML data"));
@@ -1593,6 +1765,7 @@ e_webdav_session_lock_sync (EWebDAVSession *webdav,
        if (bytes)
                g_byte_array_free (bytes, TRUE);
        g_object_unref (message);
+       g_object_unref (request);
 
        return success;
 }
@@ -1656,13 +1829,13 @@ e_webdav_session_refresh_lock_sync (EWebDAVSession *webdav,
 
        bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, 
error);
 
-       g_object_unref (request);
-
-       success = bytes != NULL;
+       success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, TRUE, _("Failed to 
refresh lock"), error) &&
+               bytes != NULL;
 
        if (bytes)
                g_byte_array_free (bytes, TRUE);
        g_object_unref (message);
+       g_object_unref (request);
 
        return success;
 }
@@ -1715,13 +1888,13 @@ e_webdav_session_unlock_sync (EWebDAVSession *webdav,
 
        bytes = e_soup_session_send_request_simple_sync (E_SOUP_SESSION (webdav), request, cancellable, 
error);
 
-       g_object_unref (request);
-
-       success = bytes != NULL;
+       success = !e_webdav_session_replace_with_detailed_error (webdav, request, bytes, TRUE, _("Failed to 
unlock"), error) &&
+               bytes != NULL;
 
        if (bytes)
                g_byte_array_free (bytes, TRUE);
        g_object_unref (message);
+       g_object_unref (request);
 
        return success;
 }
@@ -1796,7 +1969,7 @@ e_webdav_session_traverse_multistatus_response (EWebDAVSession *webdav,
                request_uri = soup_message_get_uri ((SoupMessage *) message);
        }
 
-       doc = e_xml_parse_data ((const gchar *) xml_data->data, xml_data->len);
+       doc = e_xml_parse_data (xml_data->data, xml_data->len);
 
        if (!doc) {
                g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
diff --git a/src/libedataserver/e-webdav-session.h b/src/libedataserver/e-webdav-session.h
index 540a843..d845e50 100644
--- a/src/libedataserver/e-webdav-session.h
+++ b/src/libedataserver/e-webdav-session.h
@@ -239,6 +239,18 @@ struct _EWebDAVSessionClass {
 GType          e_webdav_session_get_type               (void) G_GNUC_CONST;
 
 EWebDAVSession *e_webdav_session_new                   (ESource *source);
+SoupRequestHTTP *
+               e_webdav_session_new_request            (EWebDAVSession *webdav,
+                                                        const gchar *method,
+                                                        const gchar *uri,
+                                                        GError **error);
+gboolean       e_webdav_session_replace_with_detailed_error
+                                                       (EWebDAVSession *webdav,
+                                                        SoupRequestHTTP *request,
+                                                        const GByteArray *response_data,
+                                                        gboolean ignore_multistatus,
+                                                        const gchar *prefix,
+                                                        GError **inout_error);
 gboolean       e_webdav_session_options_sync           (EWebDAVSession *webdav,
                                                         const gchar *uri,
                                                         GHashTable **out_capabilities,
diff --git a/src/libedataserver/e-xml-utils.c b/src/libedataserver/e-xml-utils.c
index a02c327..4b7a267 100644
--- a/src/libedataserver/e-xml-utils.c
+++ b/src/libedataserver/e-xml-utils.c
@@ -192,7 +192,7 @@ e_xml_get_child_by_name (const xmlNode *parent,
  * Since: 3.26
  **/
 xmlDocPtr
-e_xml_parse_data (const gchar *data,
+e_xml_parse_data (gconstpointer data,
                  gsize length)
 {
        g_return_val_if_fail (data != NULL, NULL);
diff --git a/src/libedataserver/e-xml-utils.h b/src/libedataserver/e-xml-utils.h
index 2c98ecb..5222792 100644
--- a/src/libedataserver/e-xml-utils.h
+++ b/src/libedataserver/e-xml-utils.h
@@ -35,7 +35,7 @@ gint          e_xml_save_file                 (const gchar *filename,
 xmlNode *      e_xml_get_child_by_name         (const xmlNode *parent,
                                                 const xmlChar *child_name);
 
-xmlDocPtr      e_xml_parse_data                (const gchar *data,
+xmlDocPtr      e_xml_parse_data                (gconstpointer data,
                                                 gsize length);
 xmlXPathContextPtr
                e_xml_new_xpath_context_with_namespaces


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