[libsoup/carlosgc/simple-api: 2/4] session: Add new simple API to retrieve a URI




commit 084a75055ed8b503465591648dbdb167276830b8
Author: Carlos Garcia Campos <cgarcia igalia com>
Date:   Mon Oct 19 11:28:15 2020 +0200

    session: Add new simple API to retrieve a URI
    
    It adds soup_session_get() and soup_session_get_bytes() and the async
    alternatives to easily retrieve the contents of a URI. It supports the
    same URIs as SoupRequest API that will be removed.

 docs/reference/libsoup-3.0-sections.txt |  10 +
 libsoup/soup-session.c                  | 943 ++++++++++++++++++++++++++++++++
 libsoup/soup-session.h                  |  50 ++
 tests/session-test.c                    | 358 +++++++++++-
 4 files changed, 1345 insertions(+), 16 deletions(-)
---
diff --git a/docs/reference/libsoup-3.0-sections.txt b/docs/reference/libsoup-3.0-sections.txt
index 64989b04..aa89d8c0 100644
--- a/docs/reference/libsoup-3.0-sections.txt
+++ b/docs/reference/libsoup-3.0-sections.txt
@@ -397,6 +397,7 @@ SoupAuthDomainDigestClass
 <FILE>soup-session</FILE>
 <TITLE>SoupSession</TITLE>
 SoupSession
+SoupSessionError
 <SUBSECTION>
 soup_session_new
 soup_session_new_with_options
@@ -414,6 +415,13 @@ soup_session_send
 soup_session_send_async
 soup_session_send_finish
 <SUBSECTION>
+soup_session_get
+soup_session_get_async
+soup_session_get_finish
+soup_session_get_bytes
+soup_session_get_bytes_async
+soup_session_get_bytes_finish
+<SUBSECTION>
 soup_session_websocket_connect_async
 soup_session_websocket_connect_finish
 <SUBSECTION>
@@ -459,9 +467,11 @@ SOUP_SESSION
 SOUP_SESSION_CLASS
 SOUP_SESSION_GET_CLASS
 SOUP_TYPE_SESSION
+SOUP_SESSION_ERROR
 SoupSessionClass
 soup_session_get_type
 soup_request_error_quark
+soup_session_error_quark
 <SUBSECTION Private>
 SoupSocket
 SoupConnection
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index f9fb7176..d3c82811 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -17,6 +17,7 @@
 #include "auth/soup-auth-ntlm.h"
 #include "cache/soup-cache-private.h"
 #include "soup-connection.h"
+#include "soup-directory-input-stream.h"
 #include "soup-message-private.h"
 #include "soup-misc.h"
 #include "soup-message-queue.h"
@@ -208,6 +209,34 @@ enum {
        LAST_PROP
 };
 
+/**
+ * SOUP_SESSION_ERROR:
+ *
+ * A #GError domain for #SoupSession<!-- -->-related errors. Used with
+ * #SoupSessionError.
+ */
+/**
+ * SoupSessionError:
+ * @SOUP_SESSION_ERROR_BAD_URI: the URI could not be parsed
+ * @SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME: the URI scheme is not
+ *   supported by this #SoupSession
+ * @SOUP_SESSION_ERROR_PARSING: the server's response could not
+ *   be parsed
+ * @SOUP_SESSION_ERROR_ENCODING: the server's response was in an
+ *   unsupported format
+ *
+ * A #SoupSession error.
+ */
+
+GQuark
+soup_session_error_quark (void)
+{
+        static GQuark error;
+        if (!error)
+                error = g_quark_from_static_string ("soup_session_error_quark");
+        return error;
+}
+
 static void
 soup_session_init (SoupSession *session)
 {
@@ -3747,6 +3776,920 @@ soup_session_send (SoupSession   *session,
        return stream;
 }
 
+typedef struct {
+        goffset content_length;
+        char *content_type;
+} SessionGetAsyncData;
+
+static void
+session_get_async_data_free (SessionGetAsyncData *data)
+{
+        g_free (data->content_type);
+        g_slice_free (SessionGetAsyncData, data);
+}
+
+static void
+session_get_async_data_set_content_type (SessionGetAsyncData *data,
+                                         const char          *content_type,
+                                         GHashTable          *params)
+{
+        GString *type;
+
+        type = g_string_new (content_type);
+        if (params) {
+                GHashTableIter iter;
+                gpointer key, value;
+
+                g_hash_table_iter_init (&iter, params);
+                while (g_hash_table_iter_next (&iter, &key, &value)) {
+                        g_string_append (type, "; ");
+                        soup_header_g_string_append_param (type, key, value);
+                }
+        }
+
+        g_free (data->content_type);
+        data->content_type = g_string_free (type, FALSE);
+}
+
+static void
+http_input_stream_ready_cb (SoupSession  *session,
+                            GAsyncResult *result,
+                            GTask        *task)
+{
+        GInputStream *stream;
+        GError *error = NULL;
+
+        stream = soup_session_send_finish (session, result, &error);
+        if (stream)
+                g_task_return_pointer (task, stream, g_object_unref);
+        else
+                g_task_return_error (task, error);
+        g_object_unref (task);
+}
+
+static void
+get_http_content_sniffed (SoupMessage         *msg,
+                          const char          *content_type,
+                          GHashTable          *params,
+                          SessionGetAsyncData *data)
+{
+        session_get_async_data_set_content_type (data, content_type, params);
+}
+
+static void
+get_http_got_headers (SoupMessage         *msg,
+                      SessionGetAsyncData *data)
+{
+        goffset content_length;
+        const char *content_type;
+        GHashTable *params = NULL;
+
+        content_length = soup_message_headers_get_content_length (msg->response_headers);
+        data->content_length = content_length != 0 ? content_length : -1;
+        content_type = soup_message_headers_get_content_type (msg->response_headers, &params);
+        session_get_async_data_set_content_type (data, content_type, params);
+        g_clear_pointer (&params, g_hash_table_destroy);
+}
+
+static void
+soup_session_get_http_async (SoupSession *session,
+                             SoupURI     *uri,
+                             GTask       *task)
+{
+        SoupMessage *msg;
+        SessionGetAsyncData *data;
+
+        if (!SOUP_URI_VALID_FOR_HTTP (uri)) {
+                char *uri_string = soup_uri_to_string (uri, FALSE);
+
+                g_task_return_new_error (task,
+                                         SOUP_SESSION_ERROR,
+                                         SOUP_SESSION_ERROR_BAD_URI,
+                                         _("Invalid “%s” URI: %s"),
+                                         uri->scheme,
+                                         uri_string);
+                g_free (uri_string);
+                g_object_unref (task);
+                return;
+        }
+
+        data = g_task_get_task_data (task);
+
+        msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
+        g_signal_connect (msg, "content-sniffed",
+                          G_CALLBACK (get_http_content_sniffed), data);
+        g_signal_connect (msg, "got-headers",
+                          G_CALLBACK (get_http_got_headers), data);
+        soup_session_send_async (session, msg,
+                                 g_task_get_priority (task),
+                                 g_task_get_cancellable (task),
+                                 (GAsyncReadyCallback)http_input_stream_ready_cb,
+                                 task);
+        g_object_unref (msg);
+}
+
+static void
+file_enumerate_ready_cb (GFile        *file,
+                         GAsyncResult *result,
+                         GTask        *task)
+{
+        GFileEnumerator *enumerator;
+        GError *error = NULL;
+
+        enumerator = g_file_enumerate_children_finish (file, result, &error);
+        if (enumerator) {
+                GInputStream *stream;
+                SoupURI *uri;
+                char *uri_string;
+                SessionGetAsyncData *data;
+
+                uri_string = g_file_get_uri (file);
+                uri = soup_uri_new (uri_string);
+                g_free (uri_string);
+                stream = soup_directory_input_stream_new (enumerator, uri);
+                soup_uri_free (uri);
+                g_object_unref (enumerator);
+
+                data = g_task_get_task_data (task);
+                data->content_length = -1;
+                data->content_type = g_strdup ("text/html");
+
+                g_task_return_pointer (task, stream, g_object_unref);
+        } else {
+                g_task_return_error (task, error);
+        }
+        g_object_unref (task);
+}
+
+static void
+file_read_ready_cb (GFile        *file,
+                    GAsyncResult *result,
+                    GTask        *task)
+{
+        GInputStream *stream;
+        GError *error = NULL;
+
+        stream = G_INPUT_STREAM (g_file_read_finish (file, result, &error));
+        if (stream)
+                g_task_return_pointer (task, stream, g_object_unref);
+        else
+                g_task_return_error (task, error);
+        g_object_unref (task);
+}
+
+static void
+file_query_info_ready_cb (GFile        *file,
+                          GAsyncResult *result,
+                          GTask        *task)
+{
+        GFileInfo *info;
+
+        info = g_file_query_info_finish (file, result, NULL);
+        if (info) {
+                SessionGetAsyncData *data;
+                const char *content_type;
+
+                if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
+                        g_file_enumerate_children_async (file,
+                                                         "*",
+                                                         G_FILE_QUERY_INFO_NONE,
+                                                         g_task_get_priority (task),
+                                                         g_task_get_cancellable (task),
+                                                         (GAsyncReadyCallback)file_enumerate_ready_cb,
+                                                         task);
+                        g_object_unref (info);
+                        return;
+                }
+
+                data = g_task_get_task_data (task);
+                data->content_length = g_file_info_get_size (info);
+
+                content_type = g_file_info_get_content_type (info);
+                if (content_type)
+                        data->content_type = g_content_type_get_mime_type (content_type);
+                if (!data->content_type)
+                        data->content_type = g_strdup ("application/octet-stream");
+
+                g_object_unref (info);
+        }
+
+        g_file_read_async (file,
+                           g_task_get_priority (task),
+                           g_task_get_cancellable (task),
+                           (GAsyncReadyCallback)file_read_ready_cb,
+                           task);
+}
+
+#ifdef G_OS_WIN32
+static void
+windowsify_file_uri_path (char *path)
+{
+        char *p, *slash;
+
+        /* Copied from g_filename_from_uri(), which we can't use
+         * directly because it rejects invalid URIs that we need to
+         * keep.
+         */
+
+        /* Turn slashes into backslashes, because that's the canonical spelling */
+        p = path;
+        while ((slash = strchr (p, '/')) != NULL) {
+                *slash = '\\';
+                p = slash + 1;
+        }
+
+        /* Windows URIs with a drive letter can be like
+         * "file://host/c:/foo" or "file://host/c|/foo" (some Netscape
+         * versions). In those cases, start the filename from the
+         * drive letter.
+         */
+        if (g_ascii_isalpha (path[1])) {
+                if (path[2] == '|')
+                        path[2] = ':';
+                if (path[2] == ':')
+                        memmove (path, path + 1, strlen (path));
+        }
+}
+#endif /* G_OS_WIN32 */
+
+static void
+soup_session_get_file_async (SoupSession *session,
+                             SoupURI     *uri,
+                             GTask       *task)
+{
+        GFile *file;
+        char *path;
+
+        /* "file:/foo" is not valid, but it must be "file:///..." or "file://localhost/..." */
+        if (!uri->host || (*uri->host && g_ascii_strcasecmp (uri->host, "localhost") != 0)) {
+                char *uri_string = soup_uri_to_string (uri, FALSE);
+
+                g_task_return_new_error (task,
+                                         SOUP_SESSION_ERROR,
+                                         SOUP_SESSION_ERROR_BAD_URI,
+                                         _("Invalid “%s” URI: %s"),
+                                         uri->scheme,
+                                         uri_string);
+                g_free (uri_string);
+                g_object_unref (task);
+                return;
+        }
+
+        path = soup_uri_decode (uri->path);
+#ifdef G_OS_WIN32
+        windowsify_file_uri_path (decoded_path);
+#endif
+
+        if (uri->scheme == SOUP_URI_SCHEME_RESOURCE) {
+                char *uri_string;
+
+                uri_string = g_strdup_printf ("resource://%s", path);
+                file = g_file_new_for_uri (uri_string);
+                g_free (uri_string);
+        } else
+                file = g_file_new_for_path (path);
+        g_free (path);
+
+        g_file_query_info_async (file,
+                                 G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+                                 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
+                                 G_FILE_ATTRIBUTE_STANDARD_SIZE,
+                                 G_FILE_QUERY_INFO_NONE,
+                                 g_task_get_priority (task),
+                                 g_task_get_cancellable (task),
+                                 (GAsyncReadyCallback)file_query_info_ready_cb,
+                                 task);
+}
+
+#define BASE64_INDICATOR     ";base64"
+#define BASE64_INDICATOR_LEN (sizeof (";base64") - 1)
+
+static GBytes *
+soup_session_decode_data_uri (SoupSession *session,
+                              SoupURI     *uri,
+                              char       **content_type)
+{
+        char *uri_string;
+        const char *comma, *start, *end;
+        gboolean base64 = FALSE;
+        GBytes *bytes = NULL;
+
+        if (content_type)
+                *content_type = NULL;
+
+        uri_string = soup_uri_to_string (uri, FALSE);
+        start = uri_string + 5;
+        comma = strchr (start, ',');
+        if (comma && comma != start) {
+                /* Deal with MIME type / params */
+                if (comma >= start + BASE64_INDICATOR_LEN && !g_ascii_strncasecmp (comma - 
BASE64_INDICATOR_LEN, BASE64_INDICATOR, BASE64_INDICATOR_LEN)) {
+                        end = comma - BASE64_INDICATOR_LEN;
+                        base64 = TRUE;
+                } else
+                        end = comma;
+
+                if (content_type && end != start)
+                        *content_type = soup_uri_decoded_copy (start, end - start, NULL);
+        }
+
+        if (content_type && !*content_type)
+                *content_type = g_strdup ("text/plain;charset=US-ASCII");
+
+        if (comma)
+                start = comma + 1;
+
+        if (*start) {
+                gsize content_length;
+                int decoded_length = 0;
+                guchar *buffer = (guchar *)soup_uri_decoded_copy (start, strlen (start),
+                                                                  &decoded_length);
+
+                if (base64)
+                        buffer = g_base64_decode_inplace ((gchar*)buffer, &content_length);
+                else
+                        content_length = decoded_length;
+
+                bytes = g_bytes_new_take (buffer, content_length);
+        } else
+                bytes = g_bytes_new_static (NULL, 0);
+        g_free (uri_string);
+
+        return bytes;
+}
+
+static void
+soup_session_get_data_async (SoupSession *session,
+                             SoupURI     *uri,
+                             GTask       *task)
+{
+        SessionGetAsyncData *data;
+        GBytes *body;
+
+        if (uri->host != NULL) {
+                char *uri_string = soup_uri_to_string (uri, FALSE);
+
+                g_task_return_new_error (task,
+                                         SOUP_SESSION_ERROR,
+                                         SOUP_SESSION_ERROR_BAD_URI,
+                                         _("Invalid “%s” URI: %s"),
+                                         uri->scheme,
+                                         uri_string);
+                g_free (uri_string);
+                g_object_unref (task);
+                return;
+        }
+
+        data = g_task_get_task_data (task);
+        body = soup_session_decode_data_uri (session, uri, &data->content_type);
+        data->content_length = g_bytes_get_size (body);
+
+        g_task_return_pointer (task,
+                               g_memory_input_stream_new_from_bytes (body),
+                               g_object_unref);
+        g_bytes_unref (body);
+        g_object_unref (task);
+}
+
+/**
+ * soup_session_get_async:
+ * @session: a #SoupSession
+ * @uri: a URI, in string form
+ * @io_priority: the I/O priority of the request
+ * @cancellable: a #GCancellable
+ * @callback: the callback to invoke
+ * @user_data: data for @callback
+ *
+ * Asynchronously retrieve @uri.
+ *
+ * This function supports several URI schemes (http, https, file, resource and data). If
+ * an unsupported URI is passed it will fail with %SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME
+ * error.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call soup_session_get_finish() to get the result of the operation.
+ */
+void
+soup_session_get_async (SoupSession        *session,
+                        const char         *uri,
+                        int                 io_priority,
+                        GCancellable       *cancellable,
+                        GAsyncReadyCallback callback,
+                        gpointer            user_data)
+{
+        GTask *task;
+        SoupURI *soup_uri;
+
+        g_return_if_fail (SOUP_IS_SESSION (session));
+        g_return_if_fail (uri != NULL);
+
+        task = g_task_new (session, cancellable, callback, user_data);
+        g_task_set_priority (task, io_priority);
+
+        soup_uri = soup_uri_new (uri);
+        if (!soup_uri) {
+                g_task_return_new_error (task,
+                                         SOUP_SESSION_ERROR,
+                                         SOUP_SESSION_ERROR_BAD_URI,
+                                         _("Could not parse URI “%s”"),
+                                         uri);
+                g_object_unref (task);
+                return;
+        }
+
+        g_task_set_task_data (task,
+                              g_slice_new0 (SessionGetAsyncData),
+                              (GDestroyNotify)session_get_async_data_free);
+
+        if (soup_uri->scheme == SOUP_URI_SCHEME_HTTP || soup_uri->scheme == SOUP_URI_SCHEME_HTTPS)
+                soup_session_get_http_async (session, soup_uri, task);
+        else if (soup_uri->scheme == SOUP_URI_SCHEME_FILE || soup_uri->scheme == SOUP_URI_SCHEME_RESOURCE)
+                soup_session_get_file_async (session, soup_uri, task);
+        else if (soup_uri->scheme == SOUP_URI_SCHEME_DATA)
+                soup_session_get_data_async (session, soup_uri, task);
+        else {
+                g_task_return_new_error (task,
+                                         SOUP_SESSION_ERROR,
+                                         SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME,
+                                         _("Unsupported URI scheme “%s”"),
+                                         soup_uri->scheme);
+                g_object_unref (task);
+        }
+}
+
+/**
+ * soup_session_get_finish:
+ * @session: a #SoupSession
+ * @result: the #GAsyncResult passed to your callback
+ * @content_length: (out) (nullable): location to store content length, or %NULL
+ * @content_type: (out) (nullable) (transfer full): location to store content type, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finish an asynchronous operation started by soup_session_get_async().
+ * If the content length is unknown -1 is returned in @content_length.
+ *
+ * Returns: (transfer full): a #GInputStream to read the contents from,
+ *    or %NULL in case of error.
+ */
+GInputStream *
+soup_session_get_finish (SoupSession  *session,
+                         GAsyncResult *result,
+                         goffset      *content_length,
+                         char        **content_type,
+                         GError      **error)
+{
+        GTask *task;
+
+        g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
+        g_return_val_if_fail (g_task_is_valid (result, session), NULL);
+
+        task = G_TASK (result);
+
+        if (!g_task_had_error (task) && (content_length || content_type)) {
+                SessionGetAsyncData *data;
+
+                data = g_task_get_task_data (task);
+                if (content_length)
+                        *content_length = data->content_length;
+                if (content_type) {
+                        *content_type = data->content_type;
+                        data->content_type = NULL;
+                }
+        }
+
+        return g_task_propagate_pointer (task, error);
+}
+
+static GInputStream *
+soup_session_get_http (SoupSession  *session,
+                       SoupURI      *uri,
+                       GCancellable *cancellable,
+                       goffset      *content_length,
+                       char        **content_type,
+                       GError      **error)
+{
+        SoupMessage *msg;
+        GInputStream *stream;
+        SessionGetAsyncData data = { 0, NULL };
+
+        if (!SOUP_URI_VALID_FOR_HTTP (uri)) {
+                char *uri_string = soup_uri_to_string (uri, FALSE);
+
+                g_set_error (error,
+                             SOUP_SESSION_ERROR,
+                             SOUP_SESSION_ERROR_BAD_URI,
+                             _("Invalid “%s” URI: %s"),
+                             uri->scheme,
+                             uri_string);
+                g_free (uri_string);
+
+                return NULL;
+        }
+
+        msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
+        g_signal_connect (msg, "content-sniffed",
+                          G_CALLBACK (get_http_content_sniffed), &data);
+        g_signal_connect (msg, "got-headers",
+                          G_CALLBACK (get_http_got_headers), &data);
+        stream = soup_session_send (session, msg, cancellable, error);
+        if (stream) {
+                if (content_length)
+                        *content_length = data.content_length;
+                if (content_type) {
+                        *content_type = data.content_type;
+                        data.content_type = NULL;
+                }
+        }
+
+        g_free (data.content_type);
+
+        return stream;
+}
+
+static GInputStream *
+soup_session_get_file (SoupSession  *session,
+                       SoupURI      *uri,
+                       GCancellable *cancellable,
+                       goffset      *content_length,
+                       char        **content_type,
+                       GError      **error)
+{
+        GFile *file;
+        char *path;
+        GFileInfo *info;
+        GInputStream *stream = NULL;
+
+        /* "file:/foo" is not valid, but it must be "file:///..." or "file://localhost/..." */
+        if (!uri->host || (*uri->host && g_ascii_strcasecmp (uri->host, "localhost") != 0)) {
+                char *uri_string = soup_uri_to_string (uri, FALSE);
+
+                g_set_error (error,
+                             SOUP_SESSION_ERROR,
+                             SOUP_SESSION_ERROR_BAD_URI,
+                             _("Invalid “%s” URI: %s"),
+                             uri->scheme,
+                             uri_string);
+                g_free (uri_string);
+
+                return NULL;
+        }
+
+        path = soup_uri_decode (uri->path);
+#ifdef G_OS_WIN32
+        windowsify_file_uri_path (decoded_path);
+#endif
+
+        if (uri->scheme == SOUP_URI_SCHEME_RESOURCE) {
+                char *uri_string;
+
+                uri_string = g_strdup_printf ("resource://%s", path);
+                file = g_file_new_for_uri (uri_string);
+                g_free (uri_string);
+        } else
+                file = g_file_new_for_path (path);
+        g_free (path);
+
+        info = g_file_query_info (file,
+                                  G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+                                  G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
+                                  G_FILE_ATTRIBUTE_STANDARD_SIZE,
+                                  G_FILE_QUERY_INFO_NONE,
+                                  cancellable, NULL);
+        if (info && g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
+                GFileEnumerator *enumerator;
+
+                enumerator = g_file_enumerate_children (file,
+                                                        "*",
+                                                        G_FILE_QUERY_INFO_NONE,
+                                                        cancellable,
+                                                        error);
+                if (enumerator) {
+                        stream = soup_directory_input_stream_new (enumerator, uri);
+                        if (content_length)
+                                *content_length = -1;
+                        if (content_type)
+                                *content_type = g_strdup ("text/html");
+
+                        g_object_unref (enumerator);
+                }
+
+                g_object_unref (info);
+                g_object_unref (file);
+
+                return stream;
+        }
+
+        if (info) {
+                if (content_length)
+                        *content_length = g_file_info_get_size (info);
+
+                if (content_type) {
+                        const char *type;
+
+                        *content_type = NULL;
+
+                        type = g_file_info_get_content_type (info);
+                        if (type)
+                                *content_type = g_content_type_get_mime_type (type);
+                        if (!*content_type)
+                                *content_type = g_strdup ("application/octet-stream");
+                }
+
+                g_object_unref (info);
+        }
+
+        stream = G_INPUT_STREAM (g_file_read (file, cancellable, error));
+        g_object_unref (file);
+
+        return stream;
+}
+
+static GInputStream *
+soup_session_get_data (SoupSession  *session,
+                       SoupURI      *uri,
+                       GCancellable *cancellable,
+                       goffset      *content_length,
+                       char        **content_type,
+                       GError      **error)
+{
+        GBytes *body;
+        GInputStream *stream;
+
+        if (uri->host != NULL) {
+                char *uri_string = soup_uri_to_string (uri, FALSE);
+
+                g_set_error (error,
+                             SOUP_SESSION_ERROR,
+                             SOUP_SESSION_ERROR_BAD_URI,
+                             _("Invalid “%s” URI: %s"),
+                             uri->scheme,
+                             uri_string);
+                g_free (uri_string);
+
+                return NULL;
+        }
+
+        body = soup_session_decode_data_uri (session, uri, content_type);
+        if (content_length)
+                *content_length = g_bytes_get_size (body);
+
+        stream = g_memory_input_stream_new_from_bytes (body);
+        g_bytes_unref (body);
+
+        return stream;
+}
+
+/**
+ * soup_session_get:
+ * @session: a #SoupSession
+ * @uri: a URI, in string form
+ * @cancellable: a #GCancellable
+ * @content_length: (out) (nullable): location to store content length, or %NULL
+ * @content_type: (out) (nullable) (transfer full): location to store content type, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Synchronously retrieve @uri and return a #GInputStream to read the contents.
+ * If the content length is unknown -1 is returned in @content_length.
+ *
+ * This function supports several URI schemes (http, https, file, resource and data). If
+ * an unsupported URI is passed it will fail with %SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME
+ * error.
+ *
+ * Returns: (transfer full): a #GInputStream to read the contents from,
+ *    or %NULL in case of error.
+ */
+GInputStream *
+soup_session_get (SoupSession  *session,
+                  const char   *uri,
+                  GCancellable *cancellable,
+                  goffset      *content_length,
+                  char        **content_type,
+                  GError      **error)
+{
+        SoupURI *soup_uri;
+
+        g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
+        g_return_val_if_fail (uri != NULL, NULL);
+
+        soup_uri = soup_uri_new (uri);
+        if (!soup_uri) {
+                g_set_error (error,
+                             SOUP_SESSION_ERROR,
+                             SOUP_SESSION_ERROR_BAD_URI,
+                             _("Could not parse URI “%s”"),
+                             uri);
+
+                return NULL;
+        }
+
+        if (soup_uri->scheme == SOUP_URI_SCHEME_HTTP || soup_uri->scheme == SOUP_URI_SCHEME_HTTPS)
+                return soup_session_get_http (session, soup_uri, cancellable,
+                                              content_length, content_type, error);
+        if (soup_uri->scheme == SOUP_URI_SCHEME_FILE || soup_uri->scheme == SOUP_URI_SCHEME_RESOURCE)
+                return soup_session_get_file (session, soup_uri, cancellable,
+                                              content_length, content_type, error);
+        if (soup_uri->scheme == SOUP_URI_SCHEME_DATA)
+                return soup_session_get_data (session, soup_uri, cancellable,
+                                              content_length, content_type, error);
+
+        g_set_error (error,
+                     SOUP_SESSION_ERROR,
+                     SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME,
+                     _("Unsupported URI scheme “%s”"),
+                     soup_uri->scheme);
+
+        return NULL;
+}
+
+static void
+session_get_async_splice_ready_cb (GOutputStream *ostream,
+                                   GAsyncResult  *result,
+                                   GTask         *task)
+{
+        GError *error = NULL;
+
+        if (g_output_stream_splice_finish (ostream, result, &error) != -1) {
+                g_task_return_pointer (task,
+                                       g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM 
(ostream)),
+                                       (GDestroyNotify)g_bytes_unref);
+        } else {
+                g_task_return_error (task, error);
+        }
+        g_object_unref (task);
+}
+
+static void
+session_get_async_ready_cb (SoupSession  *session,
+                            GAsyncResult *result,
+                            GTask        *task)
+{
+        GInputStream *stream;
+        goffset content_length;
+        char *content_type;
+        GOutputStream *ostream;
+        GError *error = NULL;
+
+        stream = soup_session_get_finish (session, result, &content_length, &content_type, &error);
+        if (!stream) {
+                g_task_return_error (task, error);
+                g_object_unref (task);
+
+                return;
+        }
+
+        g_task_set_task_data (task, content_type, g_free);
+
+        if (content_length == 0) {
+                g_task_return_pointer (task,
+                                       g_bytes_new_static (NULL, 0),
+                                       (GDestroyNotify)g_bytes_unref);
+                g_object_unref (task);
+                g_object_unref (stream);
+
+                return;
+        }
+
+        ostream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
+        g_output_stream_splice_async (ostream,
+                                      stream,
+                                      G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
+                                      G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+                                      g_task_get_priority (task),
+                                      g_task_get_cancellable (task),
+                                      (GAsyncReadyCallback)session_get_async_splice_ready_cb,
+                                      task);
+        g_object_unref (ostream);
+        g_object_unref (stream);
+}
+
+/**
+ * soup_session_get_bytes_async:
+ * @session: a #SoupSession
+ * @uri: a URI, in string form
+ * @io_priority: the I/O priority of the request
+ * @cancellable: a #GCancellable
+ * @callback: the callback to invoke
+ * @user_data: data for @callback
+ *
+ * Asynchronously retrieve @uri to be returned as a #GBytes. This function
+ * is like soup_session_get_async() but the contents are read and returned
+ * as a #GBytes. It should only be used when the resource to be retireved
+ * is not too long and can be stored in memory.
+ *
+ * This function supports several URI schemes (http, https, file, resource and data). If
+ * an unsupported URI is passed it will fail with %SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME
+ * error.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call soup_session_get_bytes_finish() to get the result of the operation.
+ */
+void
+soup_session_get_bytes_async (SoupSession        *session,
+                              const char         *uri,
+                              int                 io_priority,
+                              GCancellable       *cancellable,
+                              GAsyncReadyCallback callback,
+                              gpointer            user_data)
+{
+        GTask *task;
+
+        g_return_if_fail (SOUP_IS_SESSION (session));
+        g_return_if_fail (uri != NULL);
+
+        task = g_task_new (session, cancellable, callback, user_data);
+        g_task_set_priority (task, io_priority);
+        soup_session_get_async (session, uri, io_priority, cancellable,
+                                (GAsyncReadyCallback)session_get_async_ready_cb,
+                                task);
+}
+
+/**
+ * soup_session_get_bytes_finish:
+ * @session: a #SoupSession
+ * @result: the #GAsyncResult passed to your callback
+ * @content_type: (out) (nullable) (transfer full): location to store content type, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finish an asynchronous operation started by soup_session_get_bytes_async().
+ *
+ * Returns: (transfer full): a #GBytes with the contents, or %NULL in case of error.
+ */
+GBytes *
+soup_session_get_bytes_finish (SoupSession  *session,
+                               GAsyncResult *result,
+                               char        **content_type,
+                               GError      **error)
+{
+        GTask *task;
+
+        g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
+        g_return_val_if_fail (g_task_is_valid (result, session), NULL);
+
+        task = G_TASK (result);
+
+        if (!g_task_had_error (task) && content_type)
+                *content_type = g_strdup (g_task_get_task_data (task));
+
+        return g_task_propagate_pointer (task, error);
+}
+
+/**
+ * soup_session_get_bytes:
+ * @session: a #SoupSession
+ * @uri: a URI, in string form
+ * @cancellable: a #GCancellable
+ * @content_type: (out) (nullable) (transfer full): location to store content type, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Synchronously retrieve @uri to be returned as a #GBytes. This function
+ * is like soup_session_get() but the contents are read and returned
+ * as a #GBytes. It should only be used when the resource to be retireved
+ * is not too long and can be stored in memory.
+ *
+ * This function supports several URI schemes (http, https, file, resource and data). If
+ * an unsupported URI is passed it will fail with %SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME
+ * error.
+ *
+ * Returns: (transfer full): a #GBytes with the contents, or %NULL in case of error.
+ */
+GBytes *
+soup_session_get_bytes (SoupSession  *session,
+                        const char   *uri,
+                        GCancellable *cancellable,
+                        char        **content_type,
+                        GError      **error)
+{
+        GInputStream *stream;
+        GOutputStream *ostream;
+        goffset content_length;
+        GBytes *bytes = NULL;
+
+        g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
+        g_return_val_if_fail (uri != NULL, NULL);
+
+        stream = soup_session_get (session, uri, cancellable, &content_length, content_type, error);
+        if (!stream)
+                return NULL;
+
+        if (content_length == 0) {
+                g_object_unref (stream);
+
+                return g_bytes_new_static (NULL, 0);
+        }
+
+        ostream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
+        if (g_output_stream_splice (ostream,
+                                    stream,
+                                    G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
+                                    G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+                                    cancellable, error) != -1) {
+                bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (ostream));
+        }
+        g_object_unref (ostream);
+        g_object_unref (stream);
+
+        return bytes;
+}
+
 /**
  * soup_session_request:
  * @session: a #SoupSession
diff --git a/libsoup/soup-session.h b/libsoup/soup-session.h
index c066212c..9a5a5521 100644
--- a/libsoup/soup-session.h
+++ b/libsoup/soup-session.h
@@ -34,6 +34,17 @@ G_DECLARE_FINAL_TYPE (SoupSession, soup_session, SOUP, SESSION, GObject)
 #define SOUP_SESSION_HTTP_ALIASES       "http-aliases"
 #define SOUP_SESSION_HTTPS_ALIASES      "https-aliases"
 
+SOUP_AVAILABLE_IN_ALL
+GQuark soup_session_error_quark (void);
+#define SOUP_SESSION_ERROR soup_session_error_quark ()
+
+typedef enum {
+       SOUP_SESSION_ERROR_BAD_URI,
+       SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME,
+       SOUP_SESSION_ERROR_PARSING,
+       SOUP_SESSION_ERROR_ENCODING
+} SoupSessionError;
+
 SOUP_AVAILABLE_IN_2_42
 SoupSession    *soup_session_new              (void);
 
@@ -110,6 +121,45 @@ SoupSessionFeature *soup_session_get_feature_for_message(SoupSession        *ses
                                                         GType               feature_type,
                                                         SoupMessage        *msg);
 
+SOUP_AVAILABLE_IN_ALL
+GInputStream       *soup_session_get                    (SoupSession        *session,
+                                                        const char         *uri,
+                                                        GCancellable       *cancellable,
+                                                        goffset            *content_length,
+                                                        char              **content_type,
+                                                        GError            **error);
+SOUP_AVAILABLE_IN_ALL
+void                soup_session_get_async              (SoupSession        *session,
+                                                        const char         *uri,
+                                                        int                 io_priority,
+                                                        GCancellable       *cancellable,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer            user_data);
+SOUP_AVAILABLE_IN_ALL
+GInputStream       *soup_session_get_finish             (SoupSession        *session,
+                                                        GAsyncResult       *result,
+                                                        goffset            *content_length,
+                                                        char              **content_type,
+                                                        GError            **error);
+SOUP_AVAILABLE_IN_ALL
+GBytes             *soup_session_get_bytes              (SoupSession        *session,
+                                                        const char         *uri,
+                                                        GCancellable       *cancellable,
+                                                        char              **content_type,
+                                                        GError            **error);
+SOUP_AVAILABLE_IN_ALL
+void                soup_session_get_bytes_async        (SoupSession        *session,
+                                                        const char         *uri,
+                                                        int                 io_priority,
+                                                        GCancellable       *cancellable,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer            user_data);
+SOUP_AVAILABLE_IN_ALL
+GBytes             *soup_session_get_bytes_finish       (SoupSession        *session,
+                                                        GAsyncResult       *result,
+                                                        char              **content_type,
+                                                        GError            **error);
+
 SOUP_AVAILABLE_IN_2_42
 SoupRequest     *soup_session_request          (SoupSession  *session,
                                                const char   *uri_string,
diff --git a/tests/session-test.c b/tests/session-test.c
index 1e8d2b14..1417c62e 100644
--- a/tests/session-test.c
+++ b/tests/session-test.c
@@ -2,10 +2,12 @@
 
 #include "test-utils.h"
 
+static SoupURI *base_uri;
 static gboolean server_processed_message;
 static gboolean timeout;
 static GMainLoop *loop;
 static SoupMessagePriority expected_priorities[3];
+static GBytes *index_bytes;
 
 static gboolean
 timeout_cb (gpointer user_data)
@@ -31,6 +33,13 @@ server_handler (SoupServer        *server,
                g_source_set_callback (timer, timeout_cb, &timeout, NULL);
                g_source_attach (timer, context);
                g_source_unref (timer);
+       } else if (!strcmp (path, "/index.txt")) {
+               soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL);
+               soup_server_message_set_response (msg, "text/plain",
+                                                 SOUP_MEMORY_STATIC,
+                                                 g_bytes_get_data (index_bytes, NULL),
+                                                 g_bytes_get_size (index_bytes));
+               return;
        } else
                server_processed_message = TRUE;
 
@@ -55,7 +64,7 @@ cancel_message_cb (SoupMessage *msg, gpointer session)
 }
 
 static void
-do_test_for_session (SoupSession *session, SoupURI *uri,
+do_test_for_session (SoupSession *session,
                     gboolean queue_is_async,
                     gboolean send_is_blocking,
                     gboolean cancel_is_immediate)
@@ -68,14 +77,14 @@ do_test_for_session (SoupSession *session, SoupURI *uri,
 
        debug_printf (1, "  queue_message\n");
        debug_printf (2, "    requesting timeout\n");
-       timeout_uri = soup_uri_new_with_base (uri, "/request-timeout");
+       timeout_uri = soup_uri_new_with_base (base_uri, "/request-timeout");
        msg = soup_message_new_from_uri ("GET", timeout_uri);
        soup_uri_free (timeout_uri);
        body = soup_test_session_send (session, msg, NULL, NULL);
        g_bytes_unref (body);
        g_object_unref (msg);
 
-       msg = soup_message_new_from_uri ("GET", uri);
+       msg = soup_message_new_from_uri ("GET", base_uri);
        server_processed_message = timeout = finished = FALSE;
        g_signal_connect (msg, "finished",
                          G_CALLBACK (finished_cb), &finished);
@@ -100,7 +109,7 @@ do_test_for_session (SoupSession *session, SoupURI *uri,
        }
 
        debug_printf (1, "  send_message\n");
-       msg = soup_message_new_from_uri ("GET", uri);
+       msg = soup_message_new_from_uri ("GET", base_uri);
        server_processed_message = local_timeout = FALSE;
        timeout_id = g_idle_add_full (G_PRIORITY_HIGH, timeout_cb, &local_timeout, NULL);
        body = soup_test_session_send (session, msg, NULL, NULL);
@@ -124,7 +133,7 @@ do_test_for_session (SoupSession *session, SoupURI *uri,
                return;
 
        debug_printf (1, "  cancel_message\n");
-       msg = soup_message_new_from_uri ("GET", uri);
+       msg = soup_message_new_from_uri ("GET", base_uri);
        finished = FALSE;
        g_signal_connect (msg, "finished",
                          G_CALLBACK (finished_cb), &finished);
@@ -156,13 +165,12 @@ do_test_for_session (SoupSession *session, SoupURI *uri,
 }
 
 static void
-do_plain_tests (gconstpointer data)
+do_plain_tests (void)
 {
-       SoupURI *uri = (SoupURI *)data;
        SoupSession *session;
 
        session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
-       do_test_for_session (session, uri, TRUE, TRUE, FALSE);
+       do_test_for_session (session, TRUE, TRUE, FALSE);
        soup_test_session_abort_unref (session);
 }
 
@@ -183,9 +191,8 @@ priority_test_finished_cb (SoupMessage *msg,
 }
 
 static void
-do_priority_tests (gconstpointer data)
+do_priority_tests (void)
 {
-       SoupURI *uri = (SoupURI *)data;
        SoupSession *session;
        int i, finished_count = 0;
        SoupMessagePriority priorities[] =
@@ -208,7 +215,7 @@ do_priority_tests (gconstpointer data)
                char buf[5];
 
                g_snprintf (buf, sizeof (buf), "%d", i);
-               msg_uri = soup_uri_new_with_base (uri, buf);
+               msg_uri = soup_uri_new_with_base (base_uri, buf);
                msg = soup_message_new_from_uri ("GET", msg_uri);
                soup_uri_free (msg_uri);
 
@@ -369,27 +376,346 @@ do_features_test (void)
        soup_test_session_abort_unref (session);
 }
 
+typedef enum {
+       SYNC = 1 << 0,
+       STREAM = 1 << 1
+} GetTestFlags;
+
+typedef struct {
+       GMainLoop *loop;
+       GBytes *body;
+       char *content_type;
+       GError *error;
+} GetAsyncData;
+
+static GBytes *
+stream_to_bytes (GInputStream *stream)
+{
+       GOutputStream *ostream;
+       GBytes *bytes;
+
+       ostream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
+       g_output_stream_splice (ostream,
+                               stream,
+                               G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
+                               G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+                               NULL, NULL);
+       bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (ostream));
+       g_object_unref (ostream);
+
+       return bytes;
+}
+
+static void
+get_async_ready_cb (SoupSession  *session,
+                   GAsyncResult *result,
+                   GetAsyncData *data)
+{
+       GInputStream *stream;
+       goffset content_length;
+
+       stream = soup_session_get_finish (session, result,
+                                         &content_length,
+                                         &data->content_type,
+                                         &data->error);
+       if (stream) {
+               data->body = stream_to_bytes (stream);
+               if (content_length != -1)
+                       g_assert_cmpint (g_bytes_get_size (data->body), ==, content_length);
+               g_object_unref (stream);
+       }
+
+       g_main_loop_quit (data->loop);
+}
+
+static void
+get_bytes_async_ready_cb (SoupSession  *session,
+                         GAsyncResult *result,
+                         GetAsyncData *data)
+{
+       data->body = soup_session_get_bytes_finish (session, result,
+                                                   &data->content_type,
+                                                   &data->error);
+       g_main_loop_quit (data->loop);
+}
+
+static void
+do_get_test (SoupSession *session,
+            const char  *uri,
+            gboolean     stream,
+            gboolean     sync,
+            gboolean     is_dir)
+{
+       GBytes *body = NULL;
+       char *content_type = NULL;
+       GError *error = NULL;
+
+       if (!sync) {
+               GetAsyncData data;
+               GMainContext *context;
+
+               memset (&data, 0, sizeof (GetAsyncData));
+
+               context = g_main_context_get_thread_default ();
+               data.loop = g_main_loop_new (context, TRUE);
+               if (stream) {
+                       soup_session_get_async (session, uri, G_PRIORITY_DEFAULT, NULL,
+                                               (GAsyncReadyCallback)get_async_ready_cb,
+                                               &data);
+               } else {
+                       soup_session_get_bytes_async (session, uri, G_PRIORITY_DEFAULT, NULL,
+                                                     (GAsyncReadyCallback)get_bytes_async_ready_cb,
+                                                      &data);
+               }
+               g_main_loop_run (data.loop);
+               while (g_main_context_pending (context))
+                       g_main_context_iteration (context, FALSE);
+               g_main_loop_unref (data.loop);
+
+               body = data.body;
+               content_type = data.content_type;
+               if (data.error)
+                       g_propagate_error (&error, data.error);
+       } else {
+               if (stream) {
+                       GInputStream *stream;
+                       goffset content_length = 0;
+
+                       stream = soup_session_get (session, uri, NULL,
+                                                  &content_length,
+                                                  &content_type,
+                                                  &error);
+                       body = stream_to_bytes (stream);
+                       if (content_length != -1)
+                               g_assert_cmpint (g_bytes_get_size (body), ==, content_length);
+                       g_object_unref (stream);
+               } else {
+                       body = soup_session_get_bytes (session, uri, NULL,
+                                                      &content_type, &error);
+               }
+       }
+
+       g_assert_no_error (error);
+       g_assert_nonnull (body);
+
+       if (is_dir) {
+               g_assert_cmpstr (content_type, ==, "text/html");
+               g_assert_cmpint (g_bytes_get_size (body), >, 0);
+       } else {
+               g_assert_cmpstr (content_type, ==, "text/plain");
+               g_assert_cmpmem (g_bytes_get_data (body, NULL), g_bytes_get_size (body),
+                                g_bytes_get_data (index_bytes, NULL), g_bytes_get_size (index_bytes));
+       }
+
+       g_bytes_unref (body);
+       g_free (content_type);
+}
+
+static void
+do_get_http_test (gconstpointer data)
+{
+       SoupURI *uri;
+       char *uri_string;
+       SoupSession *session;
+       GetTestFlags flags = GPOINTER_TO_UINT (data);
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
+
+       uri = soup_uri_new_with_base (base_uri, "/index.txt");
+       uri_string = soup_uri_to_string (uri, FALSE);
+       do_get_test (session, uri_string, flags & STREAM, flags & SYNC, FALSE);
+       g_free (uri_string);
+       soup_uri_free (uri);
+
+       soup_test_session_abort_unref (session);
+}
+
+static void
+do_get_file_test (gconstpointer data)
+{
+       GFile *file;
+       char *uri;
+       SoupSession *session;
+       GetTestFlags flags = GPOINTER_TO_UINT (data);
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
+
+       file = g_file_new_for_path (g_test_get_filename (G_TEST_DIST, "index.txt", NULL));
+       uri = g_file_get_uri (file);
+       do_get_test (session, uri, flags & STREAM, flags & SYNC, FALSE);
+       g_free (uri);
+       g_object_unref (file);
+
+       soup_test_session_abort_unref (session);
+}
+
+static void
+do_get_dir_test (gconstpointer data)
+{
+       GFile *file;
+       char *uri;
+       SoupSession *session;
+       GetTestFlags flags = GPOINTER_TO_UINT (data);
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
+
+       file = g_file_new_for_path (g_test_get_dir (G_TEST_DIST));
+       uri = g_file_get_uri (file);
+       do_get_test (session, uri, flags & STREAM, flags & SYNC, TRUE);
+       g_free (uri);
+       g_object_unref (file);
+
+       soup_test_session_abort_unref (session);
+}
+
+static void
+do_get_resource_test (gconstpointer data)
+{
+       SoupSession *session;
+       GetTestFlags flags = GPOINTER_TO_UINT (data);
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
+
+       do_get_test (session, "resource:///org/gnome/libsoup/tests/index.txt",
+                    flags & STREAM, flags & SYNC, FALSE);
+
+       soup_test_session_abort_unref (session);
+}
+
+static void
+do_get_data_test (gconstpointer data)
+{
+       SoupSession *session;
+       char *base64;
+       char *uri;
+       GetTestFlags flags = GPOINTER_TO_UINT (data);
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
+
+       base64 = g_base64_encode ((const guchar *)g_bytes_get_data (index_bytes, NULL),
+                                 g_bytes_get_size (index_bytes));
+        uri = g_strdup_printf ("data:text/plain;base64,%s", base64);
+       do_get_test (session, uri, flags & STREAM, flags & SYNC, FALSE);
+       g_free (uri);
+        g_free (base64);
+
+       soup_test_session_abort_unref (session);
+}
+
+static struct {
+       const char *uri;
+       int expected_error;
+} get_error_tests[] = {
+       { "./foo", SOUP_SESSION_ERROR_BAD_URI },
+       { "http:/localhost/", SOUP_SESSION_ERROR_BAD_URI },
+       { "file://foo/", SOUP_SESSION_ERROR_BAD_URI },
+       { "resource://foo/", SOUP_SESSION_ERROR_BAD_URI },
+       { "data://text/plain,foo", SOUP_SESSION_ERROR_BAD_URI },
+       { "foo://host/path", SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME }
+};
+
+static void
+do_get_error_tests (void)
+{
+       SoupSession *session;
+       guint i;
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
+
+       for (i = 0; i < G_N_ELEMENTS (get_error_tests); i++) {
+               GError *error = NULL;
+
+               g_assert_null (soup_session_get (session, get_error_tests[i].uri, NULL, NULL, NULL, &error));
+               g_assert_error (error, SOUP_SESSION_ERROR, get_error_tests[i].expected_error);
+               g_error_free (error);
+       }
+
+       soup_test_session_abort_unref (session);
+}
+
 int
 main (int argc, char **argv)
 {
        SoupServer *server;
-       SoupURI *uri;
        int ret;
 
        test_init (argc, argv, NULL);
 
        server = soup_test_server_new (TRUE);
        soup_server_add_handler (server, NULL, server_handler, NULL, NULL);
-       uri = soup_test_server_get_uri (server, "http", NULL);
+       base_uri = soup_test_server_get_uri (server, "http", NULL);
+       index_bytes = soup_test_get_index ();
+       soup_test_register_resources ();
 
-       g_test_add_data_func ("/session/SoupSession", uri, do_plain_tests);
-       g_test_add_data_func ("/session/priority", uri, do_priority_tests);
+       g_test_add_func ("/session/SoupSession", do_plain_tests);
+       g_test_add_func ("/session/priority", do_priority_tests);
        g_test_add_func ("/session/property", do_property_tests);
        g_test_add_func ("/session/features", do_features_test);
+       g_test_add_data_func ("/session/get-stream/async/http",
+                             GINT_TO_POINTER (STREAM),
+                             do_get_http_test);
+       g_test_add_data_func ("/session/get-stream/async/file",
+                             GINT_TO_POINTER (STREAM),
+                             do_get_file_test);
+       g_test_add_data_func ("/session/get-stream/async/dir",
+                             GINT_TO_POINTER (STREAM),
+                             do_get_dir_test);
+       g_test_add_data_func ("/session/get-stream/async/resource",
+                             GINT_TO_POINTER (STREAM),
+                             do_get_resource_test);
+       g_test_add_data_func ("/session/get-stream/async/data",
+                             GINT_TO_POINTER (STREAM),
+                             do_get_data_test);
+       g_test_add_data_func ("/session/get-stream/sync/http",
+                             GINT_TO_POINTER (SYNC | STREAM),
+                             do_get_http_test);
+       g_test_add_data_func ("/session/get-stream/sync/file",
+                             GINT_TO_POINTER (SYNC | STREAM),
+                             do_get_file_test);
+       g_test_add_data_func ("/session/get-stream/sync/dir",
+                             GINT_TO_POINTER (SYNC | STREAM),
+                             do_get_dir_test);
+       g_test_add_data_func ("/session/get-stream/sync/resource",
+                             GINT_TO_POINTER (SYNC | STREAM),
+                             do_get_resource_test);
+       g_test_add_data_func ("/session/get-stream/sync/data",
+                             GINT_TO_POINTER (SYNC | STREAM),
+                             do_get_data_test);
+       g_test_add_data_func ("/session/get-bytes/async/http",
+                             GINT_TO_POINTER (0),
+                             do_get_http_test);
+       g_test_add_data_func ("/session/get-bytes/async/file",
+                             GINT_TO_POINTER (0),
+                             do_get_file_test);
+       g_test_add_data_func ("/session/get-bytes/async/dir",
+                             GINT_TO_POINTER (0),
+                             do_get_dir_test);
+       g_test_add_data_func ("/session/get-bytes/async/resource",
+                             GINT_TO_POINTER (0),
+                             do_get_resource_test);
+       g_test_add_data_func ("/session/get-bytes/async/data",
+                             GINT_TO_POINTER (0),
+                             do_get_data_test);
+       g_test_add_data_func ("/session/get-bytes/sync/http",
+                             GINT_TO_POINTER (SYNC),
+                             do_get_http_test);
+       g_test_add_data_func ("/session/get-bytes/sync/file",
+                             GINT_TO_POINTER (SYNC),
+                             do_get_file_test);
+       g_test_add_data_func ("/session/get-bytes/sync/dir",
+                             GINT_TO_POINTER (SYNC),
+                             do_get_dir_test);
+       g_test_add_data_func ("/session/get-bytes/sync/resource",
+                             GINT_TO_POINTER (SYNC),
+                             do_get_resource_test);
+       g_test_add_data_func ("/session/get-bytes/sync/data",
+                             GINT_TO_POINTER (SYNC),
+                             do_get_data_test);
+       g_test_add_func ("/session/get/errors", do_get_error_tests);
 
        ret = g_test_run ();
 
-       soup_uri_free (uri);
+       soup_uri_free (base_uri);
        soup_test_server_quit_unref (server);
 
        test_cleanup ();


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