/* gcc $(pkg-config --cflags --libs webkit2gtk-4.0 gtk+-3.0 json-glib-1.0 libxml-2.0 libsoup-2.4 gio-2.0 glib-2.0) download-rhel.c */ #include #include #include #include #include typedef struct _Data Data; struct _Data { GFile *authentication_uri; GInputStream *istream; GtkWidget *window; SoupSession *session; }; static void data_clear (Data *data) { g_object_unref (data->authentication_uri); g_object_unref (data->istream); if (data->window != NULL) g_object_remove_weak_pointer (G_OBJECT (data->window), (gpointer *) &data->window); g_object_unref (data->session); } static void output_stream_splice_download_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GError *error; GOutputStream *ostream = G_OUTPUT_STREAM (source_object); error = NULL; g_output_stream_splice_finish (ostream, res, &error); if (error != NULL) { g_warning ("g_output_stream_splice failed: %s", error->message); g_error_free (error); } gtk_main_quit (); } static void file_replace_download_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { Data *data = (Data *) user_data; GError *error; GFile *local_file = G_FILE (source_object); GFileOutputStream *ostream = NULL; error = NULL; ostream = g_file_replace_finish (local_file, res, &error); if (error != NULL) { g_warning ("g_file_replace failed: %s", error->message); gtk_main_quit (); g_error_free (error); goto out; } g_output_stream_splice_async (G_OUTPUT_STREAM (ostream), data->istream, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, G_PRIORITY_DEFAULT, NULL, output_stream_splice_download_cb, data); out: g_clear_object (&ostream); } static void session_send_download_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { Data *data = (Data *) user_data; GError *error; GFile *local_file = NULL; GInputStream *istream = NULL; SoupSession *session = SOUP_SESSION (source_object); gchar *basename = NULL; error = NULL; istream = soup_session_send_finish (session, res, &error); if (error != NULL) { g_warning ("soup_session_send failed: %s", error->message); gtk_main_quit (); g_error_free (error); goto out; } data->istream = g_object_ref (istream); basename = g_file_get_basename (data->authentication_uri); local_file = g_file_new_for_path (basename); g_file_replace_async (local_file, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, G_PRIORITY_DEFAULT, NULL, file_replace_download_cb, data); out: g_clear_object (&local_file); g_clear_object (&istream); g_free (basename); } static gboolean web_view_decide_policy_cb (WebKitWebView *web_view, WebKitPolicyDecision *decision, WebKitPolicyDecisionType decision_type, gpointer user_data) { Data *data = (Data *) user_data; SoupURI *uri = NULL; WebKitNavigationAction *action; WebKitURIRequest *request; gboolean ret_val = FALSE; const gchar *request_method; const gchar *request_uri; const gchar *query; if (decision_type != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) goto out; action = webkit_navigation_policy_decision_get_navigation_action (WEBKIT_NAVIGATION_POLICY_DECISION (decision)); request = webkit_navigation_action_get_request (action); request_method = webkit_uri_request_get_http_method (request); request_uri = webkit_uri_request_get_uri (request); g_debug ("%s: %s", request_method, request_uri); if (!g_str_has_prefix (request_uri, "https://developers.redhat.com/products/rhel")) goto out; uri = soup_uri_new (request_uri); query = soup_uri_get_query (uri); if (query != NULL) { GHashTable *key_value_pairs = NULL; const gchar *uri; key_value_pairs = soup_form_decode (query); uri = g_hash_table_lookup (key_value_pairs, "tcDownloadURL"); if (uri != NULL) { SoupMessage *message = NULL; g_debug ("Download URI: %s", uri); message = soup_message_new ("GET", uri); soup_session_send_async (data->session, message, NULL, session_send_download_cb, data); gtk_widget_hide (data->window); webkit_policy_decision_ignore (decision); ret_val = TRUE; g_object_unref (message); } g_hash_table_unref (key_value_pairs); } out: g_clear_pointer (&uri, (GDestroyNotify) soup_uri_free); return ret_val; } static gchar * parse_json (SoupMessageBody *response_body) { GError *error; JsonArray *root_array; JsonNode *root_array_node_0; JsonNode *root_node; JsonObject *artifact_object; JsonObject *root_array_object_0; JsonParser *parser = NULL; const gchar *product_code; gchar *ret_val = NULL; parser = json_parser_new (); error = NULL; if (!json_parser_load_from_data (parser, response_body->data, response_body->length, &error)) { g_warning ("json_parser_load_from_data failed: %s", error->message); g_error_free (error); goto out; } root_node = json_parser_get_root (parser); root_array = json_node_get_array (root_node); root_array_node_0 = json_array_get_element (root_array, 0); root_array_object_0 = json_node_get_object (root_array_node_0); if (!json_object_has_member (root_array_object_0, "productCode")) { g_warning ("Failed to parse JSON: couldn't find productCode"); goto out; } if (!json_object_has_member (root_array_object_0, "featuredArtifact")) { g_warning ("Failed to parse JSON: couldn't find featuredArtifact"); goto out; } product_code = json_object_get_string_member (root_array_object_0, "productCode"); if (g_strcmp0 (product_code, "rhel") != 0) { g_warning ("Failed to parse JSON: productCode != rhel: %s", product_code); goto out; } artifact_object = json_object_get_object_member (root_array_object_0, "featuredArtifact"); if (!json_object_has_member (artifact_object, "url")) { g_warning ("Failed to parse JSON: couldn't find featuredArtifact.url"); goto out; } ret_val = g_strdup (json_object_get_string_member (artifact_object, "url")); out: g_clear_object (&parser); return ret_val; } static gchar * parse_xml (SoupMessageBody *response_body) { xmlDoc *doc = NULL; xmlNode *artifact_node; xmlNode *node; xmlNode *product_node; xmlNode *product_code_node; xmlNode *root_node; xmlNode *uri_node; gchar *product_code = NULL; gchar *ret_val = NULL; doc = xmlReadMemory (response_body->data, response_body->length, "rhel", NULL, 0); if (doc == NULL) { g_warning ("xmlReadMemory failed"); goto out; } root_node = xmlDocGetRootElement (doc); if (g_strcmp0 (root_node->name, "products") != 0) { g_warning ("Failed to parse XML: couldn't find products"); goto out; } product_node = root_node->children; if (g_strcmp0 (product_node->name, "product") != 0) { g_warning ("Failed to parse XML: couldn't find products.product"); goto out; } for (node = product_node->children; node != NULL; node = node->next) { if (node->type == XML_ELEMENT_NODE && g_strcmp0 ((const gchar *) node->name, "productCode") == 0) break; } if (node == NULL) { g_warning ("Failed to parse XML: couldn't find products.product.productCode"); goto out; } product_code_node = node; product_code = xmlNodeGetContent (product_code_node); if (g_strcmp0 (product_code, "rhel") != 0) { g_warning ("Failed to parse XML: products.product.productCode != rhel: %s", product_code); goto out; } for (node = product_node->children; node != NULL; node = node->next) { if (node->type == XML_ELEMENT_NODE && g_strcmp0 ((const gchar *) node->name, "featuredArtifact") == 0) break; } if (node == NULL) { g_warning ("Failed to parse XML: couldn't find products.product.featuredArtifact"); goto out; } artifact_node = node; for (node = artifact_node->children; node != NULL; node = node->next) { if (node->type == XML_ELEMENT_NODE && g_strcmp0 ((const gchar *) node->name, "url") == 0) break; } if (node == NULL) { g_warning ("Failed to parse XML: couldn't find products.product.featuredArtifact.url"); goto out; } uri_node = node; ret_val = xmlNodeGetContent (uri_node); out: g_clear_pointer (&doc, (GDestroyNotify) xmlFreeDoc); g_free (product_code); return ret_val; } static void session_queue_message_rhel_cb (SoupSession *session, SoupMessage *message, gpointer user_data) { Data *data = (Data *) user_data; GFile *authentication_uri = NULL; GtkWidget *header; GtkWidget *web_view; GtkWidget *window; const gchar *content_type; gchar *uri = NULL; if (message->status_code != SOUP_STATUS_OK) { g_warning ("soup_session_queue_message failed: %u — %s", message->status_code, message->reason_phrase); gtk_main_quit (); goto out; } content_type = soup_message_headers_get_one (message->response_headers, "Content-Type"); if (content_type == NULL) { g_warning ("Missing Content-Type"); gtk_main_quit (); goto out; } if (g_str_has_prefix (content_type, "application/json")) { uri = parse_json (message->response_body); } else if (g_str_has_prefix (content_type, "application/xml")) { uri = parse_xml (message->response_body); } else { g_warning ("Unknown Content-Type: %s", content_type); gtk_main_quit (); goto out; } if (uri == NULL) { gtk_main_quit (); goto out; } g_debug ("Authentication URI: %s", uri); authentication_uri = g_file_new_for_uri (uri); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size (GTK_WINDOW (window), 600, 400); g_signal_connect (window, "delete-event", gtk_main_quit, NULL); header = gtk_header_bar_new (); gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (header), TRUE); gtk_window_set_titlebar (GTK_WINDOW (window), header); web_view = webkit_web_view_new (); webkit_web_view_load_uri (WEBKIT_WEB_VIEW (web_view), uri); gtk_container_add (GTK_CONTAINER (window), web_view); data->authentication_uri = g_object_ref (authentication_uri); data->window = window; g_object_add_weak_pointer (G_OBJECT (data->window), (gpointer *) &data->window); g_signal_connect (web_view, "decide-policy", G_CALLBACK (web_view_decide_policy_cb), data); gtk_widget_show_all (window); out: g_clear_object (&authentication_uri); g_free (uri); } gint main (void) { Data data; SoupLogger *logger = NULL; SoupMessage *message = NULL; SoupSession *session = NULL; gtk_init (NULL, NULL); session = soup_session_new (); data.session = g_object_ref (session); logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1); soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger)); message = soup_message_new ("GET", "https://developers.redhat.com/download-manager/rest/available/rhel"); soup_session_queue_message (session, message, session_queue_message_rhel_cb, &data); gtk_main (); data_clear (&data); g_clear_object (&logger); g_clear_object (&session); return 0; }