This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch master
in repository enlightenment.

View the commit online.

commit 257d8397b1719f0b79571b0cbc55e5b0d2f27f75
Author: Carsten Haitzler <[email protected]>
AuthorDate: Tue Sep 15 11:16:49 2026 +0100

    networkmanager: add online captive portal login url refresh handling
    
    some wifi networks send you to a default page no matter what url you
    go to for a "login page". try auto detect this and open your browser
    to do just this for those networks.
---
 src/modules/networkmanager/e_mod_main.c            |  22 ++
 src/modules/networkmanager/e_mod_main.h            |   2 +
 .../networkmanager/e_networkmanager_online.c       | 284 +++++++++++++++++++++
 .../networkmanager/e_networkmanager_online.h       |  22 ++
 src/modules/networkmanager/meson.build             |   2 +
 5 files changed, 332 insertions(+)

diff --git a/src/modules/networkmanager/e_mod_main.c b/src/modules/networkmanager/e_mod_main.c
index cef0091ef..d0df5a11a 100644
--- a/src/modules/networkmanager/e_mod_main.c
+++ b/src/modules/networkmanager/e_mod_main.c
@@ -2420,13 +2420,29 @@ _enm_mod_manager_update_inst(E_NM_Module_Context *ctxt EINA_UNUSED,
 static void
 _enm_connected(Eina_Bool online)
 {
+   E_NM_Module_Context *ctxt = networkmanager_mod->data;
+
    INF("Network connected (_online_=%d)", online);
+   if (online) enm_online_check_stop(ctxt->online_check);
+   else enm_online_check_start(ctxt->online_check);
 }
 
 static void
 _enm_disconnected(void)
 {
+   E_NM_Module_Context *ctxt = networkmanager_mod->data;
+
    INF("Network disconnected");
+   enm_online_check_stop(ctxt->online_check);
+}
+
+static void
+_enm_online_check_done(void *data, Eina_Bool online)
+{
+   E_NM_Module_Context *ctxt = data;
+
+   ctxt->_online_ = online;
+   _enm_connected(online);
 }
 
 static Eina_Bool
@@ -2460,6 +2476,7 @@ _enm_connection_state_update(E_NM_Module_Context *ctxt, enum NM_State state)
 
    ctxt->pending_connected = connected;
    ctxt->pending_online = online;
+   enm_online_check_state_set(ctxt->online_check, connected, online);
    E_FREE_FUNC(ctxt->connection_timer, ecore_timer_del);
 
    /* A brief excursion back to the last reported state needs no callback. */
@@ -3254,6 +3271,9 @@ e_modapi_init(E_Module *m)
    ctxt = E_NEW(E_NM_Module_Context, 1);
    if (!ctxt) goto error_nm_context;
 
+   ctxt->_online_check_ = enm_online_check_new(_enm_online_check_done, ctxt);
+   if (!ctxt->online_check) goto error_nm_system_init;
+
    ctxt->pipe_fd = -1;
    ctxt->poll_time = networkmanager_config->poll_time;
    e_nm_module_callbacks_set(&_enm_mod_cbs);
@@ -3277,6 +3297,7 @@ e_modapi_init(E_Module *m)
    return ctxt;
 
 error_nm_system_init:
+   enm_online_check_free(ctxt->online_check);
    E_FREE(ctxt);
 error_nm_context:
    eina_log_domain_unregister(_e_nm_log_dom);
@@ -3324,6 +3345,7 @@ e_modapi_shutdown(E_Module *m)
    E_FREE_FUNC(ctxt->popup_update_timer, ecore_timer_del);
    E_FREE_FUNC(ctxt->connection_timer, ecore_timer_del);
    E_FREE_FUNC(ctxt->vpn_update_job, ecore_job_del);
+   E_FREE_FUNC(ctxt->online_check, enm_online_check_free);
    _enm_traffic_timer_stop(ctxt);
    E_FREE_FUNC(ctxt->powersave_handler, ecore_event_handler_del);
    E_FREE(ctxt);
diff --git a/src/modules/networkmanager/e_mod_main.h b/src/modules/networkmanager/e_mod_main.h
index 9eb2384e7..e326817a7 100644
--- a/src/modules/networkmanager/e_mod_main.h
+++ b/src/modules/networkmanager/e_mod_main.h
@@ -4,6 +4,7 @@
 #include <e.h>
 
 #include "e_networkmanager.h"
+#include "e_networkmanager_online.h"
 
 #define AGENT_PATH "/org/freedesktop/NetworkManager/SecretAgent"
 #define NETWORKMANAGER_CONFIG_VERSION 1
@@ -76,6 +77,7 @@ struct E_NM_Module_Context
    Ecore_Job          *vpn_update_job;   /* deferred VPN-active-changed notify */
 
    struct NM_Manager  *nm;
+   Enm_Online_Check   *online_check;
 
    /* Network activity indicator.  Polling happens on a background thread
     * via ecore_thread_feedback_run; samples are delivered back to the main
diff --git a/src/modules/networkmanager/e_networkmanager_online.c b/src/modules/networkmanager/e_networkmanager_online.c
new file mode 100644
index 000000000..7251018ec
--- /dev/null
+++ b/src/modules/networkmanager/e_networkmanager_online.c
@@ -0,0 +1,284 @@
+#include "e.h"
+#include "e_networkmanager_online.h"
+
+struct Enm_Online_Check
+{
+   Ecore_Con_Url       *url;
+   Eina_Binbuf         *body;
+   Ecore_Event_Handler *data_handler;
+   Ecore_Event_Handler *complete_handler;
+   Enm_Online_Cb        cb;
+   void                *cb_data;
+   Eina_Bool            url_initialized : 1;
+   Eina_Bool            connected : 1;
+   Eina_Bool            online : 1;
+};
+
+Enm_Online_Check *
+enm_online_check_new(Enm_Online_Cb cb, void *data)
+{
+   Enm_Online_Check *check = calloc(1, sizeof(*check));
+
+   if (!check) return NULL;
+   check->cb = cb;
+   check->cb_data = data;
+   return check;
+}
+
+void
+enm_online_check_state_set(Enm_Online_Check *check, Eina_Bool connected,
+                            Eina_Bool online)
+{
+   if (!check) return;
+   check->connected = connected;
+   check->_online_ = online;
+}
+
+void
+enm_online_check_stop(Enm_Online_Check *check)
+{
+   if (!check) return;
+   E_FREE_FUNC(check->data_handler, ecore_event_handler_del);
+   E_FREE_FUNC(check->complete_handler, ecore_event_handler_del);
+   E_FREE_FUNC(check->url, ecore_con_url_free);
+   E_FREE_FUNC(check->body, eina_binbuf_free);
+}
+
+static char *
+_enm_refresh_content_decode(const char *content)
+{
+   Eina_Strbuf *buf = eina_strbuf_new();
+   const char *p;
+   char *decoded;
+
+   if (!buf) return NULL;
+   for (p = content; *p; p++)
+     {
+        if (*p == '&')
+          {
+             const char *end = p + 1, *escape;
+
+             if (*end == '#') end++;
+             while (isalnum((unsigned char)*end)) end++;
+             if (*end == ';')
+               {
+                  escape = evas_textblock_escape_string_range_get(p, end + 1);
+                  if (escape)
+                    {
+                       if (!eina_strbuf_append(buf, escape)) goto error;
+                       p = end;
+                       continue;
+                    }
+               }
+          }
+        if (!eina_strbuf_append_char(buf, *p)) goto error;
+     }
+   decoded = eina_strbuf_string_steal(buf);
+   eina_strbuf_free(buf);
+   return decoded;
+
+error:
+   eina_strbuf_free(buf);
+   return NULL;
+}
+
+static char *
+_enm_refresh_url_get(const char *content)
+{ // parse the refresh url string ignoring the initial delay
+   char *decoded, *p, *end, *url = ""
+
+   // Decode HTML entities, preserving literal '&' in portal query strings.
+   decoded = _enm_refresh_content_decode(content);
+   if (!decoded) return NULL;
+   // skip after initial 3; timeout
+   p = strchr(decoded, ';');
+   if (!p) goto end;
+   p++;
+   // skip following shitespace if any
+   while (isspace((unsigned char)*p)) p++;
+   // look for "url" of url=""
+   if (strncasecmp(p, "url", 3)) goto end;
+   p += 3;
+   // skip whitespace if any
+   while (isspace((unsigned char)*p)) p++;
+   // fine the = and skip it once found
+   if (*p != '=') goto end;
+   p++;
+   // skip any more whitespace
+   while (isspace((unsigned char)*p)) p++;
+   end = p + strlen(p);
+   // trim whitespace at end by reducing end ptr if any is there
+   while ((end > p) && isspace((unsigned char)end[-1])) end--;
+   if ((*p == '\'') || (*p == '"'))
+     { // trim url from p to end
+        if (((end - p) < 2) || (end[-1] != *p)) goto end;
+        p++;
+        end--;
+     }
+   // truncate at end fo url string is p
+   *end = 0;
+   //  url must start with http:// or https:// only
+   if ((!strncasecmp(p, "http://", 7) && p[7]) ||
+       (!strncasecmp(p, "https://", 8) && p[8]))
+     url = ""
+end:
+   free(decoded);
+   L("NM: Network login portal url: %s", url);
+   return url;
+}
+
+static Eina_Bool
+_enm_refresh_tag_cb(void *data, Eina_Simple_XML_Type type,
+                    const char *content, unsigned int offset EINA_UNUSED,
+                    unsigned int length)
+{
+   // parse the html body and look for:
+   //   <meta http-equiv="refresh" content="0; url="" />
+   char **url = ""
+   const char *p = content, *end = content + length;
+   char *refresh = NULL;
+   Eina_Bool is_refresh = EINA_FALSE;
+
+   // not open and not empty tag -= ignore
+   if ((type != EINA_SIMPLE_XML_OPEN) &&
+       (type != EINA_SIMPLE_XML_OPEN_EMPTY)) return EINA_TRUE;
+   // if its NOT some <meta ... tag then skip it
+   if ((length <= 4) || (!!strncasecmp(p, "meta", 4)) ||
+       (!isspace((unsigned char)p[4]))) return EINA_TRUE;
+   // skip the "meta" start of the tag
+   p += 4;
+
+   // Attribute order, case, and quoting vary in portal HTML. Parse within
+   // the tag bounds without putting server-supplied data on the stack.
+   while (p < end)
+     {
+        const char *key, *value;
+        size_t key_len, value_len;
+        char quote = 0;
+
+        while ((p < end) && isspace((unsigned char)*p)) p++;
+        key = p;
+        while ((p < end) && !isspace((unsigned char)*p) &&
+               (*p != '=') && (*p != '/')) p++;
+        key_len = p - key;
+        if (!key_len) break;
+        while ((p < end) && isspace((unsigned char)*p)) p++;
+        if ((p == end) || (*p != '=')) continue;
+        p++;
+        while ((p < end) && isspace((unsigned char)*p)) p++;
+        if ((p < end) && ((*p == '\'') || (*p == '"'))) quote = *p++;
+        value = p;
+        while ((p < end) &&
+               (quote ? (*p != quote) : !isspace((unsigned char)*p))) p++;
+        if (quote && (p == end)) break;
+        value_len = p - value;
+        if (quote) p++;
+
+        if ((key_len == 10) && (!strncasecmp(key, "http-equiv", 10)))
+          is_refresh = (value_len == 7) && (!strncasecmp(value, "refresh", 7));
+        else if ((key_len == 7) && (!strncasecmp(key, "content", 7)))
+          {
+             free(refresh);
+             refresh = strndup(value, value_len);
+          }
+     }
+   if (is_refresh && refresh) *url = ""
+   free(refresh);
+   return !*url;
+}
+
+static Eina_Bool
+_enm_online_data_cb(void *data, int type EINA_UNUSED, void *event)
+{
+   Enm_Online_Check *check = data;
+   Ecore_Con_Event_Url_Data *ev = event;
+
+   if (ev->url_con != check->url) return ECORE_CALLBACK_PASS_ON;
+   // append data to our binbuf
+   if ((ev->size > 0) &&
+       (!eina_binbuf_append_length(check->body, ev->data, ev->size)))
+     // append failed - abort our online check
+     enm_online_check_stop(check);
+   return ECORE_CALLBACK_PASS_ON;
+}
+
+static Eina_Bool
+_enm_online_complete_cb(void *data, int type EINA_UNUSED, void *event)
+{
+   Enm_Online_Check *check = data;
+   Ecore_Con_Event_Url_Complete *ev = event;
+   Enm_Online_Cb cb = check->cb;
+   void *cb_data = check->cb_data;
+   Eina_Bool _online_ = EINA_FALSE;
+   char *url = ""
+
+   if (ev->url_con != check->url) return ECORE_CALLBACK_PASS_ON;
+   // Ignore a response while NM is disconnecting or already reports online.
+   if ((ev->status >= 200) && check->connected && !check->online)
+     {
+        const unsigned char *body = eina_binbuf_string_get(check->body);
+        size_t length = eina_binbuf_length_get(check->body);
+
+        // contebnt is "OK" thus we are successfully online
+        _online_ = (length == 2) && (!memcmp(body, "OK", 2));
+        if (!online && (length <= UINT_MAX)) // we are not online
+          eina_simple_xml_parse((const char *)body, length, EINA_TRUE,
+                                _enm_refresh_tag_cb, &url);
+     }
+   enm_online_check_stop(check);
+   if (online)
+     { // we are online - so just call this callback - no need for browser
+        if (cb) cb(cb_data, EINA_TRUE);
+     }
+   // we have a url meta refresh - open that in a browser
+   else if (url) e_util_open(url, NULL);
+   // we don't have a meta refreash - just open browser and hope it works
+   else if (!url) e_util_open("http://www.enlightenment.org", NULL);
+   free(url);
+   return ECORE_CALLBACK_PASS_ON;
+}
+
+void
+enm_online_check_start(Enm_Online_Check *check)
+{
+   if (!check) return;
+   if (!check->url_initialized)
+     {
+        if (!ecore_con_url_init()) return;
+        check->url_initialized = EINA_TRUE;
+     }
+   // stop prev online check
+   enm_online_check_stop(check);
+   // new place to store http fetch body data
+   check->body = eina_binbuf_new();
+   // check enlightenment.org for being online with known results
+   check->url =
"">+     ecore_con_url_new("http://www.enlightenment.org/online.php");
+   // hanlders for http fetch events
+   check->data_handler =
+     ecore_event_handler_add(ECORE_CON_EVENT_URL_DATA,
+                             _enm_online_data_cb, check);
+   check->complete_handler =
+     ecore_event_handler_add(ECORE_CON_EVENT_URL_COMPLETE,
+                             _enm_online_complete_cb, check);
+   if ((!check->body) ||
+       (!check->url) ||
+       (!check->data_handler) ||
+       (!check->complete_handler))
+     { // one of the above failed to set up - abort check
+        enm_online_check_stop(check);
+        return;
+     }
+   // max timeout to wait for some data
+   ecore_con_url_timeout_set(check->url, 15.0);
+   if (!ecore_con_url_get(check->url)) enm_online_check_stop(check);
+}
+
+void
+enm_online_check_free(Enm_Online_Check *check)
+{
+   if (!check) return;
+   enm_online_check_stop(check);
+   if (check->url_initialized) ecore_con_url_shutdown();
+   free(check);
+}
diff --git a/src/modules/networkmanager/e_networkmanager_online.h b/src/modules/networkmanager/e_networkmanager_online.h
new file mode 100644
index 000000000..6ebb0b7eb
--- /dev/null
+++ b/src/modules/networkmanager/e_networkmanager_online.h
@@ -0,0 +1,22 @@
+#ifndef E_NETWORKMANAGER_ONLINE_H
+#define E_NETWORKMANAGER_ONLINE_H
+
+#include <Eina.h>
+
+typedef struct Enm_Online_Check Enm_Online_Check;
+
+typedef void (*Enm_Online_Cb)(void *data, Eina_Bool online);
+
+/* Main-loop API.  The callback runs only when the check confirms Internet
+ * access.  Free the check before freeing its callback data. */
+Enm_Online_Check *enm_online_check_new(Enm_Online_Cb cb, void *data);
+void enm_online_check_free(Enm_Online_Check *check);
+
+/* Supply the latest network state to discard obsolete responses.  This does
+ * not start a request; the caller starts it after connectivity settles. */
+void enm_online_check_state_set(Enm_Online_Check *check, Eina_Bool connected,
+                                Eina_Bool online);
+void enm_online_check_start(Enm_Online_Check *check);
+void enm_online_check_stop(Enm_Online_Check *check);
+
+#endif
diff --git a/src/modules/networkmanager/meson.build b/src/modules/networkmanager/meson.build
index 6fb04a071..9a3a98487 100644
--- a/src/modules/networkmanager/meson.build
+++ b/src/modules/networkmanager/meson.build
@@ -4,6 +4,8 @@ src = ""
   'agent.c',
   'e_networkmanager.c',
   'e_networkmanager.h',
+  'e_networkmanager_online.c',
+  'e_networkmanager_online.h',
   'e_networkmanager_vpn.c',
   'e_networkmanager_vpn.h',
   'e_networkmanager_import.c',

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to