mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-24 17:20:36 +00:00
56e636b60c
The aim of this example is to show how to make use of the accept-certificate signal from a GTK GUI, and prompt user in case of invalid certificate. There are two subtleties to be aware of: 1. the signal is emitted from the GStreamer streaming thread, therefore the caller can't modify the GUI straight away, instead they must do it from the main thread (eg. by using g_idle_add()) 2. in case of a redirection, then a TLS failure, the caller won't know about the redirection. Actually, it's possible to be notified of the redirection by watching "message:element" and inspecting http-headers, but even in that case, the signal will be received *after* the signal "accept-certificate" (even though the redirection happened *before*). This second point is tricky. It's not uncommon to have servers that redirect http requests to https. So errors of the type "HTTP -> HTTPS -> TLS error" happen, and if the caller doesn't care about redirection, they might prompt users with a message such as "TLS error for URL http://...", which wouldn't make much sense. This example shows how to handle that right, by connecting to the signal "message:element", inspecting the http-headers, and in case of redirection, updating the TLS error dialog to indicate that the request was redirected. Here are a few examples of streams that exhibit TLS failure (at the time of this commit, of course): * https://radiolive.sanjavier.es:8443/stream: unknown-ca * https://am981.ddns.net:9005/stream.ogg: unknown-ca * http://stream.diazol.hu:7092/zene.mp3: redir then bad-identity * https://streaming.fabrik.fm/izwi/echocast/audio/index.m3u8: unknown-ca (this one is a HLS stream) Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4925>
369 lines
9.4 KiB
C
369 lines
9.4 KiB
C
/*
|
|
* GStreamer
|
|
* Copyright (C) 2023 Arnaud Rebillout <elboulangero@gmail.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 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
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
// gcc gtkhttpsrc.c $(pkg-config --cflags --libs gstreamer-1.0 gtk+-3.0)
|
|
|
|
#include <gtk/gtk.h>
|
|
#include <gst/gst.h>
|
|
|
|
//#define DEBUG_GST_BUS_MESSAGE_ELEMENT
|
|
|
|
static GstElement *playbin;
|
|
|
|
static gboolean accept_bad_certificates = FALSE; // whether we accept bad certs
|
|
static gchar *stream_uri; // the stream we want to play
|
|
static gchar *redirection_uri; // set if there was a redirection
|
|
static gchar *cert_errors; // errors with the certificate
|
|
|
|
/*
|
|
* Helpers
|
|
*/
|
|
|
|
static gchar *
|
|
tls_errors_to_string (GTlsCertificateFlags errors)
|
|
{
|
|
GPtrArray *a;
|
|
gchar *res;
|
|
|
|
a = g_ptr_array_new_full (2, NULL);
|
|
|
|
if (errors & G_TLS_CERTIFICATE_UNKNOWN_CA)
|
|
g_ptr_array_add (a, (gchar *) "unknown-ca");
|
|
if (errors & G_TLS_CERTIFICATE_BAD_IDENTITY)
|
|
g_ptr_array_add (a, (gchar *) "bad-identity");
|
|
if (errors & G_TLS_CERTIFICATE_NOT_ACTIVATED)
|
|
g_ptr_array_add (a, (gchar *) "not-activated");
|
|
if (errors & G_TLS_CERTIFICATE_EXPIRED)
|
|
g_ptr_array_add (a, (gchar *) "expired");
|
|
if (errors & G_TLS_CERTIFICATE_REVOKED)
|
|
g_ptr_array_add (a, (gchar *) "revoked");
|
|
if (errors & G_TLS_CERTIFICATE_INSECURE)
|
|
g_ptr_array_add (a, (gchar *) "insecure");
|
|
|
|
if (a->len > 0) {
|
|
g_ptr_array_add (a, NULL);
|
|
res = g_strjoinv (", ", (gchar **) a->pdata);
|
|
} else
|
|
res = g_strdup ("unknown error");
|
|
|
|
g_ptr_array_free (a, TRUE);
|
|
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
start_playback (void)
|
|
{
|
|
g_print ("Start playback: %s\n", stream_uri);
|
|
g_object_set (playbin, "uri", stream_uri, NULL);
|
|
gst_element_set_state (GST_ELEMENT (playbin), GST_STATE_PLAYING);
|
|
}
|
|
|
|
static void
|
|
stop_playback (void)
|
|
{
|
|
g_print ("Stop playback\n");
|
|
gst_element_set_state (GST_ELEMENT (playbin), GST_STATE_NULL);
|
|
}
|
|
|
|
/*
|
|
* Main window - Enter the URL of the stream and play it
|
|
*/
|
|
|
|
static GtkWidget *main_window;
|
|
|
|
static void
|
|
button_cb (GtkWidget * widget, GtkEntry * entry)
|
|
{
|
|
const gchar *uri;
|
|
|
|
uri = gtk_entry_get_text (entry);
|
|
|
|
// quick check - the purpose of this example is to play web radios
|
|
if (g_str_has_prefix (uri, "http") == FALSE) {
|
|
g_print ("Invalid entry, must start with 'http'\n");
|
|
return;
|
|
}
|
|
// only one click allowed - this is just an example!
|
|
gtk_widget_set_sensitive (widget, FALSE);
|
|
|
|
// save in global variable
|
|
stream_uri = g_strdup (uri);
|
|
|
|
start_playback ();
|
|
}
|
|
|
|
static void
|
|
show_main_window (void)
|
|
{
|
|
GtkWidget *window;
|
|
GtkWidget *grid;
|
|
GtkWidget *entry;
|
|
GtkWidget *button;
|
|
|
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_set_default_size (GTK_WINDOW (window), 640, -1);
|
|
|
|
grid = gtk_grid_new ();
|
|
gtk_container_add (GTK_CONTAINER (window), grid);
|
|
|
|
entry = gtk_entry_new ();
|
|
gtk_widget_set_hexpand (entry, TRUE);
|
|
gtk_grid_attach (GTK_GRID (grid), entry, 0, 0, 1, 1);
|
|
|
|
button = gtk_button_new_with_label ("> Play");
|
|
gtk_grid_attach (GTK_GRID (grid), button, 0, 1, 1, 1);
|
|
|
|
g_signal_connect (button, "clicked", G_CALLBACK (button_cb), entry);
|
|
g_signal_connect (window, "delete-event", gtk_main_quit, NULL);
|
|
|
|
gtk_widget_show_all (window);
|
|
|
|
// save in global variable
|
|
main_window = window;
|
|
}
|
|
|
|
/*
|
|
* Dialog - Whether to play a stream when the certifiate is invalid
|
|
*/
|
|
|
|
static GtkWidget *dialog;
|
|
|
|
static void
|
|
button_yes_cb (GtkWidget * widget, gpointer data)
|
|
{
|
|
accept_bad_certificates = TRUE;
|
|
start_playback ();
|
|
gtk_widget_destroy (dialog);
|
|
dialog = NULL;
|
|
}
|
|
|
|
static void
|
|
show_dialog (void)
|
|
{
|
|
GtkWidget *window;
|
|
GtkWidget *grid;
|
|
GtkWidget *box;
|
|
GtkWidget *label;
|
|
GtkWidget *button;
|
|
gchar *str;
|
|
|
|
// BEWARE! We can't gtk_dialog_run(), which would block the world, and
|
|
// prevent us from receiving other signals from GStreamer. In particular,
|
|
// if there was a redirection, we might not know it at this point.
|
|
|
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_set_transient_for (GTK_WINDOW (window), GTK_WINDOW (main_window));
|
|
|
|
grid = gtk_grid_new ();
|
|
gtk_container_add (GTK_CONTAINER (window), grid);
|
|
|
|
str = g_strdup_printf ("Bad certificate: %s", cert_errors);
|
|
label = gtk_label_new (str);
|
|
g_free (str);
|
|
gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1);
|
|
|
|
str = g_strdup_printf ("Stream URI: %s", stream_uri);
|
|
label = gtk_label_new (str);
|
|
g_free (str);
|
|
gtk_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1);
|
|
|
|
str = g_strdup_printf ("Redirection URI: %s", redirection_uri ?
|
|
redirection_uri : "not redirected");
|
|
label = gtk_label_new (str);
|
|
g_free (str);
|
|
gtk_grid_attach (GTK_GRID (grid), label, 0, 2, 1, 1);
|
|
|
|
label = gtk_label_new ("Play the stream anyway?");
|
|
gtk_grid_attach (GTK_GRID (grid), label, 0, 3, 2, 1);
|
|
|
|
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
|
gtk_grid_attach (GTK_GRID (grid), box, 0, 4, 1, 1);
|
|
|
|
button = gtk_button_new_with_label ("No");
|
|
g_signal_connect (button, "clicked", G_CALLBACK (gtk_main_quit), NULL);
|
|
gtk_box_pack_start (GTK_BOX (box), button, TRUE, TRUE, 0);
|
|
|
|
button = gtk_button_new_with_label ("Yes");
|
|
g_signal_connect (button, "clicked", G_CALLBACK (button_yes_cb), NULL);
|
|
gtk_box_pack_start (GTK_BOX (box), button, TRUE, TRUE, 0);
|
|
|
|
gtk_widget_show_all (window);
|
|
|
|
// save in global variable
|
|
dialog = window;
|
|
}
|
|
|
|
static void
|
|
dialog_update (void)
|
|
{
|
|
GtkWidget *grid;
|
|
GtkWidget *label;
|
|
gchar *str;
|
|
|
|
if (dialog == NULL)
|
|
return;
|
|
|
|
str = g_strdup_printf ("Redirection URI: %s", redirection_uri ?
|
|
redirection_uri : "not redirected");
|
|
grid = gtk_bin_get_child (GTK_BIN (dialog));
|
|
label = gtk_grid_get_child_at (GTK_GRID (grid), 0, 2);
|
|
gtk_label_set_text (GTK_LABEL (label), str);
|
|
g_free (str);
|
|
}
|
|
|
|
/*
|
|
* GStreamer things
|
|
*/
|
|
|
|
static gboolean
|
|
idle_cb (gpointer user_data)
|
|
{
|
|
show_dialog ();
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
accept_certificate_cb (GstElement * source, GTlsCertificate * tls_certificate,
|
|
GTlsCertificateFlags tls_errors, gpointer user_data)
|
|
{
|
|
gchar *errors;
|
|
|
|
errors = tls_errors_to_string (tls_errors);
|
|
|
|
// save in global variables
|
|
if (cert_errors)
|
|
g_free (cert_errors);
|
|
cert_errors = errors;
|
|
|
|
g_print ("Bad certificate: %s - %s\n", errors,
|
|
accept_bad_certificates ? "accepting" : "rejecting");
|
|
|
|
// Inform user that the certificate is invalid, and ask what to do.
|
|
// BEWARE! We're in the GStreamer streaming thread, we can't touch
|
|
// the GUI!
|
|
if (accept_bad_certificates == FALSE) {
|
|
//show_dialog(); // <- can't do that!
|
|
g_idle_add (idle_cb, NULL);
|
|
}
|
|
|
|
return accept_bad_certificates;
|
|
}
|
|
|
|
static void
|
|
source_setup_cb (GstElement * playbin, GstElement * source, gpointer user_data)
|
|
{
|
|
const gchar *name = G_OBJECT_TYPE_NAME (source);
|
|
|
|
if (g_signal_lookup ("accept-certificate", G_OBJECT_TYPE (source)) != 0) {
|
|
g_print ("Source %s has signal accept-certificate - connecting\n", name);
|
|
g_signal_connect (source, "accept-certificate",
|
|
G_CALLBACK (accept_certificate_cb), 0);
|
|
} else {
|
|
g_print ("Source %s does NOT have signal accept-certificate\n", name);
|
|
}
|
|
}
|
|
|
|
static void
|
|
message_element_cb (GstBus * bus, GstMessage * msg, gpointer user_data)
|
|
{
|
|
const GstStructure *s;
|
|
const gchar *name;
|
|
const gchar *uri;
|
|
|
|
s = gst_message_get_structure (msg);
|
|
if (s == NULL)
|
|
return;
|
|
|
|
name = gst_structure_get_name (s);
|
|
if (g_strcmp0 (name, "http-headers") != 0)
|
|
return;
|
|
|
|
#ifdef DEBUG_GST_BUS_MESSAGE_ELEMENT
|
|
gst_println ("%" GST_PTR_FORMAT, s);
|
|
#endif
|
|
|
|
if (gst_structure_has_field (s, "redirection-uri") == FALSE)
|
|
return;
|
|
|
|
uri = gst_structure_get_string (s, "redirection-uri");
|
|
g_print ("Redirected to: %s\n", uri);
|
|
|
|
// save in global variable
|
|
if (redirection_uri)
|
|
g_free (redirection_uri);
|
|
redirection_uri = g_strdup (uri);
|
|
|
|
dialog_update ();
|
|
}
|
|
|
|
static void
|
|
message_error_cb (GstBus * bus, GstMessage * message, gpointer user_data)
|
|
{
|
|
GError *err;
|
|
gchar *debug;
|
|
|
|
gst_message_parse_error (message, &err, &debug);
|
|
|
|
g_print ("Got error! ---------\n");
|
|
g_print (" error code: %s: %d\n", g_quark_to_string (err->domain),
|
|
err->code);
|
|
g_print (" message : %s\n", err->message);
|
|
g_print (" debug : %s\n", debug);
|
|
g_print ("--------------------\n");
|
|
|
|
stop_playback ();
|
|
|
|
g_free (debug);
|
|
g_error_free (err);
|
|
}
|
|
|
|
static void
|
|
setup_playback (void)
|
|
{
|
|
GstBus *bus;
|
|
|
|
playbin = gst_element_factory_make ("playbin", "playbin");
|
|
g_signal_connect (playbin, "source-setup",
|
|
G_CALLBACK (source_setup_cb), NULL);
|
|
|
|
bus = gst_element_get_bus (playbin);
|
|
gst_bus_add_signal_watch (bus);
|
|
g_signal_connect (bus, "message::element",
|
|
G_CALLBACK (message_element_cb), NULL);
|
|
g_signal_connect (bus, "message::error", G_CALLBACK (message_error_cb), NULL);
|
|
}
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
gst_init (&argc, &argv);
|
|
gtk_init (&argc, &argv);
|
|
|
|
setup_playback ();
|
|
show_main_window ();
|
|
|
|
gtk_main ();
|
|
|
|
stop_playback ();
|
|
gst_deinit ();
|
|
|
|
return 0;
|
|
}
|