[WiFi Setup] Implement captive portal to automatically sign in

A captive portal allows a connected user to be redirected to the node when signing in to its AP.
If no WiFi credentials are present, it will redirect to the setup page immediately.
This commit is contained in:
TD-er
2021-04-15 14:21:31 +02:00
parent 111a9ea713
commit 3f438ae886
7 changed files with 68 additions and 19 deletions
+14 -9
View File
@@ -78,11 +78,16 @@ bool string2float(const String& string, float& floatvalue) {
/********************************************************************************************\
Convert a char string to IP byte array
\*********************************************************************************************/
boolean str2ip(const String& string, byte *IP) {
bool isIP(const String& string) {
IPAddress tmpip;
return (tmpip.fromString(string));
}
bool str2ip(const String& string, byte *IP) {
return str2ip(string.c_str(), IP);
}
boolean str2ip(const char *string, byte *IP)
bool str2ip(const char *string, byte *IP)
{
IPAddress tmpip; // Default constructor => set to 0.0.0.0
@@ -644,7 +649,7 @@ String URLEncode(const char *msg)
return encodedMsg;
}
void repl(const String& key, const String& val, String& s, boolean useURLencode)
void repl(const String& key, const String& val, String& s, bool useURLencode)
{
if (useURLencode) {
// URLEncode does take resources, so check first if needed.
@@ -656,7 +661,7 @@ void repl(const String& key, const String& val, String& s, boolean useURLencode)
}
#ifndef BUILD_NO_SPECIAL_CHARACTERS_STRINGCONVERTER
void parseSpecialCharacters(String& s, boolean useURLencode)
void parseSpecialCharacters(String& s, bool useURLencode)
{
bool no_accolades = s.indexOf('{') == -1 || s.indexOf('}') == -1;
bool no_html_entity = s.indexOf('&') == -1 || s.indexOf(';') == -1;
@@ -740,7 +745,7 @@ void parseSpecialCharacters(String& s, boolean useURLencode)
/********************************************************************************************\
replace other system variables like %sysname%, %systime%, %ip%
\*********************************************************************************************/
void parseControllerVariables(String& s, struct EventStruct *event, boolean useURLencode) {
void parseControllerVariables(String& s, struct EventStruct *event, bool useURLencode) {
s = parseTemplate(s, useURLencode);
parseEventVariables(s, event, useURLencode);
}
@@ -748,7 +753,7 @@ void parseControllerVariables(String& s, struct EventStruct *event, boolean useU
void parseSingleControllerVariable(String & s,
struct EventStruct *event,
byte taskValueIndex,
boolean useURLencode) {
bool useURLencode) {
if (validTaskIndex(event->TaskIndex)) {
LoadTaskSettings(event->TaskIndex);
repl(F("%valname%"), ExtraTaskSettings.TaskDeviceValueNames[taskValueIndex], s, useURLencode);
@@ -761,7 +766,7 @@ void parseSingleControllerVariable(String & s,
// Simple macro to create the replacement string only when needed.
#define SMART_REPL(T, S) \
if (s.indexOf(T) != -1) { repl((T), (S), s, useURLencode); }
void parseSystemVariables(String& s, boolean useURLencode)
void parseSystemVariables(String& s, bool useURLencode)
{
#ifndef BUILD_NO_SPECIAL_CHARACTERS_STRINGCONVERTER
parseSpecialCharacters(s, useURLencode);
@@ -770,7 +775,7 @@ void parseSystemVariables(String& s, boolean useURLencode)
SystemVariables::parseSystemVariables(s, useURLencode);
}
void parseEventVariables(String& s, struct EventStruct *event, boolean useURLencode)
void parseEventVariables(String& s, struct EventStruct *event, bool useURLencode)
{
repl(F("%id%"), String(event->idx), s, useURLencode);
@@ -867,7 +872,7 @@ bool getConvertArgumentString(const String& marker, const String& s, String& arg
// Parse conversions marked with "%conv_marker%(float)"
// Must be called last, since all sensor values must be converted, processed, etc.
void parseStandardConversions(String& s, boolean useURLencode) {
void parseStandardConversions(String& s, bool useURLencode) {
if (s.indexOf(F("%c_")) == -1) {
return; // Nothing to replace
}
+11 -9
View File
@@ -32,10 +32,12 @@ bool string2float(const String& string,
/********************************************************************************************\
Convert a char string to IP byte array
\*********************************************************************************************/
boolean str2ip(const String& string,
bool isIP(const String& string);
bool str2ip(const String& string,
byte *IP);
boolean str2ip(const char *string,
bool str2ip(const char *string,
byte *IP);
String formatIP(const IPAddress& ip);
@@ -198,11 +200,11 @@ String URLEncode(const char *msg);
void repl(const String& key,
const String& val,
String & s,
boolean useURLencode);
bool useURLencode);
#ifndef BUILD_NO_SPECIAL_CHARACTERS_STRINGCONVERTER
void parseSpecialCharacters(String& s,
boolean useURLencode);
bool useURLencode);
#endif // ifndef BUILD_NO_SPECIAL_CHARACTERS_STRINGCONVERTER
/********************************************************************************************\
@@ -210,19 +212,19 @@ void parseSpecialCharacters(String& s,
\*********************************************************************************************/
void parseControllerVariables(String & s,
struct EventStruct *event,
boolean useURLencode);
bool useURLencode);
void parseSingleControllerVariable(String & s,
struct EventStruct *event,
byte taskValueIndex,
boolean useURLencode);
bool useURLencode);
void parseSystemVariables(String& s,
boolean useURLencode);
bool useURLencode);
void parseEventVariables(String & s,
struct EventStruct *event,
boolean useURLencode);
bool useURLencode);
bool getConvertArgument(const String& marker,
const String& s,
@@ -246,7 +248,7 @@ bool getConvertArgumentString(const String& marker,
// Parse conversions marked with "%conv_marker%(float)"
// Must be called last, since all sensor values must be converted, processed, etc.
void parseStandardConversions(String& s,
boolean useURLencode);
bool useURLencode);
bool HasArgv(const char *string,
+4
View File
@@ -17,6 +17,10 @@ void handleNotFound() {
checkRAM(F("handleNotFound"));
#endif
if (captivePortal()) { // If captive portal redirect instead of displaying the error page.
return;
}
// if Wifi setup, launch setup wizard if AP_DONT_FORCE_SETUP is not set.
if (WiFiEventData.wifiSetup && !Settings.ApDontForceSetup())
{
+6
View File
@@ -31,6 +31,8 @@
#endif
// ********************************************************************************
// Web Interface root page
// ********************************************************************************
@@ -39,6 +41,10 @@ void handle_root() {
checkRAM(F("handle_root"));
#endif
if (captivePortal()) { // If captive portal redirect instead of displaying the page.
return;
}
// if Wifi setup, launch setup wizard if AP_DONT_FORCE_SETUP is not set.
if (WiFiEventData.wifiSetup && !Settings.ApDontForceSetup())
{
+4
View File
@@ -6,11 +6,15 @@
#ifdef WEBSERVER_ROOT
// ********************************************************************************
// Web Interface root page
// ********************************************************************************
void handle_root();
#endif // ifdef WEBSERVER_ROOT
+23 -1
View File
@@ -267,6 +267,24 @@ size_t streamFile_htmlEscape(const String& fileName)
return size;
}
bool captivePortal() {
if (!isIP(web_server.hostHeader()) && web_server.hostHeader() != (NetworkGetHostname() + F(".local"))) {
const bool fromAP = web_server.client().localIP() == apIP;
String redirectURL = F("http://");
redirectURL += web_server.client().localIP().toString();
if (fromAP && !SecuritySettings.hasWiFiCredentials()) {
redirectURL += F("/setup");
}
web_server.sendHeader(F("Location"), redirectURL, true);
web_server.send(302, F("text/plain"), ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
web_server.client().stop(); // Stop is needed because we sent no content length
return true;
}
return false;
}
// ********************************************************************************
// Web Interface init
// ********************************************************************************
@@ -280,7 +298,11 @@ void WebServerInit()
// Prepare webserver pages
#ifdef WEBSERVER_ROOT
web_server.on(F("/"), handle_root);
web_server.on(F("/"), handle_root);
// Entries for several captive portal URLs.
// Maybe not needed. Might be handled by notFound handler.
web_server.on(F("/generate_204"), handle_root); //Android captive portal.
web_server.on(F("/fwlink"), handle_root); //Microsoft captive portal.
#endif // ifdef WEBSERVER_ROOT
#ifdef WEBSERVER_ADVANCED
web_server.on(F("/advanced"), handle_advanced);
+6
View File
@@ -44,6 +44,12 @@ size_t streamFile_htmlEscape(const String& fileName);
void WebServerInit();
// ********************************************************************************
// Redirect to captive portal if we got a request for another domain.
// Return true in that case so the page handler does not try to handle the request again.
// ********************************************************************************
bool captivePortal();
void setWebserverRunning(bool state);
void getWebPageTemplateDefault(const String& tmplName,