Login + file listing (personal project)

I have been trying for weeks to do the following: When entering the IP address of the arduino board, release a login form and if the entered data is correct, then list files inside the micro SD card. It is worth noting that the listed files can be opened in the browser (all are text type).

The login page, first try to create it in an html file saved on the SD card, but it did not work to the end. I had to use basic authentication via HTTP:

#include <SPI.h>
#include <Ethernet.h>

byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(192, 168, 1, 117);

EthernetServer server(80);

char header[300];
int bufferSize = 0;

void setup() {
  Serial.begin(9600);
  Ethernet.begin(mac, ip);
  server.begin();
  Serial.print("server is at ");
  Serial.println(Ethernet.localIP());
}

void loop() {
  EthernetClient client = server.available();
  if (client) {
    Serial.println("new client");
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        if(bufferSize < 300) header[bufferSize++] = c;
       
  
        if (c == '\n' && currentLineIsBlank) {

          //'arduino:admin' (user:password)
         
          Serial.println(header);
          
          if(strstr(header, "YXJkdWlubzphZG1pbg==") != NULL) {
            client.println("HTTP/1.1 200 OK");
            client.println("Content-Type: text/html");
            client.println("Connection: close"); 

            client.println();
            if(strstr(header, "GET / HTTP/1.1")) {
               Serial.println("Lo has ingresado great");
               client.println("<html>");
               client.println("<h2>Felicidades Qlo</h2>");
               client.println("</html>");
            }
            
          } else {
            client.println("HTTP/1.1 401 Unauthorized");
            client.println("WWW-Authenticate: Basic realm=\"Secure\"");
            client.println("Content-Type: text/html");
            client.println();
            client.println("<html>REJECTED</html>");                     
          }
          
          bufferSize = 0;
          StrClear(header, 300);
                
          break;
          
        }
        if (c == '\n') {
          currentLineIsBlank = true;
        }
        else if (c != '\r') {
          currentLineIsBlank = false;
        }
      }
    }
    delay(1);
    client.stop();
    Serial.println("client disconnected");
  }
}

void StrClear(char *str, char length)
{
    for (int i = 0; i < length; i++) {
        str[i] = 0;
    }
}

To list and view files within the SD, I follow the tutorial on this page:Arduino Tutorials - Ethernet+SD

#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>

byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(192, 168, 1, 117);
EthernetServer server(80);
Sd2Card card;
SdVolume volume;
SdFile root;
SdFile file;

#define error(s) error_P(PSTR(s))

void error_P(const char* str) {
  PgmPrint("error: ");
  SerialPrintln_P(str);
  if (card.errorCode()) {
    PgmPrint("SD error: ");
    Serial.print(card.errorCode(), HEX);
    Serial.print(',');
    Serial.println(card.errorData(), HEX);
  }
  while(1);
}

struct Millis{
    unsigned long offset = 0;
    unsigned long get(){return millis() - offset;}
    void reset(){offset = millis();}
    void set(unsigned long value){offset - millis() - value;}
};


void setup() {
  Serial.begin(9600);
  pinMode( 8, OUTPUT );                   
  digitalWrite( 9, HIGH );
  if( !card.init( SPI_HALF_SPEED, 4 ) ) error( "card.init failed!" );
  if( !volume.init( &card ) ) error( "vol.init failed!" );
  if( !root.openRoot( &volume ) ) error( "openRoot failed" );
  Ethernet.begin(mac, ip);
  server.begin();
  Serial.print("server is at ");
  Serial.println(Ethernet.localIP());
}


#define BUFSIZ 100
Millis lol;

void loop()
{
  char clientline[BUFSIZ];
  int index = 0;
  LEDCunt();
  EthernetClient client = server.available();
  if (client) {
    boolean current_line_is_blank = true;
 
    index = 0;
 
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
 
        
        if (c != '\n' && c != '\r') {
          clientline[index] = c;
          index++;
          //Se comprueba si el tamaño del Buffer es demasiado grande
          if (index >= BUFSIZ) 
            index = BUFSIZ -1;
 
          //Si no es grande, se continua normalmente el codigo
          continue;
        }
 
        // Si esta linea se cumple, significa que ya salio el bucle de la condicion anterior.
        // Osea.... ya se encontro el espacio en blanco final (\n,\t)
        clientline[index] = 0;
        
        // Insertamos el array para proseguir con la solicitacion de directorios de la tarjeta SD (ROOT)
        if (strstr (clientline, "GET / ") != 0) {
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println();
          client.println("<h2>Archivos:</h2>");
          //Listamos archivos
          ListFiles(client, 0);
        } else if (strstr (clientline, "GET /") != 0) {
       
          char *filename;
 
          filename = clientline + 5; 

          //Limpiamos el array igualando elementos a 0
          (strstr (clientline, " HTTP"))[0] = 0;
 
          // Cuando demos click al archivo que vamos abrir, imprime el nombre por consola.
          Serial.println(filename);
 
          if (! file.open(&root, filename, O_READ)) {
            client.println("HTTP/1.1 404 Not Found");
            client.println("Content-Type: text/html");
            client.println();
            client.println("<h2>File Not Found!</h2>");
            break;
          }
 
          Serial.println("Opened!");
 
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/plain");
          client.println();
 
          int16_t c;
          while ((c = file.read()) > 0) {
              client.print((char)c);
          }
          file.close();
        } else {
          //Si no encuentra el archivo o no puede abrirlo
          client.println("HTTP/1.1 404 Not Found");
          client.println("Content-Type: text/html");
          client.println();
          client.println("<h2>File Not Found!</h2>");
        }
        break;
      }
    }
    delay(1);
    client.stop();
  }
}

void StrClear(char *str, char length)
{
    for (int i = 0; i < length; i++) {
        str[i] = 0;
    }
}

void ListFiles(EthernetClient client, uint8_t flags) {
  dir_t p;

  root.rewind();
  client.println("<ul>");
  while (root.readDir(p) > 0) {
    
    if (p.name[0] == DIR_NAME_FREE) break;

    // omite entradas (archivos) eliminadas
    if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') continue;

    // enlista subdirectorios y archivos(solamente)
    if (!DIR_IS_FILE_OR_SUBDIR(&p)) continue;

    // imprime espacios en blanco para extension del archivo
    client.print("<li><a href=\"");
    for (uint8_t i = 0; i < 11; i++) {
      if (p.name[i] == ' ') continue;
      if (i == 8) {
        client.print('.');
      }
      client.print((char)p.name[i]);
    }
    client.print("\">");

    // imprime el nombre del archivo
    for (uint8_t i = 0; i < 11; i++) {
      if (p.name[i] == ' ') continue;
      if (i == 8) {
        client.print('.');
      }
      client.print((char)p.name[i]);
    }

    client.print("</a>");

    if (DIR_IS_SUBDIR(&p)) {
      client.print('/');
    }

    // imprime la fecha de ultima modificacion si se solicita
    if (flags & LS_DATE) {
       root.printFatDate(p.lastWriteDate);
       client.print(' ');
       root.printFatTime(p.lastWriteTime);
    }
    // imprime el tamaño del archivo si se solicita
    if (!DIR_IS_SUBDIR(&p) && (flags & LS_SIZE)) {
      client.print(' ');
      client.print(p.fileSize);
    }
    client.println("</li>");
  }
  client.println("</ul>");
}

void LEDCunt(){
  if(lol.get() < 700){
    digitalWrite(9,HIGH);
  }
  if(lol.get() > 700){
    digitalWrite(9,LOW);
  }
  if(lol.get() == 1400){
    lol.reset();
  }
}

I added those comments to understand the code. Now what I am struggling to achieve, is to be able to combine the Login (first code) and the list of files (second code). I do not know why sometimes I think that is impossible, because in my tests, my memory is very limited (91%). I would like to know alternatives if they exist? Or is it possible to combine these two codes?

No one tried this before? ... :o

Do both of your code examples work? What exactly is the problem you are experiencing?

The simple answer is that it should be possible to achieve what you require. You will need to verify that every GET request contains the correct authentication header before serving up the list of files.

If you want to have a login page, then you will need to have a simple form of session management so that you can keep track of subsequent requests and whether they came from someone who has authenticated using the login page.

Checkout how to set a cookie in your HTTP response for the login page. Send the client a random number to store in the cookie. Keep track of the random numbers you have issued on the Arduino.

When you get a HTTP request, check the headers for your authentication cookie. If it is there, check the random number in the cookie against the list of random numbers you have issued and if it is there, let the request proceed to list the files. If it is not there, respond with a HTTP 302 and redirect the user's browser to your login page, or a HTTP 401 unauthorised.

To make it all more secure, keep track of the time you issued each security token as well, so that you can expire them after a period of time so they don't last forever.

As you can see, the login page makes things quite a bit more complex.

I thank you very much for your answer !. Yes, both codes work separately, but at the time of joining them, the login screen does not even come out ... I'll read what you've posted

Hi, I've tried this that I'm going to show you (and what the console prints). This is my linked code (LOGIN + SD):

#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>

byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(192, 168, 1, 117);
EthernetServer server(80);
Sd2Card card;
SdVolume volume;
SdFile root;
SdFile file;

#define error(s) error_P(PSTR(s))

void error_P(const char* str) {
  PgmPrint("error: ");
  SerialPrintln_P(str);
  if (card.errorCode()) {
    PgmPrint("SD error: ");
    Serial.print(card.errorCode(), HEX);
    Serial.print(',');
    Serial.println(card.errorData(), HEX);
  }
  while(1);
}

struct Millis{
    unsigned long offset = 0;
    unsigned long get(){return millis() - offset;}
    void reset(){offset = millis();}
    void set(unsigned long value){offset - millis() - value;}
};


void setup() {
  Serial.begin(9600);
  pinMode( 8, OUTPUT );                   
  digitalWrite( 9, HIGH );
  if( !card.init( SPI_HALF_SPEED, 4 ) ) error( "card.init failed!" );
  if( !volume.init( &card ) ) error( "vol.init failed!" );
  if( !root.openRoot( &volume ) ) error( "openRoot failed" );
  Ethernet.begin(mac, ip);
  server.begin();
  Serial.print("server is at ");
  Serial.println(Ethernet.localIP());
}


#define BUFSIZ 100
Millis lol;

void loop()
{
  char clientline[BUFSIZ];
  int index = 0;
  LEDCunt();
  EthernetClient client = server.available();
  if (client) {
    boolean current_line_is_blank = true;
    boolean activo = false;
    index = 0;
 
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        while(!activo){
           activo = login();
        }
        
        if (c != '\n' && c != '\r') {
          clientline[index] = c;
          index++;
          //Se comprueba si el tamaño del Buffer es demasiado grande
          if (index >= BUFSIZ) 
            index = BUFSIZ -1;
 
          //Si no es grande, se continua normalmente el codigo
          continue;
        }
 
        // Si esta linea se cumple, significa que ya salio el bucle de la condicion anterior.
        // Osea.... ya se encontro el espacio en blanco final (\n,\t)
        clientline[index] = 0;
        
        // Insertamos el array para proseguir con la solicitacion de directorios de la tarjeta SD (ROOT)
        if (strstr (clientline, "GET / ") != 0) {
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println();
          client.println("<h2>Archivos:</h2>");
          //Listamos archivos
          ListFiles(client, 0);
        } else if (strstr (clientline, "GET /") != 0) {
       
          char *filename;
 
          filename = clientline + 5; 

          //Limpiamos el array igualando elementos a 0
          (strstr (clientline, " HTTP"))[0] = 0;
 
          // Cuando demos click al archivo que vamos abrir, imprime el nombre por consola.
          Serial.println(filename);
 
          if (! file.open(&root, filename, O_READ)) {
            client.println("HTTP/1.1 404 Not Found");
            client.println("Content-Type: text/html");
            client.println();
            client.println("<h2>File Not Found!</h2>");
            break;
          }
 
          Serial.println("Opened!");
 
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/plain");
          client.println();
 
          int16_t c;
          while ((c = file.read()) > 0) {
              client.print((char)c);
          }
          file.close();
        } else {
          //Si no encuentra el archivo o no puede abrirlo
          client.println("HTTP/1.1 404 Not Found");
          client.println("Content-Type: text/html");
          client.println();
          client.println("<h2>File Not Found!</h2>");
        }
        break;
      }
    }
    delay(1);
    client.stop();
  }
}

boolean login(){
  int bufferSize;
  char header[300];
  EthernetClient client = server.available();
  if (client) {
    Serial.println("new client");
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        if(bufferSize < 75) header[bufferSize++] = c;
       
  
        if (c == '\n' && currentLineIsBlank) {

          //'arduino:admin' (user:password) encriptado base64
         
          Serial.println(header);
          
          if(strstr(header, "YXJkdWlubzphZG1pbg==") != NULL) {
            client.println("HTTP/1.1 200 OK");
            client.println("Content-Type: text/html");
            client.println("Connection: close"); 

            client.println();
            if(strstr(header, "GET / HTTP/1.1")) {
              return true;
            } else {
              return false;
            }
            
          } else {
            client.println("HTTP/1.1 401 Unauthorized");
            client.println("WWW-Authenticate: Basic realm=YXJkdWlubzphZG1pbg==");
            client.println("Content-Type: text/html");
            client.println();
            client.println("<html>¿Quien eres?</html>"); 
            return false;                    
          }
          
          bufferSize = 0;
          StrClear(header, 300);
            
          break;
          
        }
        if (c == '\n') {
          currentLineIsBlank = true;
        }
        else if (c != '\r') {
          currentLineIsBlank = false;
        }
      }
    }
    delay(1);
    client.stop();
    Serial.println("Login terminado");
  }
}

void StrClear(char *str, char length)
{
    for (int i = 0; i < length; i++) {
        str[i] = 0;
    }
}

void ListFiles(EthernetClient client, uint8_t flags) {
  dir_t p;

  root.rewind();
  client.println("<ul>");
  while (root.readDir(p) > 0) {
    
    if (p.name[0] == DIR_NAME_FREE) break;

    // omite entradas (archivos) eliminadas
    if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') continue;

    // enlista subdirectorios y archivos(solamente)
    if (!DIR_IS_FILE_OR_SUBDIR(&p)) continue;

    // imprime espacios en blanco para extension del archivo
    client.print("<li><a href=\"");
    for (uint8_t i = 0; i < 11; i++) {
      if (p.name[i] == ' ') continue;
      if (i == 8) {
        client.print('.');
      }
      client.print((char)p.name[i]);
    }
    client.print("\">");

    // imprime el nombre del archivo
    for (uint8_t i = 0; i < 11; i++) {
      if (p.name[i] == ' ') continue;
      if (i == 8) {
        client.print('.');
      }
      client.print((char)p.name[i]);
    }

    client.print("</a>");

    if (DIR_IS_SUBDIR(&p)) {
      client.print('/');
    }

    // imprime la fecha de ultima modificacion si se solicita
    if (flags & LS_DATE) {
       root.printFatDate(p.lastWriteDate);
       client.print(' ');
       root.printFatTime(p.lastWriteTime);
    }
    // imprime el tamaño del archivo si se solicita
    if (!DIR_IS_SUBDIR(&p) && (flags & LS_SIZE)) {
      client.print(' ');
      client.print(p.fileSize);
    }
    client.println("</li>");
  }
  client.println("</ul>");
}

void LEDCunt(){
  if(lol.get() < 700){
    digitalWrite(9,HIGH);
  }
  if(lol.get() > 700){
    digitalWrite(9,LOW);
  }
  if(lol.get() == 1400){
    lol.reset();
  }
}

The login opens this time, but ... it does not work. I Attach an image in this reply to see the doodles that it throws where it says "Authorization:". It is assumed that what should print there, is what is locked in red (user encryption and password). Why would this happen?

I think this is happening because you haven't terminated your buffer with a null character. When you do the:

Serial.println(header);

C doesn't know where the end of your header array is, so it keeps going past the end of the array and into other memory which contains who knows what. When you have determined that the buffer contains what you want, add a null as the last character. To make it easier to remember, you can add this to the top of your code:

#define END_OF_STRING ('\0')

I think you need a few other changes to make this work reliably. You seem to stop putting characters into the buffer after it reaches 75 characters:

if(bufferSize < 75) header[bufferSize++] = c;

which seems a little close to how big the header will be (I haven't counted the characters).

I would also put some timeout logic in there so you don't loop around forever with a badly behaved client. So loop while the client is connected and you've been looping for less than the timeout.