1

I'm trying to make a onetime URL with a random string to for example give a user onetime access to a house with a smart lock. I have a function to make a fixed 8-character string and the following code with the address that is randomly made.

  controlserver.on(userurl, HTTP_GET, [](AsyncWebServerRequest *request){
   if(urlactive==true){
   if(request->authenticate("admin","admin")){  
      running=true; 
      request->send(200); 
      
      Serial.println("entered"); 
      urlactive=false;
      }else {
       return request->requestAuthentication();
      }
   }else{
       generateRandomString();
       request->send(200, "text/html", HTML_CSS_STYLING+ "<body>"
    "<br><h1 style=\"color:white; direction: rtl;\">LInk is not valid!</h1></body>" );
       
   }
  });

the following function generates the userurl which is a const char* because the requirement of ESPAsyncWebServer library.

void generateRandomString() {
  const char specialChars[] = "0123456789";
  const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  String randomStringurl = "";
  String keppmyuserpass = "";
  randomStringurl += letters[random(26, 51)];
  for (int i = 0; i < 6; i++) {
    randomStringurl += letters[random(0, 51)];
  }
  randomStringurl += specialChars[random(0, 9)];
  Serial.println(randomStringurl);
  userurl = randomStringurl.c_str();

}

everything works fine when I enter the generated string that I convert into const char* using c_str() but I noticed an error when I copied the string partially by accident and too much to my surprise, I saw that the popup for the username and password showed up and I didn't expecte it, because I have a notFound function that will show an error when the entered URL doesn't exist. entered different strings but the problem is persistent. I think there is a problem with my string conversion to const char* using c_str(). but is it?

Here is compliable code, the wifi mode is STA WiFi.mode(WIFI_STA); :

#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>

const char* userurl;
String  randomStringurl = "";
String  keppmyuserpass = "";
  
AsyncWebServer server(80);

const char* ssid = "your_ssid";
const char* password = "your_password";

const char* PARAM_MESSAGE = "message";

void notFound(AsyncWebServerRequest *request) {
    request->send(404, "text/plain", "Not found");
}
void generateRandomString() {
  const char specialChars[] = "0123456789";
  const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  randomStringurl = "";
  keppmyuserpass = "";
  randomStringurl += letters[random(26, 51)];
  for (int i = 0; i < 6; i++) {
    randomStringurl += letters[random(0, 51)];
  }
  randomStringurl += specialChars[random(0, 9)];
  Serial.println(randomStringurl);
  userurl = randomStringurl.c_str();
  keppmyuserpass = randomStringurl;
  keppmyuserpass += letters[random(26, 51)];
  keppmyuserpass += specialChars[random(0, 9)];
  Serial.println(keppmyuserpass);

}
void setup() {

    Serial.begin(115200);
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
        Serial.printf("WiFi Failed!\n");
        return;
    }

    Serial.print("IP Address: ");
    Serial.println(WiFi.localIP());

    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send(200, "text/plain", "Hello, world");
    });

    // Send a GET request to <IP>/get?message=<message>
    server.on(userurl, HTTP_GET, [] (AsyncWebServerRequest *request) {
        String message;
        if (request->hasParam(PARAM_MESSAGE)) {
            message = request->getParam(PARAM_MESSAGE)->value();
        } else {
            message = "No message sent";
        }
        request->send(200, "text/plain", "Hello, GET: " + message);
    });

    // Send a POST request to <IP>/post with a form field message set to <message>
    server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request){
        String message;
        if (request->hasParam(PARAM_MESSAGE, true)) {
            message = request->getParam(PARAM_MESSAGE, true)->value();
        } else {
            message = "No message sent";
        }
        request->send(200, "text/plain", "Hello, POST: " + message);
    });

    server.onNotFound(notFound);

    server.begin();
    generateRandomString();
}

void loop() {
}

And here is the code from ESPAsyncWebServer.h library, that required the url to be a const char* uri what am I doing wrong?

AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody);

New Update: So, I forgot to add a "/" before the URL, by doing so it solved the problem and here is the new function:

void generateRandomString() {
  const char specialChars[] = "0123456789";
  const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  randomStringurl = "";
  keppmyuserpass = "";
  randomStringurl +="/";
  randomStringurl += letters[random(26, 51)];
  for (int i = 0; i < 6; i++) {
    randomStringurl += letters[random(0, 51)];
  }
  randomStringurl += specialChars[random(0, 9)];
  Serial.println(randomStringurl);
  userurl = randomStringurl.c_str();
  keppmyuserpass = randomStringurl;
  keppmyuserpass += letters[random(26, 51)];
  keppmyuserpass += specialChars[random(0, 9)];
  OTUPC = keppmyuserpass.c_str();
  Serial.println(keppmyuserpass);

}

But when I renew the link, the new link is not recognized and the first generated link was working. I think that @6v6gt might be right about this: "Could it also be that the value of userurl must be known when setup() runs and the callback is registered?"

6
  • 1
    useurl will point to a char array, that is no longer there, as the local String randomStringurl object is created on the stack and destroyed when the function exits. The dynamically created array inside of it will also be deleted. So this will only work as long as that memory portion won't be overwritten (which you cannot know).
    – chrisl
    Nov 27, 2023 at 18:02
  • 2
    Though your problem doesn't really fit that. Please show us a minimal compilable example code that shows the problem, not just some snippets.
    – chrisl
    Nov 27, 2023 at 18:03
  • @chrisl I made the randomStringurl global String but the problem is still persistanat. I added a compliable code. Isn't the null terminated character at the end of char the problem maker? because normally I define the URL's like "/someurl" not just by passing a const chr* someurl.
    – T R Y
    Nov 28, 2023 at 11:04
  • 1
    const char* userurl; . . . ; userurl = randomStringurl.c_str(); This is not preserving a copy of the String variable's contents. It is simply keeping a copy of a pointer to an area which may vanish (as has been pointed out). You have to create a global (or static) buffer something like char userurl[80] and then use strcpy() to populate it. Could it also be that the value of userurl must be known when setup() runs and the callback is registered?.
    – 6v6gt
    Nov 28, 2023 at 11:31
  • @6v6gt how can I know that the value of userurl must be known?
    – T R Y
    Nov 28, 2023 at 12:37

1 Answer 1

1

6v6gt is right, that the URI for the handler must be known, when you use the on() function to set the handler. Inside the library, the on() function calls setUri(const String& uri), which which saves the uri in the classes interal String member variable. Thus the handle will not notice, if the originally used variable changes.

I think the most elegant solution here would to be to use a regular expression for your handler URI (A regular expression is a string expression, that describes a template that can match other strings). That will let the handler match any URI, that fits that regex. Inside the handler you can then get the relevant part of the URI and check, if it is currently valid. If yes, you serve your content, otherwise you serve an error (for example a 404 Not Found).

The third answer to this question has a code snippet for this:

#include <uri/UriRegex.h>

web_server.on(UriRegex("/home/([0-9]+)/first"), HTTP_GET, [&]() {
    web_server.send(200, "text/plain", "Hello from first! URL arg: " + web_server.pathArg(0));
});

The part of the URI, that's between the brackets, is the regex group, that is then made available via web_server.pathArg(0). Your current code creates a string with one capital letter, 6 lower case letters and one digit, thus a regex group ([A-Z][a-z]{6}[0-9]). You can read about how regex work online.

When you look at this repo of ESPAsyncWebServer (I'm not sure, which version you have), in the Readme right at the end under "path variable" you find this snippet:

server.on("^\\/sensor\\/([0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) {
  String sensorId = request->pathArg(0);
});

And a note, that regex support must be enabled.

As I didn't test this, I suggest you try both methods and see, what works for you.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.