diff --git a/common/Makefile.am.inc b/common/Makefile.am.inc index 06359d91..d39d6513 100644 --- a/common/Makefile.am.inc +++ b/common/Makefile.am.inc @@ -94,6 +94,8 @@ libflatpak_common_la_SOURCES = \ common/flatpak-transaction.h \ common/flatpak-transaction.c \ common/flatpak-utils.c \ + common/flatpak-utils-http.c \ + common/flatpak-utils-http-private.h \ common/flatpak-utils-private.h \ common/flatpak-chain-input-stream.c \ common/flatpak-chain-input-stream-private.h \ diff --git a/common/flatpak-utils-http-private.h b/common/flatpak-utils-http-private.h new file mode 100644 index 00000000..6b0e3bf2 --- /dev/null +++ b/common/flatpak-utils-http-private.h @@ -0,0 +1,56 @@ +/* + * Copyright © 2014 Red Hat, Inc + * + * This program 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 . + * + * Authors: + * Alexander Larsson + */ + +#ifndef __FLATPAK_UTILS_HTTP_H__ +#define __FLATPAK_UTILS_HTTP_H__ + +#include + +#include + +SoupSession * flatpak_create_soup_session (const char *user_agent); + +typedef enum { + FLATPAK_HTTP_FLAGS_NONE = 0, + FLATPAK_HTTP_FLAGS_ACCEPT_OCI = 1 << 0, +} FlatpakHTTPFlags; + +typedef void (*FlatpakLoadUriProgress) (guint64 downloaded_bytes, + gpointer user_data); + +GBytes * flatpak_load_http_uri (SoupSession *soup_session, + const char *uri, + FlatpakHTTPFlags flags, + const char *etag, + char **out_etag, + FlatpakLoadUriProgress progress, + gpointer user_data, + GCancellable *cancellable, + GError **error); +gboolean flatpak_download_http_uri (SoupSession *soup_session, + const char *uri, + FlatpakHTTPFlags flags, + GOutputStream *out, + FlatpakLoadUriProgress progress, + gpointer user_data, + GCancellable *cancellable, + GError **error); + +#endif /* __FLATPAK_UTILS_HTTP_H__ */ diff --git a/common/flatpak-utils-http.c b/common/flatpak-utils-http.c new file mode 100644 index 00000000..63c1c6ec --- /dev/null +++ b/common/flatpak-utils-http.c @@ -0,0 +1,308 @@ +/* + * Copyright © 2014 Red Hat, Inc + * + * This program 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 . + * + * Authors: + * Alexander Larsson + */ + +#include "flatpak-utils-http-private.h" +#include "flatpak-oci-registry-private.h" + +#include + +typedef struct +{ + GMainLoop *loop; + GError *error; + GOutputStream *out; + guint64 downloaded_bytes; + GString *content; + char buffer[16 * 1024]; + FlatpakLoadUriProgress progress; + GCancellable *cancellable; + gpointer user_data; + guint64 last_progress_time; + char *etag; +} LoadUriData; + +static void +stream_closed (GObject *source, GAsyncResult *res, gpointer user_data) +{ + LoadUriData *data = user_data; + GInputStream *stream = G_INPUT_STREAM (source); + + g_autoptr(GError) error = NULL; + + if (!g_input_stream_close_finish (stream, res, &error)) + g_warning ("Error closing http stream: %s", error->message); + + g_main_loop_quit (data->loop); +} + +static void +load_uri_read_cb (GObject *source, GAsyncResult *res, gpointer user_data) +{ + LoadUriData *data = user_data; + GInputStream *stream = G_INPUT_STREAM (source); + gsize nread; + + nread = g_input_stream_read_finish (stream, res, &data->error); + if (nread == -1 || nread == 0) + { + if (data->progress) + data->progress (data->downloaded_bytes, data->user_data); + g_input_stream_close_async (stream, + G_PRIORITY_DEFAULT, NULL, + stream_closed, data); + return; + } + + if (data->out != NULL) + { + gsize n_written; + + if (!g_output_stream_write_all (data->out, data->buffer, nread, &n_written, + NULL, &data->error)) + { + data->downloaded_bytes += n_written; + g_input_stream_close_async (stream, + G_PRIORITY_DEFAULT, NULL, + stream_closed, data); + return; + } + + data->downloaded_bytes += n_written; + } + else + { + data->downloaded_bytes += nread; + g_string_append_len (data->content, data->buffer, nread); + } + + if (g_get_monotonic_time () - data->last_progress_time > 1 * G_USEC_PER_SEC) + { + if (data->progress) + data->progress (data->downloaded_bytes, data->user_data); + data->last_progress_time = g_get_monotonic_time (); + } + + g_input_stream_read_async (stream, data->buffer, sizeof (data->buffer), + G_PRIORITY_DEFAULT, data->cancellable, + load_uri_read_cb, data); +} + +static void +load_uri_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SoupRequestHTTP *request = SOUP_REQUEST_HTTP (source_object); + + g_autoptr(GInputStream) in = NULL; + LoadUriData *data = user_data; + + in = soup_request_send_finish (SOUP_REQUEST (request), res, &data->error); + if (in == NULL) + { + g_main_loop_quit (data->loop); + return; + } + + g_autoptr(SoupMessage) msg = soup_request_http_get_message ((SoupRequestHTTP *) request); + if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) + { + int code; + GQuark domain = G_IO_ERROR; + + switch (msg->status_code) + { + case 304: + domain = FLATPAK_OCI_ERROR; + code = FLATPAK_OCI_ERROR_NOT_CHANGED; + break; + + case 404: + case 410: + code = G_IO_ERROR_NOT_FOUND; + break; + + default: + code = G_IO_ERROR_FAILED; + } + + data->error = g_error_new (domain, code, + "Server returned status %u: %s", + msg->status_code, + soup_status_get_phrase (msg->status_code)); + g_main_loop_quit (data->loop); + return; + } + + data->etag = g_strdup (soup_message_headers_get_one (msg->response_headers, "ETag")); + + g_input_stream_read_async (in, data->buffer, sizeof (data->buffer), + G_PRIORITY_DEFAULT, data->cancellable, + load_uri_read_cb, data); +} + +SoupSession * +flatpak_create_soup_session (const char *user_agent) +{ + SoupSession *soup_session; + const char *http_proxy; + + soup_session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, user_agent, + SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + SOUP_SESSION_TIMEOUT, 60, + SOUP_SESSION_IDLE_TIMEOUT, 60, + NULL); + soup_session_remove_feature_by_type (soup_session, SOUP_TYPE_CONTENT_DECODER); + http_proxy = g_getenv ("http_proxy"); + if (http_proxy) + { + g_autoptr(SoupURI) proxy_uri = soup_uri_new (http_proxy); + if (!proxy_uri) + g_warning ("Invalid proxy URI '%s'", http_proxy); + else + g_object_set (soup_session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL); + } + + return soup_session; +} + +GBytes * +flatpak_load_http_uri (SoupSession *soup_session, + const char *uri, + FlatpakHTTPFlags flags, + const char *etag, + char **out_etag, + FlatpakLoadUriProgress progress, + gpointer user_data, + GCancellable *cancellable, + GError **error) +{ + GBytes *bytes = NULL; + + g_autoptr(GMainContext) context = NULL; + g_autoptr(SoupRequestHTTP) request = NULL; + g_autoptr(GMainLoop) loop = NULL; + g_autoptr(GString) content = g_string_new (""); + LoadUriData data = { NULL }; + SoupMessage *m; + + g_debug ("Loading %s using libsoup", uri); + + context = g_main_context_ref_thread_default (); + + loop = g_main_loop_new (context, TRUE); + data.loop = loop; + data.content = content; + data.progress = progress; + data.cancellable = cancellable; + data.user_data = user_data; + data.last_progress_time = g_get_monotonic_time (); + + request = soup_session_request_http (soup_session, "GET", + uri, error); + if (request == NULL) + return NULL; + + m = soup_request_http_get_message (request); + if (etag) + soup_message_headers_replace (m->request_headers, "If-None-Match", etag); + + if (flags & FLATPAK_HTTP_FLAGS_ACCEPT_OCI) + soup_message_headers_replace (m->request_headers, "Accept", + "application/vnd.oci.image.manifest.v1+json"); + + soup_request_send_async (SOUP_REQUEST (request), + cancellable, + load_uri_callback, &data); + + g_main_loop_run (loop); + + if (data.error) + { + g_propagate_error (error, data.error); + g_free (data.etag); + return NULL; + } + + bytes = g_string_free_to_bytes (g_steal_pointer (&content)); + g_debug ("Received %" G_GUINT64_FORMAT " bytes", data.downloaded_bytes); + + if (out_etag) + *out_etag = g_steal_pointer (&data.etag); + + g_free (data.etag); + + return bytes; +} + +gboolean +flatpak_download_http_uri (SoupSession *soup_session, + const char *uri, + FlatpakHTTPFlags flags, + GOutputStream *out, + FlatpakLoadUriProgress progress, + gpointer user_data, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(SoupRequestHTTP) request = NULL; + g_autoptr(GMainLoop) loop = NULL; + g_autoptr(GMainContext) context = NULL; + LoadUriData data = { NULL }; + SoupMessage *m; + + g_debug ("Loading %s using libsoup", uri); + + context = g_main_context_ref_thread_default (); + + loop = g_main_loop_new (context, TRUE); + data.loop = loop; + data.out = out; + data.progress = progress; + data.cancellable = cancellable; + data.user_data = user_data; + data.last_progress_time = g_get_monotonic_time (); + + request = soup_session_request_http (soup_session, "GET", + uri, error); + if (request == NULL) + return FALSE; + + if (flags & FLATPAK_HTTP_FLAGS_ACCEPT_OCI) + soup_message_headers_replace (m->request_headers, "Accept", + "application/vnd.oci.image.manifest.v1+json"); + + soup_request_send_async (SOUP_REQUEST (request), + cancellable, + load_uri_callback, &data); + + g_main_loop_run (loop); + + if (data.error) + { + g_propagate_error (error, data.error); + return FALSE; + } + + g_debug ("Received %" G_GUINT64_FORMAT " bytes", data.downloaded_bytes); + + return TRUE; +} diff --git a/common/flatpak-utils-private.h b/common/flatpak-utils-private.h index 617e5dc8..5620c5a3 100644 --- a/common/flatpak-utils-private.h +++ b/common/flatpak-utils-private.h @@ -27,11 +27,11 @@ #include #include #include -#include #include "flatpak-dbus-generated.h" #include "flatpak-document-dbus-generated.h" #include "flatpak-context-private.h" #include "flatpak-error.h" +#include "flatpak-utils-http-private.h" #include #include @@ -41,9 +41,6 @@ typedef enum { FLATPAK_HOST_COMMAND_FLAGS_CLEAR_ENV = 1 << 0, } FlatpakHostCommandFlags; -typedef void (*FlatpakLoadUriProgress) (guint64 downloaded_bytes, - gpointer user_data); - #define FLATPAK_ANSI_BOLD_ON "\x1b[1m" #define FLATPAK_ANSI_BOLD_OFF "\x1b[22m" @@ -694,31 +691,6 @@ long flatpak_number_prompt (int min, const char *prompt, ...) G_GNUC_PRINTF (3, 4); -SoupSession * flatpak_create_soup_session (const char *user_agent); - -typedef enum { - FLATPAK_HTTP_FLAGS_NONE = 0, - FLATPAK_HTTP_FLAGS_ACCEPT_OCI = 1 << 0, -} FlatpakHTTPFlags; - -GBytes * flatpak_load_http_uri (SoupSession *soup_session, - const char *uri, - FlatpakHTTPFlags flags, - const char *etag, - char **out_etag, - FlatpakLoadUriProgress progress, - gpointer user_data, - GCancellable *cancellable, - GError **error); -gboolean flatpak_download_http_uri (SoupSession *soup_session, - const char *uri, - FlatpakHTTPFlags flags, - GOutputStream *out, - FlatpakLoadUriProgress progress, - gpointer user_data, - GCancellable *cancellable, - GError **error); - typedef void (*FlatpakProgressCallback)(const char *status, guint progress, gboolean estimating, diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c index 521c325e..dc1f3c9a 100644 --- a/common/flatpak-utils.c +++ b/common/flatpak-utils.c @@ -45,7 +45,6 @@ #include #include "libglnx/libglnx.h" -#include #include #include @@ -5293,292 +5292,6 @@ flatpak_number_prompt (int min, int max, const char *prompt, ...) } } - -typedef struct -{ - GMainLoop *loop; - GError *error; - GOutputStream *out; - guint64 downloaded_bytes; - GString *content; - char buffer[16 * 1024]; - FlatpakLoadUriProgress progress; - GCancellable *cancellable; - gpointer user_data; - guint64 last_progress_time; - char *etag; -} LoadUriData; - -static void -stream_closed (GObject *source, GAsyncResult *res, gpointer user_data) -{ - LoadUriData *data = user_data; - GInputStream *stream = G_INPUT_STREAM (source); - - g_autoptr(GError) error = NULL; - - if (!g_input_stream_close_finish (stream, res, &error)) - g_warning ("Error closing http stream: %s", error->message); - - g_main_loop_quit (data->loop); -} - -static void -load_uri_read_cb (GObject *source, GAsyncResult *res, gpointer user_data) -{ - LoadUriData *data = user_data; - GInputStream *stream = G_INPUT_STREAM (source); - gsize nread; - - nread = g_input_stream_read_finish (stream, res, &data->error); - if (nread == -1 || nread == 0) - { - if (data->progress) - data->progress (data->downloaded_bytes, data->user_data); - g_input_stream_close_async (stream, - G_PRIORITY_DEFAULT, NULL, - stream_closed, data); - return; - } - - if (data->out != NULL) - { - gsize n_written; - - if (!g_output_stream_write_all (data->out, data->buffer, nread, &n_written, - NULL, &data->error)) - { - data->downloaded_bytes += n_written; - g_input_stream_close_async (stream, - G_PRIORITY_DEFAULT, NULL, - stream_closed, data); - return; - } - - data->downloaded_bytes += n_written; - } - else - { - data->downloaded_bytes += nread; - g_string_append_len (data->content, data->buffer, nread); - } - - if (g_get_monotonic_time () - data->last_progress_time > 1 * G_USEC_PER_SEC) - { - if (data->progress) - data->progress (data->downloaded_bytes, data->user_data); - data->last_progress_time = g_get_monotonic_time (); - } - - g_input_stream_read_async (stream, data->buffer, sizeof (data->buffer), - G_PRIORITY_DEFAULT, data->cancellable, - load_uri_read_cb, data); -} - -static void -load_uri_callback (GObject *source_object, - GAsyncResult *res, - gpointer user_data) -{ - SoupRequestHTTP *request = SOUP_REQUEST_HTTP (source_object); - - g_autoptr(GInputStream) in = NULL; - LoadUriData *data = user_data; - - in = soup_request_send_finish (SOUP_REQUEST (request), res, &data->error); - if (in == NULL) - { - g_main_loop_quit (data->loop); - return; - } - - g_autoptr(SoupMessage) msg = soup_request_http_get_message ((SoupRequestHTTP *) request); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) - { - int code; - GQuark domain = G_IO_ERROR; - - switch (msg->status_code) - { - case 304: - domain = FLATPAK_OCI_ERROR; - code = FLATPAK_OCI_ERROR_NOT_CHANGED; - break; - - case 404: - case 410: - code = G_IO_ERROR_NOT_FOUND; - break; - - default: - code = G_IO_ERROR_FAILED; - } - - data->error = g_error_new (domain, code, - "Server returned status %u: %s", - msg->status_code, - soup_status_get_phrase (msg->status_code)); - g_main_loop_quit (data->loop); - return; - } - - data->etag = g_strdup (soup_message_headers_get_one (msg->response_headers, "ETag")); - - g_input_stream_read_async (in, data->buffer, sizeof (data->buffer), - G_PRIORITY_DEFAULT, data->cancellable, - load_uri_read_cb, data); -} - -SoupSession * -flatpak_create_soup_session (const char *user_agent) -{ - SoupSession *soup_session; - const char *http_proxy; - - soup_session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, user_agent, - SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, - SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, - SOUP_SESSION_TIMEOUT, 60, - SOUP_SESSION_IDLE_TIMEOUT, 60, - NULL); - soup_session_remove_feature_by_type (soup_session, SOUP_TYPE_CONTENT_DECODER); - http_proxy = g_getenv ("http_proxy"); - if (http_proxy) - { - g_autoptr(SoupURI) proxy_uri = soup_uri_new (http_proxy); - if (!proxy_uri) - g_warning ("Invalid proxy URI '%s'", http_proxy); - else - g_object_set (soup_session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL); - } - - return soup_session; -} - -GBytes * -flatpak_load_http_uri (SoupSession *soup_session, - const char *uri, - FlatpakHTTPFlags flags, - const char *etag, - char **out_etag, - FlatpakLoadUriProgress progress, - gpointer user_data, - GCancellable *cancellable, - GError **error) -{ - GBytes *bytes = NULL; - - g_autoptr(GMainContext) context = NULL; - g_autoptr(SoupRequestHTTP) request = NULL; - g_autoptr(GMainLoop) loop = NULL; - g_autoptr(GString) content = g_string_new (""); - LoadUriData data = { NULL }; - SoupMessage *m; - - g_debug ("Loading %s using libsoup", uri); - - context = g_main_context_ref_thread_default (); - - loop = g_main_loop_new (context, TRUE); - data.loop = loop; - data.content = content; - data.progress = progress; - data.cancellable = cancellable; - data.user_data = user_data; - data.last_progress_time = g_get_monotonic_time (); - - request = soup_session_request_http (soup_session, "GET", - uri, error); - if (request == NULL) - return NULL; - - m = soup_request_http_get_message (request); - if (etag) - soup_message_headers_replace (m->request_headers, "If-None-Match", etag); - - if (flags & FLATPAK_HTTP_FLAGS_ACCEPT_OCI) - soup_message_headers_replace (m->request_headers, "Accept", - "application/vnd.oci.image.manifest.v1+json"); - - soup_request_send_async (SOUP_REQUEST (request), - cancellable, - load_uri_callback, &data); - - g_main_loop_run (loop); - - if (data.error) - { - g_propagate_error (error, data.error); - g_free (data.etag); - return NULL; - } - - bytes = g_string_free_to_bytes (g_steal_pointer (&content)); - g_debug ("Received %" G_GUINT64_FORMAT " bytes", data.downloaded_bytes); - - if (out_etag) - *out_etag = g_steal_pointer (&data.etag); - - g_free (data.etag); - - return bytes; -} - -gboolean -flatpak_download_http_uri (SoupSession *soup_session, - const char *uri, - FlatpakHTTPFlags flags, - GOutputStream *out, - FlatpakLoadUriProgress progress, - gpointer user_data, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(SoupRequestHTTP) request = NULL; - g_autoptr(GMainLoop) loop = NULL; - g_autoptr(GMainContext) context = NULL; - LoadUriData data = { NULL }; - SoupMessage *m; - - g_debug ("Loading %s using libsoup", uri); - - context = g_main_context_ref_thread_default (); - - loop = g_main_loop_new (context, TRUE); - data.loop = loop; - data.out = out; - data.progress = progress; - data.cancellable = cancellable; - data.user_data = user_data; - data.last_progress_time = g_get_monotonic_time (); - - request = soup_session_request_http (soup_session, "GET", - uri, error); - if (request == NULL) - return FALSE; - - m = soup_request_http_get_message (request); - if (flags & FLATPAK_HTTP_FLAGS_ACCEPT_OCI) - soup_message_headers_replace (m->request_headers, "Accept", - "application/vnd.oci.image.manifest.v1+json"); - - soup_request_send_async (SOUP_REQUEST (request), - cancellable, - load_uri_callback, &data); - - g_main_loop_run (loop); - - if (data.error) - { - g_propagate_error (error, data.error); - return FALSE; - } - - g_debug ("Received %" G_GUINT64_FORMAT " bytes", data.downloaded_bytes); - - return TRUE; -} - /* In this NULL means don't care about these paths, while an empty array means match anything */ char **