summaryrefslogtreecommitdiffstats
path: root/plugins/www
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 17:06:32 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 17:06:32 +0000
commit2dad5357405ad33cfa792f04b3ab62a5d188841e (patch)
treeb8f8893942060fe3cfb04ac374cda96fdfc8f453 /plugins/www
parentInitial commit. (diff)
downloadremmina-2dad5357405ad33cfa792f04b3ab62a5d188841e.tar.xz
remmina-2dad5357405ad33cfa792f04b3ab62a5d188841e.zip
Adding upstream version 1.4.34+dfsg.upstream/1.4.34+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plugins/www')
-rw-r--r--plugins/www/CMakeLists.txt72
-rw-r--r--plugins/www/resources/js/.eslintrc.json13
-rw-r--r--plugins/www/resources/js/package.json11
-rw-r--r--plugins/www/resources/js/www-js.js98
-rw-r--r--plugins/www/scalable/emblems/org.remmina.Remmina-www-symbolic.svg117
-rw-r--r--plugins/www/www_config.h46
-rw-r--r--plugins/www/www_plugin.c987
-rw-r--r--plugins/www/www_plugin.h80
-rw-r--r--plugins/www/www_utils.c154
-rw-r--r--plugins/www/www_utils.h47
10 files changed, 1625 insertions, 0 deletions
diff --git a/plugins/www/CMakeLists.txt b/plugins/www/CMakeLists.txt
new file mode 100644
index 0000000..f9aa907
--- /dev/null
+++ b/plugins/www/CMakeLists.txt
@@ -0,0 +1,72 @@
+# remmina-plugin-www - The GTK+ Remote Desktop Client
+#
+# Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+#
+# In addition, as a special exception, the copyright holders give
+# permission to link the code of portions of this program with the
+# OpenSSL library under certain conditions as described in each
+# individual source file, and distribute linked combinations
+# including the two.
+# You must obey the GNU General Public License in all respects
+# for all of the code used other than OpenSSL. If you modify
+# file(s) with this exception, you may extend this exception to your
+# version of the file(s), but you are not obligated to do so. If you
+# do not wish to do so, delete this exception statement from your
+# version. If you delete this exception statement from all source
+# files in the program, then also delete it here.
+
+
+set(REMMINA_PLUGIN_WWW_SRCS
+ www_utils.c
+ www_utils.h
+ www_config.h
+ www_plugin.c
+ www_plugin.h
+)
+
+add_library(remmina-plugin-www MODULE ${REMMINA_PLUGIN_WWW_SRCS})
+set_target_properties(remmina-plugin-www PROPERTIES PREFIX "")
+set_target_properties(remmina-plugin-www PROPERTIES NO_SONAME 1)
+
+add_definitions(${WEBKIT2GTK_CFLAGS_OTHER})
+
+find_required_package(LIBSOUP)
+if(LIBSOUP_FOUND)
+ include_directories(${REMMINA_COMMON_INCLUDE_DIRS} ${WEBKIT2GTK_INCLUDE_DIRS} ${LIBSOUP_INCLUDE_DIRS})
+ target_link_libraries(remmina-plugin-www ${REMMINA_COMMON_LIBRARIES} ${LIBSOUP_LIBRARIES} ${WEBKIT2GTK_LIBRARIES})
+else()
+ message(FATAL_ERROR "libsoup library not found")
+endif()
+
+install(TARGETS remmina-plugin-www DESTINATION ${REMMINA_PLUGINDIR})
+
+install(FILES
+ scalable/emblems/org.remmina.Remmina-www-symbolic.svg
+ DESTINATION ${APPICONSCALE_EMBLEMS_DIR})
+
+if(WITH_ICON_CACHE)
+ gtk_update_icon_cache("${REMMINA_DATADIR}/icons/hicolor")
+endif()
+
+# In the feature we will have more resources to add, so I use a GLOB
+file(GLOB WWW_RES_FILES "${CMAKE_CURRENT_SOURCE_DIR}/resources/js/www-js.js")
+foreach(ITEM ${WWW_RES_FILES})
+ list( APPEND FILES_TO_DEPLOY "${ITEM}" )
+endforeach()
+install( FILES ${FILES_TO_DEPLOY} DESTINATION "${REMMINA_RESDIR}" )
+
diff --git a/plugins/www/resources/js/.eslintrc.json b/plugins/www/resources/js/.eslintrc.json
new file mode 100644
index 0000000..8f6a415
--- /dev/null
+++ b/plugins/www/resources/js/.eslintrc.json
@@ -0,0 +1,13 @@
+{
+ "env": {
+ "browser": true,
+ "es2021": true
+ },
+ "extends": "eslint:recommended",
+ "parserOptions": {
+ "ecmaVersion": "latest",
+ "sourceType": "module"
+ },
+ "rules": {
+ }
+}
diff --git a/plugins/www/resources/js/package.json b/plugins/www/resources/js/package.json
new file mode 100644
index 0000000..70b8fe8
--- /dev/null
+++ b/plugins/www/resources/js/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "js",
+ "version": "1.0.0",
+ "description": "",
+ "main": "www-js.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "Antenore Gatta",
+ "license": "GPL-2.0"
+}
diff --git a/plugins/www/resources/js/www-js.js b/plugins/www/resources/js/www-js.js
new file mode 100644
index 0000000..06980c9
--- /dev/null
+++ b/plugins/www/resources/js/www-js.js
@@ -0,0 +1,98 @@
+/* jshint esversion: 6 */
+
+/** A quick function to find and fill login fields */
+function setLoginFields () {
+ const evt = new Event('change')
+
+ const frames = window.frames
+
+ let doc
+ let pswdField
+ let usrField
+ let formNode
+ let userFound = false
+
+ if (frames.length !== 0) {
+ for (let i = 0; i < frames.length; i++) {
+ doc = frames[i].document
+ pswdField = doc.querySelectorAll('input[type=\'password\']')
+ if ((pswdField !== undefined) && (pswdField !== null)) {
+ break
+ }
+ }
+ if ((pswdField === undefined) || (pswdField === null)) {
+ /* What if we don't have login forms in the iFrame? -> window */
+ doc = window.document
+ pswdField = doc.querySelectorAll('input[type=\'password\']')
+ }
+ } else {
+ doc = window.document
+ pswdField = doc.querySelectorAll('input[type=\'password\']')
+ }
+
+ if (pswdField !== undefined) {
+ pswdField.forEach(function (pswdElement) {
+ if (pswdElement.getAttribute('autocomplete') !== 'new-password') {
+ pswdElement.value = 'PWDPLACEHOLDER'
+ }
+
+ formNode = pswdElement.form
+ if (formNode !== null) {
+ console.debug('Form elements found')
+ usrField = formNode.querySelectorAll('input[type=\'text\']')
+
+ usrField.forEach(function (usrElement) {
+ usrElement.value = 'USRPLACEHOLDER'
+ if (usrElement !== null) {
+ usrElement.dispatchEvent(evt)
+ userFound = true
+ }
+ })
+ pswdElement.dispatchEvent(evt)
+ }
+ if (formNode === null || !userFound) {
+ console.debug('Form elements found')
+ console.debug('Inputs elements may be in other containers')
+ const inputs = doc.getElementsByTagName('input')
+ for (let i = 0; i < inputs.length; i += 1) {
+ console.debug('input type: ' + inputs[i].type)
+ switch (inputs[i].type) {
+ case 'new-password':
+ continue
+ case 'password':
+ continue
+ case 'hidden':
+ continue
+ case 'email':
+ inputs[i].value = 'USRPLACEHOLDER'
+ userFound = true
+ break
+ case 'text':
+ if (!userFound) {
+ inputs[i].value = 'USRPLACEHOLDER'
+ userFound = true
+ }
+ break
+ default:
+ console.debug('Tentativily add username if no userFound')
+ if (!userFound) {
+ inputs[i].value = 'USRPLACEHOLDER'
+ userFound = true
+ }
+ // code block
+ }
+ if (userFound) {
+ inputs[i].dispatchEvent(evt)
+ console.debug('Username field found and set(?)')
+ break
+ }
+ }
+ }
+ })
+ } else {
+ console.debug('We already have a password')
+
+ }
+}
+
+setLoginFields()
diff --git a/plugins/www/scalable/emblems/org.remmina.Remmina-www-symbolic.svg b/plugins/www/scalable/emblems/org.remmina.Remmina-www-symbolic.svg
new file mode 100644
index 0000000..23a1e7b
--- /dev/null
+++ b/plugins/www/scalable/emblems/org.remmina.Remmina-www-symbolic.svg
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="100"
+ height="100"
+ viewBox="0 0 26.458334 26.458333"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.4 5da689c313, 2019-01-14"
+ sodipodi:docname="remmina-www-symbolic.svg">
+ <title
+ id="title4674">remmina-resail-symbolic</title>
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="4.1973765"
+ inkscape:cx="-123.70227"
+ inkscape:cy="21.504897"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:document-rotation="0"
+ showgrid="false"
+ borderlayer="true"
+ inkscape:showpageshadow="true"
+ units="px"
+ inkscape:pagecheckerboard="false"
+ showguides="false"
+ inkscape:window-width="3840"
+ inkscape:window-height="2070"
+ inkscape:window-x="0"
+ inkscape:window-y="30"
+ inkscape:window-maximized="0"
+ objecttolerance="10"
+ guidetolerance="10"
+ inkscape:snap-tangential="true"
+ inkscape:snap-perpendicular="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0">
+ <inkscape:grid
+ type="xygrid"
+ id="grid10"
+ dotted="false"
+ originx="-190.61239"
+ originy="-78.157086" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>remmina-resail-symbolic</dc:title>
+ <cc:license
+ rdf:resource="https://creativecommons.org/licenses/by-sa/3.0/" />
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Antenore Gatta</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:rights>
+ <cc:Agent>
+ <dc:title>Antenore Gatta 2019</dc:title>
+ </cc:Agent>
+ </dc:rights>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-190.61238,-192.38458)">
+ <g
+ style="isolation:isolate"
+ id="Group"
+ transform="matrix(0.26458333,0,0,0.26458333,153.78079,124.26165)" />
+ <g
+ style="isolation:isolate"
+ id="g62"
+ transform="matrix(0.26458333,0,0,0.26458333,153.78079,124.26165)">
+ <g
+ id="g52" />
+ <g
+ id="g54" />
+ <g
+ id="g56" />
+ <g
+ id="g60">
+ <g
+ id="g58" />
+ </g>
+ </g>
+ <path
+ style="opacity:1;fill:#000000;fill-opacity:0.87999998;stroke:none;stroke-width:2.91110802;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 50 1.6816406 A 48.655695 48.318765 0 0 0 1.34375 50 A 48.655695 48.318765 0 0 0 50 98.318359 A 48.655695 48.318765 0 0 0 98.65625 50 A 48.655695 48.318765 0 0 0 50 1.6816406 z M 29.576172 15.396484 L 31.576172 67.396484 L 43.203125 16.888672 L 56.087891 16.888672 L 67.576172 67.396484 L 69.576172 15.396484 L 83.330078 18.236328 L 74.576172 85.396484 L 61.576172 85.132812 L 49.361328 33.128906 L 37.576172 85.396484 L 24.576172 85.396484 L 15.322266 18.236328 L 29.576172 15.396484 z "
+ transform="matrix(0.26458333,0,0,0.26458333,190.61238,192.38458)"
+ id="path4668" />
+ </g>
+</svg>
diff --git a/plugins/www/www_config.h b/plugins/www/www_config.h
new file mode 100644
index 0000000..225b7b2
--- /dev/null
+++ b/plugins/www/www_config.h
@@ -0,0 +1,46 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * Initially based on the plugin "Remmina Plugin EXEC", created and written by
+ * Fabio Castelli (Muflone) <muflone@vbsimple.net>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#pragma once
+#ifndef __PLUGIN_CONFIG_H
+#define __PLUGIN_CONFIG_H
+
+#define PLUGIN_NAME "WWW"
+#define PLUGIN_DESCRIPTION N_("Remmina web-browser plugin")
+#define PLUGIN_VERSION "0.1.0"
+#define PLUGIN_APPICON "org.remmina.Remmina-www-symbolic"
+#endif
diff --git a/plugins/www/www_plugin.c b/plugins/www/www_plugin.c
new file mode 100644
index 0000000..b248e6b
--- /dev/null
+++ b/plugins/www/www_plugin.c
@@ -0,0 +1,987 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.
+ * If you modify file(s) with this exception, you may extend this exception
+ * to your version of the file(s), but you are not obligated to do so.
+ * If you do not wish to do so, delete this exception statement from your
+ * version.
+ * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#include "www_config.h"
+
+#include "common/remmina_plugin.h"
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <glib.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <stdlib.h>
+
+#include <webkit2/webkit2.h>
+#if WEBKIT_CHECK_VERSION(2, 21, 1)
+#include <jsc/jsc.h>
+#endif
+#include "www_utils.h"
+#include "www_plugin.h"
+
+#define UNUSED(x) (void)(x)
+
+#define GET_PLUGIN_DATA(gp) (RemminaPluginWWWData *)g_object_get_data(G_OBJECT(gp), "plugin-data")
+
+typedef struct _RemminaPluginWWWData {
+ WWWWebViewDocumentType document_type;
+ GtkWidget * box;
+ WebKitSettings * settings;
+ WebKitWebContext * context;
+ WebKitWebsiteDataManager * data_mgr;
+ WebKitCredential * credentials;
+ WebKitAuthenticationRequest * request;
+ WebKitWebView * webview;
+ WebKitLoadEvent load_event;
+
+ gchar * url;
+ gboolean authenticated;
+ gboolean formauthenticated;
+} RemminaPluginWWWData;
+
+RemminaPluginService *remmina_plugin_service = NULL;
+
+void remmina_plugin_www_download_started(WebKitWebContext *context,
+ WebKitDownload *download, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ webkit_download_set_allow_overwrite(download, TRUE);
+ g_signal_connect(G_OBJECT(download), "notify::response",
+ G_CALLBACK(remmina_plugin_www_response_received), gp);
+ g_signal_connect(download, "created-destination",
+ G_CALLBACK(remmina_plugin_www_notify_download), gp);
+}
+
+void remmina_plugin_www_response_received(WebKitDownload *download, GParamSpec *ps, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ REMMINA_PLUGIN_DEBUG("Download response received");
+}
+
+void remmina_plugin_www_notify_download(WebKitDownload *download, gchar *destination, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ REMMINA_PLUGIN_DEBUG("Download is finished");
+ const gchar *dest = webkit_download_get_destination(download);
+
+ www_utils_send_notification("www-plugin-download-completed-id", _("File downloaded"), dest);
+ //download(gp, webkit_download_get_response(download));
+ //webkit_download_cancel(download);
+}
+
+static gboolean remmina_plugin_www_decide_policy_cb(
+ WebKitWebView * webview,
+ WebKitPolicyDecision * decision,
+ WebKitPolicyDecisionType decision_type,
+ RemminaProtocolWidget * gp)
+{
+ TRACE_CALL(__func__);
+
+ gboolean res = TRUE;
+
+ switch (decision_type) {
+ case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
+ remmina_plugin_www_decide_nav(decision, gp);
+ break;
+ case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
+ remmina_plugin_www_decide_newwin(decision, gp);
+ break;
+ case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
+ res = remmina_plugin_www_decide_resource(decision, gp);
+ break;
+ default:
+ webkit_policy_decision_ignore(decision);
+ break;
+ }
+ return res;
+}
+void remmina_plugin_www_decide_nav(WebKitPolicyDecision *decision, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ REMMINA_PLUGIN_DEBUG("Policy decision navigation");
+ const gchar *url = NULL;
+ WebKitNavigationAction *a =
+ webkit_navigation_policy_decision_get_navigation_action(
+ WEBKIT_NAVIGATION_POLICY_DECISION(decision));
+
+ switch (webkit_navigation_action_get_navigation_type(a)) {
+ case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED:
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_LINK_CLICKED");
+ url = webkit_uri_request_get_uri(
+ webkit_navigation_action_get_request(a));
+ REMMINA_PLUGIN_DEBUG("url is %s ", url);
+ break;
+ case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED:
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED");
+ break;
+ case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD:
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_BACK_FORWARD");
+ break;
+ case WEBKIT_NAVIGATION_TYPE_RELOAD:
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_RELOAD");
+ break;
+ case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED");
+ break;
+ case WEBKIT_NAVIGATION_TYPE_OTHER:
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_OTHER");
+ break;
+ default:
+ /* Do not navigate to links with a "_blank" target (popup) */
+ if (webkit_navigation_policy_decision_get_frame_name(
+ WEBKIT_NAVIGATION_POLICY_DECISION(decision))) {
+ webkit_policy_decision_ignore(decision);
+ } else {
+ /* Filter out navigation to different domain ? */
+ /* get action→urirequest, copy and load in new window+view
+ * on Ctrl+Click ? */
+ webkit_policy_decision_use(decision);
+ }
+ break;
+ }
+}
+
+void remmina_plugin_www_on_create(WebKitWebView *webview, WebKitNavigationAction *a, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ REMMINA_PLUGIN_DEBUG("New web-view");
+
+ const gchar *url = NULL;
+
+ RemminaPluginWWWData *gpdata;
+
+ gpdata = (RemminaPluginWWWData *)g_object_get_data(G_OBJECT(gp), "plugin-data");
+
+ switch (webkit_navigation_action_get_navigation_type(a)) {
+ case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED:
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_LINK_CLICKED");
+ url = webkit_uri_request_get_uri(
+ webkit_navigation_action_get_request(a));
+ REMMINA_PLUGIN_DEBUG("Downloading url %s ", url);
+ WebKitDownload *d = webkit_web_view_download_uri(gpdata->webview, url);
+ UNUSED(d);
+
+ break;
+ case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED:
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED");
+ break;
+ case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD:
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_BACK_FORWARD");
+ break;
+ case WEBKIT_NAVIGATION_TYPE_RELOAD:
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_RELOAD");
+ break;
+ case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED");
+ /* Filter domains here */
+ /* If the value of “mouse-button” is not 0, then the navigation was triggered by a mouse event.
+ * test for link clicked but no button ? */
+ url = webkit_uri_request_get_uri(
+ webkit_navigation_action_get_request(a));
+ REMMINA_PLUGIN_DEBUG("Trying to open url: %s", url);
+ webkit_web_view_load_uri(gpdata->webview, url);
+ break;
+ case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_OTHER");
+ /* Filter domains here */
+ /* If the value of “mouse-button” is not 0, then the navigation was triggered by a mouse event.
+ * test for link clicked but no button ? */
+ url = webkit_uri_request_get_uri(
+ webkit_navigation_action_get_request(a));
+ REMMINA_PLUGIN_DEBUG("Trying to open url: %s", url);
+ webkit_web_view_load_uri(gpdata->webview, url);
+ break;
+ default:
+ break;
+ }
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE is %d", webkit_navigation_action_get_navigation_type(a));
+}
+
+void remmina_plugin_www_decide_newwin(WebKitPolicyDecision *decision, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ REMMINA_PLUGIN_DEBUG("Policy decision new window");
+
+ const gchar *url = NULL;
+
+ RemminaPluginWWWData *gpdata;
+
+ gpdata = (RemminaPluginWWWData *)g_object_get_data(G_OBJECT(gp), "plugin-data");
+
+ WebKitNavigationAction *a =
+ webkit_navigation_policy_decision_get_navigation_action(
+ WEBKIT_NAVIGATION_POLICY_DECISION(decision));
+
+ switch (webkit_navigation_action_get_navigation_type(a)) {
+ case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED:
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_LINK_CLICKED");
+ url = webkit_uri_request_get_uri(
+ webkit_navigation_action_get_request(a));
+ REMMINA_PLUGIN_DEBUG("Downloading url %s ", url);
+ WebKitDownload *d = webkit_web_view_download_uri(gpdata->webview, url);
+ UNUSED(d);
+
+
+ break;
+ case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED:
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED");
+ break;
+ case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD:
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_BACK_FORWARD");
+ break;
+ case WEBKIT_NAVIGATION_TYPE_RELOAD:
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_RELOAD");
+ break;
+ case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED");
+ /* Filter domains here */
+ /* If the value of “mouse-button” is not 0, then the navigation was triggered by a mouse event.
+ * test for link clicked but no button ? */
+ url = webkit_uri_request_get_uri(
+ webkit_navigation_action_get_request(a));
+ REMMINA_PLUGIN_DEBUG("Trying to open url: %s", url);
+ webkit_web_view_load_uri(gpdata->webview, url);
+ break;
+ case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE_OTHER");
+ /* Filter domains here */
+ /* If the value of “mouse-button” is not 0, then the navigation was triggered by a mouse event.
+ * test for link clicked but no button ? */
+ url = webkit_uri_request_get_uri(
+ webkit_navigation_action_get_request(a));
+ REMMINA_PLUGIN_DEBUG("Trying to open url: %s", url);
+ webkit_web_view_load_uri(gpdata->webview, url);
+ break;
+ default:
+ break;
+ }
+ REMMINA_PLUGIN_DEBUG("WEBKIT_NAVIGATION_TYPE is %d", webkit_navigation_action_get_navigation_type(a));
+
+ webkit_policy_decision_ignore(decision);
+}
+gboolean remmina_plugin_www_decide_resource(WebKitPolicyDecision *decision, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ REMMINA_PLUGIN_DEBUG("Policy decision resource");
+ WebKitResponsePolicyDecision *response_decision =
+ WEBKIT_RESPONSE_POLICY_DECISION(decision);
+ WebKitURIResponse *response =
+ webkit_response_policy_decision_get_response(response_decision);;
+ const gchar *request_uri = webkit_uri_response_get_uri(response);
+
+ WebKitURIRequest *request;
+ WebKitWebResource *main_resource;
+ WWWWebViewDocumentType type;
+ const char *mime_type;
+
+ RemminaPluginWWWData *gpdata;
+
+ gpdata = (RemminaPluginWWWData *)g_object_get_data(G_OBJECT(gp), "plugin-data");
+
+ mime_type = webkit_uri_response_get_mime_type(response);
+
+ REMMINA_PLUGIN_DEBUG("The media type is %s", mime_type);
+
+ /* If WebKit can't handle the media type, start the download
+ * process */
+ if (webkit_response_policy_decision_is_mime_type_supported(response_decision))
+ return FALSE;
+
+ /* If it's not the main resource we don't need to set the document type. */
+ request = webkit_response_policy_decision_get_request(response_decision);
+ request_uri = webkit_uri_request_get_uri(request);
+ main_resource = webkit_web_view_get_main_resource(gpdata->webview);
+ if (g_strcmp0(webkit_web_resource_get_uri(main_resource), request_uri) != 0)
+ return FALSE;
+
+ type = WWW_WEB_VIEW_DOCUMENT_OTHER;
+ if (!strcmp(mime_type, "text/html") || !strcmp(mime_type, "text/plain"))
+ type = WWW_WEB_VIEW_DOCUMENT_HTML;
+ else if (!strcmp(mime_type, "application/xhtml+xml"))
+ type = WWW_WEB_VIEW_DOCUMENT_XML;
+ else if (!strncmp(mime_type, "image/", 6))
+ type = WWW_WEB_VIEW_DOCUMENT_IMAGE;
+ else if (!strncmp(mime_type, "application/octet-stream", 6))
+ type = WWW_WEB_VIEW_DOCUMENT_OCTET_STREAM;
+
+ REMMINA_PLUGIN_DEBUG("Document type is %i", type);
+
+ /* FIXME: Maybe it makes more sense to have an API to query the media
+ * type when the load of a page starts than doing this here.
+ */
+ if (gpdata->document_type != type) {
+ gpdata->document_type = type;
+
+ //g_object_notify_by_pspec (G_OBJECT (webview), obj_properties[PROP_DOCUMENT_TYPE]);
+ }
+
+ webkit_policy_decision_download(decision);
+ return TRUE;
+}
+
+static void remmina_www_web_view_js_finished(GObject *object, GAsyncResult *result, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+
+ WebKitJavascriptResult *js_result;
+ GError *error = NULL;
+
+ js_result = webkit_web_view_run_javascript_finish(WEBKIT_WEB_VIEW(object), result, &error);
+ if (!js_result) {
+ REMMINA_PLUGIN_DEBUG("Could not run JavaScript code: %s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+#if WEBKIT_CHECK_VERSION(2, 21, 1)
+ gchar *str_value;
+ JSCValue *value = webkit_javascript_result_get_js_value(js_result);
+ if (jsc_value_is_string(value) || jsc_value_is_boolean(value)) {
+ JSCException *exception;
+
+ str_value = jsc_value_to_string(value);
+ exception = jsc_context_get_exception(jsc_value_get_context(value));
+ if (exception)
+ REMMINA_PLUGIN_DEBUG("Could not run JavaScript code: %s", jsc_exception_get_message(exception));
+ else
+ g_print("Script result: %s\n", str_value);
+ g_free(str_value);
+ } else {
+ str_value = jsc_value_to_string(value);
+ REMMINA_PLUGIN_DEBUG("Received something other than a string from JavaScript: %s", str_value);
+ g_free(str_value);
+ }
+#endif
+ if (js_result) webkit_javascript_result_unref(js_result);
+}
+
+static gboolean remmina_www_query_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ return TRUE;
+}
+
+static gboolean remmina_plugin_www_load_failed_tls_cb(WebKitWebView *webview, gchar *failing_uri, GTlsCertificate *certificate, GTlsCertificateFlags errors, RemminaProtocolWidget *gp) __attribute__ ((unused));
+static gboolean remmina_plugin_www_load_failed_tls_cb(WebKitWebView *webview,
+ gchar *failing_uri, GTlsCertificate *certificate,
+ GTlsCertificateFlags errors, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ /* Avoid failing if certificate is not good. TODO: Add widgets to let the user decide */
+ REMMINA_PLUGIN_DEBUG("Ignoring certificate and return TRUE");
+ return TRUE;
+}
+
+static void remmina_plugin_www_init(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ RemminaPluginWWWData *gpdata;
+ RemminaFile *remminafile;
+ gchar *datapath;
+ gchar *cache_dir;
+
+ gpdata = g_new0(RemminaPluginWWWData, 1);
+ g_object_set_data_full(G_OBJECT(gp), "plugin-data", gpdata, g_free);
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ /* RemminaPluginWWWData initialization */
+
+ gpdata->authenticated = FALSE;
+ gpdata->formauthenticated = FALSE;
+ gpdata->document_type = WWW_WEB_VIEW_DOCUMENT_HTML;
+
+ datapath = g_build_path("/",
+ g_path_get_dirname(remmina_plugin_service->file_get_path(remminafile)),
+ PLUGIN_NAME,
+ NULL);
+ cache_dir = g_build_path("/", datapath, "cache", NULL);
+ REMMINA_PLUGIN_DEBUG("WWW data path is %s", datapath);
+
+ if (datapath) {
+ gchar *indexeddb_dir = g_build_filename(datapath, "indexeddb", NULL);
+ gchar *local_storage_dir = g_build_filename(datapath, "local_storage", NULL);
+ gchar *applications_dir = g_build_filename(datapath, "applications", NULL);
+ gchar *websql_dir = g_build_filename(datapath, "websql", NULL);
+ gpdata->data_mgr = webkit_website_data_manager_new(
+ "disk-cache-directory", cache_dir,
+ "indexeddb-directory", indexeddb_dir,
+ "local-storage-directory", local_storage_dir,
+ "offline-application-cache-directory", applications_dir,
+ "websql-directory", websql_dir,
+ NULL
+ );
+ g_free(indexeddb_dir);
+ g_free(local_storage_dir);
+ g_free(applications_dir);
+ g_free(websql_dir);
+ g_free(datapath);
+ } else {
+ gpdata->data_mgr = webkit_website_data_manager_new_ephemeral();
+ }
+
+
+ if (remmina_plugin_service->file_get_string(remminafile, "server"))
+ gpdata->url = g_strdup(remmina_plugin_service->file_get_string(remminafile, "server"));
+ else
+ gpdata->url = "https://remmina.org";
+ REMMINA_PLUGIN_DEBUG("URL is set to %s", gpdata->url);
+
+ gpdata->settings = webkit_settings_new();
+ gpdata->context = webkit_web_context_new_with_website_data_manager(gpdata->data_mgr);
+
+ /* enable-fullscreen, default TRUE, TODO: Try FALSE */
+
+#ifdef DEBUG
+ /* Turn on the developer extras */
+ webkit_settings_set_enable_developer_extras(gpdata->settings, TRUE);
+ webkit_settings_set_enable_write_console_messages_to_stdout(gpdata->settings, TRUE);
+#endif
+
+ /* allow-file-access-from-file-urls */
+ webkit_settings_set_allow_file_access_from_file_urls(gpdata->settings, TRUE);
+ /* allow-modal-dialogs */
+ webkit_settings_set_allow_modal_dialogs(gpdata->settings, TRUE);
+ /* enable-caret-browsing */
+ webkit_settings_set_enable_caret_browsing(gpdata->settings, TRUE);
+ /* enable-html5-database */
+ webkit_settings_set_enable_html5_database(gpdata->settings, TRUE);
+
+ /* user-agent. */
+ if (remmina_plugin_service->file_get_string(remminafile, "user-agent")) {
+ gchar *useragent = g_strdup(remmina_plugin_service->file_get_string(remminafile, "user-agent"));
+ webkit_settings_set_user_agent(gpdata->settings, useragent);
+ REMMINA_PLUGIN_DEBUG("User Agent set to: %s", useragent);
+ g_free(useragent);
+ }
+ /* enable-java */
+ if (remmina_plugin_service->file_get_int(remminafile, "enable-java", FALSE)) {
+ webkit_settings_set_enable_java(gpdata->settings, TRUE);
+ REMMINA_PLUGIN_DEBUG("Enable Java");
+ }
+ /* enable-smooth-scrolling */
+ if (remmina_plugin_service->file_get_int(remminafile, "enable-smooth-scrolling", FALSE)) {
+ webkit_settings_set_enable_smooth_scrolling(gpdata->settings, TRUE);
+ REMMINA_PLUGIN_DEBUG("enable-smooth-scrolling enabled");
+ }
+ /* enable-spatial-navigation */
+ if (remmina_plugin_service->file_get_int(remminafile, "enable-spatial-navigation", FALSE)) {
+ webkit_settings_set_enable_spatial_navigation(gpdata->settings, TRUE);
+ REMMINA_PLUGIN_DEBUG("enable-spatial-navigation enabled");
+ }
+ /* enable-webaudio */
+ if (remmina_plugin_service->file_get_int(remminafile, "enable-webaudio", FALSE)) {
+ webkit_settings_set_enable_webaudio(gpdata->settings, TRUE);
+ REMMINA_PLUGIN_DEBUG("enable-webaudio enabled");
+ }
+ /* enable-plugins */
+#if WEBKIT_CHECK_VERSION(2, 32, 0)
+#else
+ if (remmina_plugin_service->file_get_int(remminafile, "enable-plugins", FALSE)) {
+ webkit_settings_set_enable_plugins(gpdata->settings, TRUE);
+ REMMINA_PLUGIN_DEBUG("Enable plugins");
+ }
+#endif
+ /* enable-webgl */
+#if WEBKIT_CHECK_VERSION(2, 32, 0)
+#else
+ if (remmina_plugin_service->file_get_int(remminafile, "enable-webgl", FALSE)) {
+ webkit_settings_set_enable_webgl(gpdata->settings, TRUE);
+ webkit_settings_set_enable_accelerated_2d_canvas(gpdata->settings, TRUE);
+ REMMINA_PLUGIN_DEBUG("enable-webgl enabled");
+ }
+#endif
+
+ if (remmina_plugin_service->file_get_int(remminafile, "ignore-tls-errors", FALSE)) {
+#if WEBKIT_CHECK_VERSION(2, 32, 0)
+ webkit_website_data_manager_set_tls_errors_policy(
+ gpdata->data_mgr, WEBKIT_TLS_ERRORS_POLICY_IGNORE);
+#else
+ webkit_web_context_set_tls_errors_policy(
+ gpdata->context, WEBKIT_TLS_ERRORS_POLICY_IGNORE);
+#endif
+ REMMINA_PLUGIN_DEBUG("Ignore TLS errors");
+ }
+ if (remmina_plugin_service->file_get_string(remminafile, "proxy-url")) {
+ gchar *proxyurl = g_strdup(remmina_plugin_service->file_get_string(remminafile, "proxy-url"));
+ WebKitNetworkProxySettings *proxy_settings = webkit_network_proxy_settings_new(proxyurl, NULL);
+#if WEBKIT_CHECK_VERSION(2, 32, 0)
+ webkit_website_data_manager_set_network_proxy_settings(
+ gpdata->data_mgr, WEBKIT_NETWORK_PROXY_MODE_CUSTOM, proxy_settings);
+#else
+ webkit_web_context_set_network_proxy_settings(
+ gpdata->context, WEBKIT_NETWORK_PROXY_MODE_CUSTOM, proxy_settings);
+#endif
+ webkit_network_proxy_settings_free(proxy_settings);
+ g_free(proxyurl);
+ }
+
+ webkit_web_context_set_automation_allowed(gpdata->context, TRUE);
+ webkit_settings_set_javascript_can_open_windows_automatically(gpdata->settings, TRUE);
+ webkit_settings_set_allow_modal_dialogs(gpdata->settings, TRUE);
+
+#if !WEBKIT_CHECK_VERSION(2, 38, 0)
+ /** Frames flattening
+ * Some websites engage in embedding frames-inside-of-frames. WebKit has
+ * the ability to flatten them so they behave, when scrolling, as one big
+ * frame. If for some reason it is not enabled, go ahead and turn it on.
+ */
+ if (!webkit_settings_get_enable_frame_flattening(gpdata->settings))
+ webkit_settings_set_enable_frame_flattening(gpdata->settings, true);
+#endif
+
+ webkit_settings_set_enable_resizable_text_areas(gpdata->settings, true);
+
+ g_signal_connect(G_OBJECT(gpdata->context), "download-started",
+ G_CALLBACK(remmina_plugin_www_download_started), gp);
+}
+
+static gboolean remmina_plugin_www_on_auth(WebKitWebView *webview, WebKitAuthenticationRequest *request, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ gchar *s_username, *s_password;
+ gint ret;
+ RemminaPluginWWWData *gpdata;
+ gboolean save;
+ gboolean disablepasswordstoring;
+ RemminaFile *remminafile;
+
+ gpdata = (RemminaPluginWWWData *)g_object_get_data(G_OBJECT(gp), "plugin-data");
+
+ REMMINA_PLUGIN_DEBUG("Authenticate");
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ disablepasswordstoring = remmina_plugin_service->file_get_int(remminafile, "disablepasswordstoring", FALSE);
+ ret = remmina_plugin_service->protocol_plugin_init_auth(gp,
+ (disablepasswordstoring ? 0 : REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD) | REMMINA_MESSAGE_PANEL_FLAG_USERNAME,
+ _("Enter WWW authentication credentials"),
+ remmina_plugin_service->file_get_string(remminafile, "username"),
+ remmina_plugin_service->file_get_string(remminafile, "password"),
+ NULL,
+ NULL);
+ if (ret == GTK_RESPONSE_OK) {
+ s_username = remmina_plugin_service->protocol_plugin_init_get_username(gp);
+ s_password = remmina_plugin_service->protocol_plugin_init_get_password(gp);
+
+ save = remmina_plugin_service->protocol_plugin_init_get_savepassword(gp);
+ if (save) {
+ remmina_plugin_service->file_set_string(remminafile, "username", s_username);
+ remmina_plugin_service->file_set_string(remminafile, "password", s_password);
+ } else {
+ remmina_plugin_service->file_set_string(remminafile, "username", NULL);
+ remmina_plugin_service->file_set_string(remminafile, "password", NULL);
+ }
+ if (request) {
+ gpdata->credentials = webkit_credential_new(
+ g_strdup(s_username),
+ g_strdup(s_password),
+ WEBKIT_CREDENTIAL_PERSISTENCE_FOR_SESSION);
+ webkit_authentication_request_authenticate(request, gpdata->credentials);
+ webkit_credential_free(gpdata->credentials);
+ }
+ if (s_username) g_free(s_username);
+ if (s_password) g_free(s_password);
+ gpdata->authenticated = TRUE;
+ } else {
+ gpdata->authenticated = FALSE;
+ }
+
+ return gpdata->authenticated;
+}
+
+static void remmina_plugin_www_form_auth(WebKitWebView *webview,
+ WebKitLoadEvent load_event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ gchar *s_js;
+ GString *jsstr;
+ RemminaPluginWWWData *gpdata;
+ RemminaFile *remminafile;
+ gchar *remmina_dir;
+ gchar *www_js_file = NULL;
+ GError *error = NULL;
+
+ gpdata = (RemminaPluginWWWData *)g_object_get_data(G_OBJECT(gp), "plugin-data");
+ if (gpdata && !gpdata->formauthenticated)
+ gpdata->formauthenticated = FALSE;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ REMMINA_PLUGIN_DEBUG("load-changed emitted");
+
+
+ const gchar *const *dirs = g_get_system_data_dirs();
+
+ unsigned int i = 0;
+
+ for (i = 0; dirs[i] != NULL; ++i) {
+ remmina_dir = g_build_path("/", dirs[i], "remmina", "res", NULL);
+ GDir *system_data_dir = g_dir_open(remmina_dir, 0, &error);
+ // ignoring this error is ok, because the folder may not exists
+ if (error) {
+ g_error_free(error);
+ error = NULL;
+ } else {
+ if (system_data_dir) {
+ g_dir_close(system_data_dir);
+ g_free(www_js_file);
+ www_js_file = g_strdup_printf("%s/www-js.js", remmina_dir);
+ if (g_file_test(www_js_file, G_FILE_TEST_EXISTS))
+ break;
+ }
+ }
+ g_free(remmina_dir);
+ }
+
+ switch (load_event) {
+ case WEBKIT_LOAD_STARTED:
+ REMMINA_PLUGIN_DEBUG("Load started");
+ break;
+ case WEBKIT_LOAD_REDIRECTED:
+ REMMINA_PLUGIN_DEBUG("Load redirected");
+ break;
+ case WEBKIT_LOAD_COMMITTED:
+ /* The load is being performed. Current URI is
+ * the final one and it won't change unless a new
+ * load is requested or a navigation within the
+ * same page is performed
+ * uri = webkit_web_view_get_uri (webview); */
+ REMMINA_PLUGIN_DEBUG("Load committed");
+ break;
+ case WEBKIT_LOAD_FINISHED:
+ /* Load finished, we can now set user/password
+ * in the HTML form */
+ REMMINA_PLUGIN_DEBUG("Load finished");
+ if (gpdata && gpdata->formauthenticated == TRUE)
+ break;
+
+ if (remmina_plugin_service->file_get_string(remminafile, "username") ||
+ remmina_plugin_service->file_get_string(remminafile, "password")) {
+ REMMINA_PLUGIN_DEBUG("Authentication is enabled");
+ if (www_js_file) {
+ error = NULL;
+ if (g_file_get_contents(www_js_file, &s_js, NULL, &error)) {
+ jsstr = g_string_new(s_js);
+ if (remmina_plugin_service->file_get_string(remminafile, "username"))
+ www_utils_string_replace_all(jsstr, "USRPLACEHOLDER",
+ remmina_plugin_service->file_get_string(remminafile, "username"));
+ if (remmina_plugin_service->file_get_string(remminafile, "password"))
+ www_utils_string_replace_all(jsstr, "PWDPLACEHOLDER",
+ remmina_plugin_service->file_get_string(remminafile, "password"));
+ s_js = g_string_free(jsstr, FALSE);
+
+ if (!s_js || s_js[0] == '\0') {
+ break;
+ } else {
+ REMMINA_PLUGIN_DEBUG("Trying to send this JavaScript: %s", s_js);
+ webkit_web_view_run_javascript(
+ webview,
+ s_js,
+ NULL,
+ remmina_www_web_view_js_finished,
+ gp);
+ g_free(s_js);
+ }
+ } else {
+ if (error) {
+ REMMINA_PLUGIN_DEBUG("Unable to read file: %s\n", error->message);
+ g_error_free(error);
+ } else {
+ REMMINA_PLUGIN_DEBUG("Unable to read file. No error returned from glib.\n");
+ }
+ }
+ }
+ }
+ break;
+ }
+}
+
+static gboolean remmina_plugin_www_close_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginWWWData *gpdata;
+
+ gpdata = (RemminaPluginWWWData *)g_object_get_data(G_OBJECT(gp), "plugin-data");
+
+ webkit_web_view_stop_loading(gpdata->webview);
+ webkit_web_view_try_close(gpdata->webview);
+
+ if (gpdata->url) g_free(gpdata->url);
+ gpdata->authenticated = FALSE;
+ gpdata->formauthenticated = FALSE;
+ gpdata->webview = NULL;
+ gpdata->data_mgr = NULL;
+ gpdata->settings = NULL;
+ gpdata->context = NULL;
+
+ /* Remove instance->context from gp object data to avoid double free */
+ g_object_steal_data(G_OBJECT(gp), "plugin-data");
+
+ remmina_plugin_service->protocol_plugin_signal_connection_closed(gp);
+ return FALSE;
+}
+
+static gboolean remmina_plugin_www_open_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ RemminaPluginWWWData *gpdata;
+ RemminaFile *remminafile;
+
+ gpdata = (RemminaPluginWWWData *)g_object_get_data(G_OBJECT(gp), "plugin-data");
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ gpdata->box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add(GTK_CONTAINER(gp), gpdata->box);
+
+ remmina_plugin_service->protocol_plugin_register_hostkey(gp, gpdata->box);
+
+ gpdata->webview = WEBKIT_WEB_VIEW(webkit_web_view_new_with_context(gpdata->context));
+ webkit_web_view_set_settings(gpdata->webview, gpdata->settings);
+
+ if (remmina_plugin_service->file_get_string(remminafile, "username") ||
+ remmina_plugin_service->file_get_string(remminafile, "password")) {
+ REMMINA_PLUGIN_DEBUG("Authentication is enabled");
+ remmina_plugin_www_on_auth(gpdata->webview, NULL, gp);
+ }
+
+ //"signal::load-failed-with-tls-errors", G_CALLBACK(remmina_plugin_www_load_failed_tls_cb), gp,
+ g_object_connect(
+ G_OBJECT(gpdata->webview),
+ "signal::create", G_CALLBACK(remmina_plugin_www_on_create), gp,
+ "signal::load-changed", G_CALLBACK(remmina_plugin_www_form_auth), gp,
+ "signal::authenticate", G_CALLBACK(remmina_plugin_www_on_auth), gp,
+ "signal::decide-policy", G_CALLBACK(remmina_plugin_www_decide_policy_cb), gp,
+ NULL);
+
+ gtk_widget_set_hexpand(GTK_WIDGET(gpdata->webview), TRUE);
+ gtk_widget_set_vexpand(GTK_WIDGET(gpdata->webview), TRUE);
+ gtk_container_add(GTK_CONTAINER(gpdata->box), GTK_WIDGET(gpdata->webview));
+ webkit_web_view_load_uri(gpdata->webview, gpdata->url);
+#ifdef DEBUG
+ if (remmina_plugin_service->file_get_int(remminafile, "enable-webinspector", FALSE)) {
+ REMMINA_PLUGIN_DEBUG("WebInspector enabled");
+ WebKitWebInspector *inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(gpdata->webview));
+ webkit_web_inspector_attach(inspector);
+ webkit_web_inspector_show(WEBKIT_WEB_INSPECTOR(inspector));
+ }
+#endif
+ remmina_plugin_service->protocol_plugin_signal_connection_opened(gp);
+ gtk_widget_show_all(gpdata->box);
+
+ return TRUE;
+}
+
+static void remmina_plugin_www_save_snapshot(GObject *object, GAsyncResult *result, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ WebKitWebView *webview = WEBKIT_WEB_VIEW(object);
+
+ RemminaFile *remminafile;
+
+ GError *err = NULL;
+ cairo_surface_t *surface;
+ //unsigned char* buffer;
+ int width;
+ int height;
+ GdkPixbuf *screenshot;
+ GString *pngstr;
+ gchar *pngname;
+ //cairo_forma_t* cairo_format;
+ GDateTime *date = g_date_time_new_now_utc();
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ surface = webkit_web_view_get_snapshot_finish(WEBKIT_WEB_VIEW(webview), result, &err);
+ if (err)
+ REMMINA_PLUGIN_DEBUG("An error happened generating the snapshot: %s\n", err->message);
+ //buffer = cairo_image_surface_get_data (surface);
+ width = cairo_image_surface_get_width(surface);
+ height = cairo_image_surface_get_height(surface);
+ //cairo_format = cairo_image_surface_get_format (surface);
+
+ screenshot = gdk_pixbuf_get_from_surface(surface, 0, 0, width, height);
+ if (screenshot == NULL)
+ REMMINA_PLUGIN_DEBUG("WWW: gdk_pixbuf_get_from_surface failed");
+
+ pngstr = g_string_new(g_strdup_printf("%s/%s.png",
+ remmina_plugin_service->pref_get_value("screenshot_path"),
+ remmina_plugin_service->pref_get_value("screenshot_name")));
+ www_utils_string_replace_all(pngstr, "%p",
+ remmina_plugin_service->file_get_string(remminafile, "name"));
+ www_utils_string_replace_all(pngstr, "%h", "URL");
+ www_utils_string_replace_all(pngstr, "%Y",
+ g_strdup_printf("%d", g_date_time_get_year(date)));
+ www_utils_string_replace_all(pngstr, "%m", g_strdup_printf("%d",
+ g_date_time_get_month(date)));
+ www_utils_string_replace_all(pngstr, "%d",
+ g_strdup_printf("%d", g_date_time_get_day_of_month(date)));
+ www_utils_string_replace_all(pngstr, "%H",
+ g_strdup_printf("%d", g_date_time_get_hour(date)));
+ www_utils_string_replace_all(pngstr, "%M",
+ g_strdup_printf("%d", g_date_time_get_minute(date)));
+ www_utils_string_replace_all(pngstr, "%S",
+ g_strdup_printf("%f", g_date_time_get_seconds(date)));
+ g_date_time_unref(date);
+ pngname = g_string_free(pngstr, FALSE);
+ REMMINA_PLUGIN_DEBUG("Saving screenshot as %s", pngname);
+
+ cairo_surface_write_to_png(surface, pngname);
+ if (g_file_test(pngname, G_FILE_TEST_EXISTS))
+ www_utils_send_notification("www-plugin-screenshot-is-ready-id", _("Screenshot taken"), pngname);
+
+ cairo_surface_destroy(surface);
+}
+static gboolean remmina_plugin_www_get_snapshot(RemminaProtocolWidget *gp, RemminaPluginScreenshotData *rpsd)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginWWWData *gpdata;
+
+ gpdata = (RemminaPluginWWWData *)g_object_get_data(G_OBJECT(gp), "plugin-data");
+
+ webkit_web_view_get_snapshot(gpdata->webview,
+ WEBKIT_SNAPSHOT_REGION_FULL_DOCUMENT,
+ WEBKIT_SNAPSHOT_OPTIONS_NONE,
+ NULL,
+ (GAsyncReadyCallback)remmina_plugin_www_save_snapshot,
+ gp);
+ return FALSE;
+}
+
+/* Array of RemminaProtocolSetting for basic settings.
+ * Each item is composed by:
+ * a) RemminaProtocolSettingType for setting type
+ * b) Setting name
+ * c) Setting description
+ * d) Compact disposition
+ * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO
+ * f) Setting tooltip
+ * g) Validation data pointer, will be passed to the validation callback method.
+ * h) Validation callback method (Can be NULL. Every entry will be valid then.)
+ * use following prototype:
+ * gboolean mysetting_validator_method(gpointer key, gpointer value,
+ * gpointer validator_data);
+ * gpointer key is a gchar* containing the setting's name,
+ * gpointer value contains the value which should be validated,
+ * gpointer validator_data contains your passed data.
+ */
+static const RemminaProtocolSetting remmina_plugin_www_basic_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "server", N_("URL"), FALSE, NULL, N_("http://address or https://address"), NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "username", N_("Username"), FALSE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "password", N_("Password"), FALSE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL, NULL, NULL }
+};
+
+/* Array of RemminaProtocolSetting for advanced settings.
+ * Each item is composed by:
+ * a) RemminaProtocolSettingType for setting type
+ * b) Setting name
+ * c) Setting description
+ * d) Compact disposition
+ * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO
+ * f) Setting Tooltip
+ */
+static const RemminaProtocolSetting remmina_plugin_www_advanced_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "user-agent", N_("User agent"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "proxy-url", N_("Proxy URL"), FALSE, NULL, N_("E.g. https://example.org, socks://mysocks:1080") },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "enable-java", N_("Turn on Java support"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "enable-smooth-scrolling", N_("Turn on smooth scrolling"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "enable-spatial-navigation", N_("Turn on spatial navigation"), TRUE, NULL, NULL },
+#if WEBKIT_CHECK_VERSION(2, 32, 0)
+#else
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "enable-plugins", N_("Turn on plugin support"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "enable-webgl", N_("Turn on WebGL support"), TRUE, NULL, NULL },
+#endif
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "enable-webaudio", N_("Turn on HTML5 audio support"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "ignore-tls-errors", N_("Ignore TLS errors"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disablepasswordstoring", N_("Forget passwords after use"), TRUE, NULL, NULL },
+#ifdef DEBUG
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "enable-webinspector", N_("Turn on Web Inspector"), TRUE, NULL, NULL },
+#endif
+ { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL }
+};
+
+/* Array for available features.
+ * The last element of the array must be REMMINA_PROTOCOL_FEATURE_TYPE_END. */
+static const RemminaProtocolFeature remmina_www_features[] =
+{
+ { REMMINA_PROTOCOL_FEATURE_TYPE_END, 0, NULL, NULL, NULL }
+};
+
+/* Protocol plugin definition and features */
+static RemminaProtocolPlugin remmina_plugin =
+{
+ REMMINA_PLUGIN_TYPE_PROTOCOL, // Type
+ PLUGIN_NAME, // Name
+ PLUGIN_DESCRIPTION, // Description
+ GETTEXT_PACKAGE, // Translation domain
+ PLUGIN_VERSION, // Version number
+ PLUGIN_APPICON, // Icon for normal connection
+ NULL, // Icon for SSH connection
+ remmina_plugin_www_basic_settings, // Array for basic settings
+ remmina_plugin_www_advanced_settings, // Array for advanced settings
+ REMMINA_PROTOCOL_SSH_SETTING_NONE, // SSH settings type
+ remmina_www_features, // Array for available features
+ remmina_plugin_www_init, // Plugin initialization
+ remmina_plugin_www_open_connection, // Plugin open connection
+ remmina_plugin_www_close_connection, // Plugin close connection
+ remmina_www_query_feature, // Query for available features
+ NULL, // Call feature
+ NULL, // Send keystroke
+ remmina_plugin_www_get_snapshot, // Capture screenshot
+ NULL, // RCW map event
+ NULL // RCW unmap event
+};
+
+G_MODULE_EXPORT gboolean remmina_plugin_entry(RemminaPluginService *service)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_service = service;
+
+ bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+
+
+ if (!service->register_plugin((RemminaPlugin *)&remmina_plugin))
+ return FALSE;
+ return TRUE;
+}
diff --git a/plugins/www/www_plugin.h b/plugins/www/www_plugin.h
new file mode 100644
index 0000000..73caeba
--- /dev/null
+++ b/plugins/www/www_plugin.h
@@ -0,0 +1,80 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#pragma once
+
+#include "common/remmina_plugin.h"
+
+#include <glib.h>
+#include <webkit2/webkit2.h>
+
+typedef enum {
+ WWW_WEB_VIEW_DOCUMENT_HTML,
+ WWW_WEB_VIEW_DOCUMENT_XML,
+ WWW_WEB_VIEW_DOCUMENT_IMAGE,
+ WWW_WEB_VIEW_DOCUMENT_OCTET_STREAM,
+ WWW_WEB_VIEW_DOCUMENT_OTHER
+} WWWWebViewDocumentType;
+
+extern RemminaPluginService *remmina_plugin_service;
+
+#define REMMINA_PLUGIN_INFO(fmt, ...) \
+ remmina_plugin_service->_remmina_info(__func__, fmt, ##__VA_ARGS__)
+
+#define REMMINA_PLUGIN_MESSAGE(fmt, ...) \
+ remmina_plugin_service->_remmina_message(__func, fmt, ##__VA_ARGS__)
+
+#define REMMINA_PLUGIN_DEBUG(fmt, ...) \
+ remmina_plugin_service->_remmina_debug(__func__, fmt, ##__VA_ARGS__)
+
+#define REMMINA_PLUGIN_WARNING(fmt, ...) \
+ remmina_plugin_service->_remmina_warning(__func__, fmt, ##__VA_ARGS__)
+
+/* This will intentionally crash Remmina */
+#define REMMINA_PLUGIN_ERROR(fmt, ...) \
+ remmina_plugin_service->_remmina_error(__func__, fmt, ##__VA_ARGS__)
+
+#define REMMINA_PLUGIN_CRITICAL(fmt, ...) \
+ remmina_plugin_service->_remmina_critical(__func__, fmt, ##__VA_ARGS__)
+#define REMMINA_PLUGIN_AUDIT(fmt, ...) \
+ remmina_plugin_service->_remmina_audit(__func__, fmt, ##__VA_ARGS__)
+
+G_BEGIN_DECLS
+void remmina_plugin_www_decide_nav(WebKitPolicyDecision *decision, RemminaProtocolWidget *gp);
+void remmina_plugin_www_decide_newwin(WebKitPolicyDecision *decision, RemminaProtocolWidget *gp);
+gboolean remmina_plugin_www_decide_resource(WebKitPolicyDecision *decision, RemminaProtocolWidget *gp);
+void remmina_plugin_www_response_received(WebKitDownload *download, GParamSpec *ps, RemminaProtocolWidget *gp);
+void remmina_plugin_www_notify_download(WebKitDownload *download, gchar *destination, RemminaProtocolWidget *gp);
+
+G_END_DECLS
diff --git a/plugins/www/www_utils.c b/plugins/www/www_utils.c
new file mode 100644
index 0000000..fe5fa0a
--- /dev/null
+++ b/plugins/www/www_utils.c
@@ -0,0 +1,154 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+/* Some utils taken form remmina_utils
+* TODO: use directly remmina_utils */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/utsname.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include "remmina/remmina_trace_calls.h"
+
+/** Returns @c TRUE if @a ptr is @c NULL or @c *ptr is @c FALSE. */
+#define EMPTY(ptr) \
+ (!(ptr) || !*(ptr))
+
+/* Used to send desktop notifications */
+void www_utils_send_notification(const gchar *notification_id,
+ const gchar *notification_title, const gchar *notification_message)
+{
+ TRACE_CALL(__func__);
+
+ GNotification *notification = g_notification_new(notification_title);
+ g_notification_set_body(notification, notification_message);
+#if GLIB_CHECK_VERSION(2, 42, 0)
+ g_notification_set_priority(notification, G_NOTIFICATION_PRIORITY_NORMAL);
+#endif
+ g_application_send_notification(g_application_get_default(), notification_id, notification);
+ g_object_unref(notification);
+}
+
+gint www_utils_strpos(const gchar *haystack, const gchar *needle)
+{
+ TRACE_CALL(__func__);
+ const gchar *sub;
+
+ if (!*needle)
+ return -1;
+
+ sub = strstr(haystack, needle);
+ if (!sub)
+ return -1;
+
+ return sub - haystack;
+}
+
+/* end can be -1 for haystack->len.
+ * returns: position of found text or -1.
+ * (C) Taken from geany */
+gint www_utils_string_find(GString *haystack, gint start, gint end, const gchar *needle)
+{
+ TRACE_CALL(__func__);
+ gint pos;
+
+ g_return_val_if_fail(haystack != NULL, -1);
+ if (haystack->len == 0)
+ return -1;
+
+ g_return_val_if_fail(start >= 0, -1);
+ if (start >= (gint)haystack->len)
+ return -1;
+
+ g_return_val_if_fail(!EMPTY(needle), -1);
+
+ if (end < 0)
+ end = haystack->len;
+
+ pos = www_utils_strpos(haystack->str + start, needle);
+ if (pos == -1)
+ return -1;
+
+ pos += start;
+ if (pos >= end)
+ return -1;
+ return pos;
+}
+
+/* Replaces @len characters from offset @a pos.
+ * len can be -1 to replace the remainder of @a str.
+ * returns: pos + strlen(replace).
+ * (C) Taken from geany */
+gint www_utils_string_replace(GString *str, gint pos, gint len, const gchar *replace)
+{
+ TRACE_CALL(__func__);
+ g_string_erase(str, pos, len);
+ if (replace) {
+ g_string_insert(str, pos, replace);
+ pos += strlen(replace);
+ }
+ return pos;
+}
+
+/**
+ * Replaces all occurrences of @a needle in @a haystack with @a replace.
+ *
+ * @param haystack The input string to operate on. This string is modified in place.
+ * @param needle The string which should be replaced.
+ * @param replace The replacement for @a needle.
+ *
+ * @return Number of replacements made.
+ **/
+guint www_utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace)
+{
+ TRACE_CALL(__func__);
+ guint count = 0;
+ gint pos = 0;
+ gsize needle_length = strlen(needle);
+
+ while (1) {
+ pos = www_utils_string_find(haystack, pos, -1, needle);
+
+ if (pos == -1)
+ break;
+
+ pos = www_utils_string_replace(haystack, pos, needle_length, replace);
+ count++;
+ }
+ return count;
+}
diff --git a/plugins/www/www_utils.h b/plugins/www/www_utils.h
new file mode 100644
index 0000000..b5fab5e
--- /dev/null
+++ b/plugins/www/www_utils.h
@@ -0,0 +1,47 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+/* Some utils taken form remmina_utils
+* TODO: use directly remmina_utils */
+
+#pragma once
+
+#include "common/remmina_plugin.h"
+
+G_BEGIN_DECLS
+gint www_utils_string_find(GString *haystack, gint start, gint end, const gchar *needle);
+gint www_utils_string_replace(GString *str, gint pos, gint len, const gchar *replace);
+guint www_utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace);
+void www_utils_send_notification(const gchar *notification_id, const gchar *notification_title, const gchar *notification_message);
+G_END_DECLS