Файловый WEB сервер

rene
Offline
Зарегистрирован: 21.01.2014

Доброго времени суток! Пытаюсь реализовать файловый веб сервер на базе Arduino mega и Ethernet shild v3. Все основные функции написаны, контроллер считывает содержимое SD карты, формирует веб страницу со ссылками на файлы при клике на которые открывается диалоговое окно сохранения файла, но...!

Не могу передать содержимое самого файла. Файлы скачиваются нулевого размера. Вот кусок кода:

client.print("HTTP/1.1 200 OK");
client.print("Content-Description: File Transfer");
client.print("Content-Type: application/octet-stream");
client.print("Content-Disposition: attachment; filename=");
client.print(getFileName);
client.print("Content-Transfer-Encoding: binary");
client.print("Expires: 0");
client.print("Cache-Control: must-revalidate");
client.print("Pragma: public");
client.print("Content-Length: ");
client.print(myFile.size());

while (myFile.available()) {
   client.write(myFile.read());
}

Сами файлы открываются нормально, могу вывести содержимое в консоли, но передаваться по hhtp не хочет. Установил сниффер, запустил закачку файла с какого то сайта. Судя по пакетам, сами данные передаются в TCP пакетах, но как это реализовать, не понимаю.

sva1509
Offline
Зарегистрирован: 07.12.2012

Сама WEB страница открывается ? Не открываются только картинки ?

И з вашего кода видно только отправка заголовка и перекачка файла, к стати без его открытия перед 11 строкой имеет смымл открыть myFile

rene
Offline
Зарегистрирован: 21.01.2014

Да, страница открывается, так же как и файл. Привожу код скачивания файла:

            myFile = SD.open("TEST.TXT");
            if (myFile) {

              // Заголовок для открытия окна сохранения файла
              client.println("HTTP/1.1 200 OK");
              client.println("Content-Description: File Transfer");
              client.println("Content-Type: application/octet-stream");
              client.println("Content-Disposition: attachment; filename=TEST.TXT");
              client.println("Content-Transfer-Encoding: binary");
              client.println("Expires: 0");
              client.println("Cache-Control: must-revalidate");
              client.println("Pragma: public");
              client.println("Content-Length: ");
              client.println(myFile.size());
              client.println();

              // Передаем пока есть что передавать
              while (myFile.available()) {
                client.write(myFile.read());
              }           
              myFile.close();
            }
            else {

              // Ошибка открытия файла
              Serial.println("Error opening);
            }

Была ошибка. Fiddler мне подсказал что заголовок должен заканчиваться на /r/n/r/n, т.е. в конце заголовка надо было добавить пустую client.println();, но это не исправило положение, файл не скачивается

rene
Offline
Зарегистрирован: 21.01.2014

Из сниффера видно, что файл от сервера передается, но как то странно, по одному байту в пакете.

rene
Offline
Зарегистрирован: 21.01.2014

Похоже дело именно в этом, передавать данные необходимо скопом, иначе каждый считанный из файла байт формирует свой пакет передачи. Попробовал передать client.write("aaaaaaaaaaaaaaaaaaaaaaaa"); Файл скачался нормально, в сниффере все эти аааа... в одном пакете.

Теперь вопрос, как передать содержимое файла за один присест, а не побайтно?

Radjah
Offline
Зарегистрирован: 06.08.2014

Читаешь кусок файла в буфер и кидаешь кусками. Только под буфер память нужна.

rene
Offline
Зарегистрирован: 21.01.2014

Ну ладно, первый кусок я так скину, а второй кусок пойдет отдельным пакетом, не привязанным к первому?

Наверняка кто то заморачивался по поводу пересылки файла по сети стандартными средствами, например SMB или FTP?

pastry777
Offline
Зарегистрирован: 16.01.2014

я вот так скачиваю,правда без запроса,сразу качает...побайтно...а вас почему это напрягает?из за времени передачи?у меня файлы текстовые все,мне особо не мешает,по байтам ну и пусть,10сек и файл у меня..

File power = SD.open("/power.txt");
          if (power){
           client.println("HTTP/1.1 200 OK");
           client.println("Content-Disposition: attachment; filename=power.txt;");
           client.println();
             while (power.available()) {
             client.write(power.read());}
           power.close();
           delay(100);
           client.stop();                        
          }

 

rene
Offline
Зарегистрирован: 21.01.2014

Да мне не принципиально, хоть побитно, лишь бы качал. Спасибо заинформацию, буду пробовать

ataman
Offline
Зарегистрирован: 16.01.2015

А файловый сервер будет доступен с глобальной сети ? Или только локально ?

pastry777
Offline
Зарегистрирован: 16.01.2014

ну,по сути наши скетчи практически не отличаются,только я в конце закрываю соединение с клиентом,видимо у вас на этом заминка..delay перед этим положительно влияет,меньше сбоев,браузеру нужно время полностью обработать инфу,не убирайте совсем,можно уменьшить немного опытным путем...

rene
Offline
Зарегистрирован: 21.01.2014

Только локально, в глобальной сети думаю тормоза будут дикие с однобайтной пересылкой, там даже страница побайтно формируется, проверял

Radjah
Offline
Зарегистрирован: 06.08.2014

Читать до полного просветления http://arduino.cc/en/Reference/ClientWrite

pastry777
Offline
Зарегистрирован: 16.01.2014

не скажи=)у меня как раз все через интернет...так мало того,у ардуины интернет 3g с юсб модема с хреноватым сигналом ,ардуина собственно сама еще и смотрит есть связь или нет и перегружает модем если че,и все ок=)так что не боись..

rene
Offline
Зарегистрирован: 21.01.2014

pastry777 пишет:

ну,по сути наши скетчи практически не отличаются,только я в конце закрываю соединение с клиентом,видимо у вас на этом заминка..delay перед этим положительно влияет,меньше сбоев,браузеру нужно время полностью обработать инфу,не убирайте совсем,можно уменьшить немного опытным путем...

Ааа!!! Заработало!

Дело не в закрытии соединения, оно у меня закрывается далее по коду, дело именно в заголовке, похоже  один из параметров указанных мной, как раз запрещает отправку данных побайтно, буду смотреть.

Спасибо огромное!

rene
Offline
Зарегистрирован: 21.01.2014

Radjah пишет:

Читать до полного просветления http://arduino.cc/en/Reference/ClientWrite

Если это тонкий намек на то, что write умеет отсылать данные серией байтов, то я это знал, но это не решает проблемы. Как отправить вторую, третью и т.д. порцию данных, чтобы это было продолжением первого пакета (это пакеты помеченные как "continuation")? Для этого необходимо залезть в заголовок TCP. Ну или как вариант загрузить весь файл в память и отправить его целиком, наверное так получится если памяти хватит.

И еще один момент, если при отправке данных с помощью write или print используются переменные, они автоматически фрагментируются по пакетам, например:

client.print("Open file: test.txt"); - упакуется в один пакет

 

String fileName = "test.txt";

client.print("Opent file: " + fileName); - разобьется на множество tcp пакетов по одному байту полезной информации в каждом

Ну покрайне мере мои эксперементы со сниффером показали имено это

rene
Offline
Зарегистрирован: 21.01.2014

А Fiddler`у не понравился такой заголовок, говорит No Connection: close, no Content-Length. No way to tell if the response is complete.

Странно, буду смотреть

TevatroN
Offline
Зарегистрирован: 17.11.2013

Может пригодится, проверено работает, открывает, качает. Одно но, стабильно работает на меге 2560.

Лишнее можно обрезать, выкинуть.

/*
 * USERS OF ARDUINO 0023 AND EARLIER: use the 'SDWebBrowse.pde' sketch...
 * 'SDWebBrowse.ino' can be ignored.
 * USERS OF ARDUINO 1.0 AND LATER: **DELETE** the 'SDWebBrowse.pde' sketch
 * and use ONLY the 'SDWebBrowse.ino' file.  By default, BOTH files will
 * load when using the Sketchbook menu, and the .pde version will cause
 * compiler errors in 1.0.  Delete the .pde, then load the sketch.
 *
 * I can't explain WHY this is necessary, but something among the various
 * libraries here appears to be wreaking inexplicable havoc with the
 * 'ARDUINO' definition, making the usual version test unusable (BOTH
 * cases evaluate as true).  FML.
 */

/*
 * This sketch uses the microSD card slot on the Arduino Ethernet shield
 * to serve up files over a very minimal browsing interface
 *
 * Some code is from Bill Greiman's SdFatLib examples, some is from the
 * Arduino Ethernet WebServer example and the rest is from Limor Fried
 * (Adafruit) so its probably under GPL
 *
 * Tutorial is at http://www.ladyada.net/learn/arduino/ethfiles.html
 * Pull requests should go to http://github.com/adafruit/SDWebBrowse
 */

/*
// Para que funcione y busque los caracteres acentuados, debe modificarse una librerУ­a.
// SdBaseFile.cpp, linea 418 pone:
//       if (i > n || c < 0X21 || c > 0X7E)goto fail;
// debe cambiuarse a:
//       if (i > n || c < 0X21)goto fail;
*/

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

// Las cadenas constantes se ponen en memoria de programa
#define P_P(x,y) print_P(x,PSTR(y));
#define Pln_P(x,y) println_P(x,PSTR(y));


// Descomentar para no mostrar la versiУГn
#define VERSION "Alberto Seva WebServerSD.015m" 
// Desconectar para no mostrar los atributos en el listado de directorios
// Se pueden aУБadir condiciones para no ver determinados tipos de ficheros
#define ATRIBUTOS
// Descomentar para ver los caracteres leidos en HEX para cada byte de la entrada
// de directorio.
//#define DEBUG_FileOpenClose
//#define DEBUG_VerFiledirHex
//#define DEBUG_VerDatosTarjeta
//#define DEBUG_VerTreeTarjeta
//#define DEBUG_VerConversionUTF_8
//#define DEBUG_VerCambiosPath
//#define DEBUG_NombreLargo

// http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1284593828

/************ ETHERNET STUFF ************/
byte mac[] = { 0xCA, 0xAF, 0x78, 0x1C, 0x13, 0x77 }; //mac - адрес ethernet shielda
byte ip[] = { 192,168,2,111 };        // ip адрес ethernet shielda
//byte subnet[] = { 255, 255, 255, 0 }; //маска подсети
EthernetServer server(80); //порт сервера

/************ SDCARD STUFF ************/
Sd2Card card;
SdVolume volume;
SdFile root;
SdFile dir;
SdFile file;

// Buffer multiusos:
// - Para Transferencia de ficheros en bloques
// - Para Path en enlaces a ficheros al mostrar el directorio
// - Buffer de recepciУГn de la orden
// AtenciУГn, una vez obtenido el nombre del fichero, se emplea para transferencia del propio fichero
// o para gestion del nombre si es directorio
#define SIZEBUFFER 128  // 256 38s,
char MiBuffer[SIZEBUFFER];

#define BNL_FILAS 6
// Nombre largo
// Como aparecen las entradas desde atrУЁs hacia alante, se rellena desde atras
// y luego se lee como unicode
// unsigned int 2 bytes para unicode
int BnlFila_i=0;     // -1 si nombre largo mal    
boolean BnlSi=false; // Hay nombre largo
char Bnl[BNL_FILAS+1][26];

// store error strings in flash to save RAM
#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);
}

void setup() {
  // Ultimo caracter de buffer de nombre largo a 0
  Bnl[BNL_FILAS+1][0]='\0';
  Bnl[BNL_FILAS+1][1]='\0';
  // Debug por Serial
  Serial.begin(115200);
#ifdef VERSION  
  PgmPrint("\n");PgmPrintln(VERSION);
  PgmPrint("Free RAM: ");
  Serial.println(FreeRam());  
#endif  
  
  // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with
  // breadboards.  use SPI_FULL_SPEED for better performance.
  pinMode(10, OUTPUT);                       // set the SS pin as an output (necessary!)
  digitalWrite(10, HIGH);                    // but turn off the W5100 chip!

  // initilize card
  if (!card.init(SPI_HALF_SPEED, 4)) error("card.init failed!");

#ifdef DEBUG_VerDatosTarjeta
  // print the type of card
//  PgmPrint("\nCard type: ");
  Serial.print(("\nCard type: "));
  switch(card.type()) {
    case SD_CARD_TYPE_SD1:
      PgmPrintln("SD1");
      break;
    case SD_CARD_TYPE_SD2:
      PgmPrintln("SD2");
      break;
    case SD_CARD_TYPE_SDHC:
      PgmPrintln("SDHC");
      break;
    default:
      PgmPrintln("Unknown");
  }
#endif

  // initialize a FAT volume
  if (!volume.init(&card)) error("vol.init failed!");

#ifdef DEBUG_VerDatosTarjeta
  PgmPrint("Volume is FAT: "); Serial.println(volume.fatType(),DEC);
  PgmPrint(" Bloques por cluster: "); Serial.println(volume.blocksPerCluster(),DEC);
  PgmPrint(" Bloques por FAT: "); Serial.println(volume.blocksPerFat(),DEC);
  PgmPrint(" Numero de clusters: "); Serial.println(volume.clusterCount(),DEC);
  uint32_t volumesize;
  volumesize = volume.blocksPerCluster();    // clusters are collections of blocks
  volumesize *= volume.clusterCount();       // we'll have a lot of clusters
  volumesize *= 512;                        // SD card blocks are always 512 bytes
  volumesize /= 1024;                       // Kib
  volumesize /= 1024;                       // Mib
  PgmPrint(" Volume size (Mbytes): "); Serial.println(volumesize,DEC);
#endif

  if (!root.openRoot(&volume)) error("openRoot failed");

#ifdef DEBUG_VerTreeTarjeta
  // Recursive list of all directories
  PgmPrintln("Files found in all dirs:");
  root.ls(&Serial,LS_R | LS_DATE | LS_SIZE,1);
  Serial.println();
#endif

  PgmPrintln("Done");
  
  // Debugging complete, we start the server!
  Ethernet.begin(mac, ip);
  server.begin();
}

// Cabecera HTML (no se muestra en codigo fuente)
#define TXT 1
#define RAR 2
#define MP3 3
#define JPG 4
#define XLS 5
#define GIF 6
#define HTM 7
#define ZIP 8
#define PDF 9
#define TAR 10

void htmlCab(EthernetClient client,int tipo) {
  Pln_P(&client,"HTTP/1.1 200 OK");
  PgmPrintln("HTTP/1.1 200 OK");
  P_P(&client,"Content-Type: ");
  PgmPrint("Content-Type: ");
  switch (tipo) {
    case TXT: Pln_P(&client,"text/html");
              PgmPrint("text/html");
              break;
    case RAR: Pln_P(&client,"application/x-rar");
              PgmPrint("application/x-rar");
              break;
    case MP3: Pln_P(&client,"audio/mpeg");
              PgmPrint("audio/mpeg");
              break;
    case JPG: Pln_P(&client,"image/jpeg");
              PgmPrint("image/jpeg");
              break;
    case XLS: Pln_P(&client,"application/xls");
              PgmPrint("application/xls");
              break;
    case GIF: Pln_P(&client,"image/gif");
              PgmPrint("image/gif");
              break;
    case HTM: Pln_P(&client,"text/html");
              PgmPrint("text/html");
              break;
    case ZIP: Pln_P(&client,"application/x-zip");
              PgmPrint("application/x-zip");
              break;
    case PDF: Pln_P(&client,"application/pdf");
              PgmPrint("application/pdf");
              break;
    case TAR: Pln_P(&client,"application/x-tar");
              PgmPrint("application/x-tar");
              break;
    default:  Pln_P(&client,"application");
              PgmPrint("application");
  }
  client.println();
  Serial.println();
  PgmPrint("Tipo Content-Type: ");Serial.println(tipo);
}

// Para mostrar bien las cosas
void htmlStart(EthernetClient client) { 
  Pln_P(&client,"<html>");
  Pln_P(&client,"<head>");
  // Los nombres cortos 8.3 estУЁn guardados en cp850.
  // Pln_P(&client,"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=windows-1252\">");
  Pln_P(&client,"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=cp850\">");
  Pln_P(&client,"</head>");
  Pln_P(&client,"<body>");
}

// Para mostrar bien las cosas
void htmlEnd(EthernetClient client) {
  Pln_P(&client,"</body>\n</html>");
}

void HTNLNotFound(EthernetClient client) {
  Pln_P(&client,"HTTP/1.1 404 Not Found");
  Pln_P(&client,"Content-Type: text/html");
  client.println();  
  htmlStart(client);
  Pln_P(&client,"<h2>File Not Found!</h2>");
  htmlEnd(client);
}

// Entre
boolean Entre(char c,char cle, char cge) {
  return ((cle<=c) && (c<=cge));
}

// How big our line buffer should be. 100 is plenty!
//#define BUFSIZ 100
#define BUFSIZ SIZEBUFFER

void loop()
{
//  char clientline[BUFSIZ];
  char * clientline = MiBuffer;

  int index = 0;
  
  EthernetClient client = server.available();
  if (client) {
    // an http request ends with a blank line
    boolean current_line_is_blank = true;
    
    // reset the input buffer
    index = 0;
    
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        
        // If it isn't a new line, add the character to the buffer
        if (c != '\n' && c != '\r') {
          clientline[index] = c;
          index++;
          // are we too big for the buffer? start tossing out data
          if (index >= BUFSIZ) 
            index = BUFSIZ -1;
          
          // continue to read more data!
          continue;
        }
        
        // got a \n or \r new line, which means the string is done
        clientline[index] = 0;
        
        // Print it out for debugging
        Serial.println(clientline);
       
        // Look for substring such as a request to get the root file
        if (strstr(clientline,"GET / ") != 0) {
          // send a standard http response header
          htmlCab(client,HTM);
          // print all the files
          htmlStart(client);
          Pln_P(&client,"<h2>Files:</h2>");
          Pln_P(&client,"<pre>");
          PonRoot(); // Path para enlaces
#ifdef DEBUG_VerCambiosPath          
          PgmPrint("++Path: ");Serial.println(MiBuffer);
#endif
//          ls(&root,&client,LS_R | LS_DATE | LS_SIZE,1);
          ls(&root,&client,LS_DATE | LS_SIZE,1);
          P_P(&client,"</pre>");
          htmlEnd(client);
          break;
        }
       
        // Caracteres %xx
        int i=0,j=i,estado=0,bien=true;
        if (strstr(clientline,"%") !=0 ) {

#ifdef DEBUG_VerConversionUTF_8          
           PgmPrint("++Caracteres especiales: ");Serial.println(clientline);
#endif           
           while (bien) {
             if (estado==0) {
               while ( (clientline[j]=clientline[i++]) != '\0' ) {
                 if (clientline[j]=='%') {estado=1;break;}
                 else j++;
               }
               if (clientline[j]=='\0') break;
             }
             // Como estУЁ en estado 1 mira si es uno o dos bytes
             if (estado==1) {
               // si es dos bites mira y guarda si es 2 УГ 3
               // si es 2 bytes empieza por C
               if (clientline[i]=='C') {
                 i++; 
                 // Debe seguir 2 УГ 3
                 if (clientline[i]=='2') estado=2;
                 else if (clientline[i]='3') estado=3;
                 else {bien=false; break;}
                 i++;
                 // y finalizar con la cebecera del siguiente byte
                 if (clientline[i]!='%') {bien=false; break;}
                 else i++;
               } else  {
                 if (Entre(clientline[i],'0','7') && Entre(clientline[i+1],'0','F')) {
                   clientline[j]=(char)( ( (clientline[i++] & 0xF) * 0x10) + (clientline[i++] & 0xF) );
                   j++;
                   estado=0;
                 } else {
                   bien=false; break;
                 }
               }
             }
             // Estado es 2 УГ 3
             if ( Entre(clientline[i],'8','F') && Entre(clientline[i+1],'0','F') ) {
//               clientline[j]=( ( (clientline[i++] & 0xF) * 0x10) + (clientline[i++] & 0xF) );
               clientline[j]=clientline[i++];
               clientline[j]&=0xF;
               clientline[j]*=0x10;
               clientline[j]+=(clientline[i++] & 0xF);
               if (estado==3) clientline[j]|=0x40;

// Para que funcione y busque los caracteres acentuados, debe modificarse una librerУ­a.
// SdBaseFile.cpp, linea 418 pone:
//       if (i > n || c < 0X21 || c > 0X7E)goto fail;
// debe cambiuarse a:
//       if (i > n || c < 0X21)goto fail;

//

                   // Cambiar caracteres a cp850
                   // PodrУ­a hacerse de otra forma, pero no quedarУ­a tan bonito
                   switch (clientline[j]) {
                     
case 0xA0: {clientline[j]=(char)0xA0;break;} //  NO-BREAK SPACE
case 0xA1: {clientline[j]=(char)0xAD;break;} // ТЁ INVERTED EXCLAMATION MARK
case 0xA2: {clientline[j]=(char)0xBD;break;} // ТЂ CENT SIGN
case 0xA3: {clientline[j]=(char)0x9C;break;} // ТЃ POUND SIGN
case 0xA4: {clientline[j]=(char)0xCF;break;} // ТЄ CURRENCY SIGN
case 0xA5: {clientline[j]=(char)0xBE;break;} // ТЅ YEN SIGN
case 0xA6: {clientline[j]=(char)0xDD;break;} // ТІ BROKEN BAR
case 0xA7: {clientline[j]=(char)0xF5;break;} // ТЇ SECTION SIGN
case 0xA8: {clientline[j]=(char)0xF9;break;} // ТЈ DIAERESIS
case 0xA9: {clientline[j]=(char)0xB8;break;} // ТЉ COPYRIGHT SIGN
case 0xAA: {clientline[j]=(char)0xA6;break;} // ТЊ FEMININE ORDINAL INDICATOR
case 0xAB: {clientline[j]=(char)0xAE;break;} // ТЋ LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
case 0xAC: {clientline[j]=(char)0xAA;break;} // ТЌ NOT SIGN
case 0xAD: {clientline[j]=(char)0xF0;break;} // Т­ SOFT HYPHEN
case 0xAE: {clientline[j]=(char)0xA9;break;} // ТЎ REGISTERED SIGN
case 0xAF: {clientline[j]=(char)0xEE;break;} // ТЏ MACRON
case 0xB0: {clientline[j]=(char)0xF8;break;} // ТА DEGREE SIGN
case 0xB1: {clientline[j]=(char)0xF1;break;} // ТБ PLUS-MINUS SIGN
case 0xB2: {clientline[j]=(char)0xFD;break;} // ТВ SUPERSCRIPT TWO
case 0xB3: {clientline[j]=(char)0xFC;break;} // ТГ SUPERSCRIPT THREE
case 0xB4: {clientline[j]=(char)0xEF;break;} // ТД ACUTE ACCENT
case 0xB5: {clientline[j]=(char)0xE6;break;} // ТЕ MICRO SIGN
case 0xB6: {clientline[j]=(char)0xF4;break;} // ТЖ PILCROW SIGN
case 0xB7: {clientline[j]=(char)0xFA;break;} // ТЗ MIDDLE DOT
case 0xB8: {clientline[j]=(char)0xF7;break;} // ТИ CEDILLA
case 0xB9: {clientline[j]=(char)0xD5;break;} // ТЙ SUPERSCRIPT ONE
case 0xBA: {clientline[j]=(char)0xA7;break;} // ТК MASCULINE ORDINAL INDICATOR
case 0xBB: {clientline[j]=(char)0xAF;break;} // ТЛ RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
case 0xBC: {clientline[j]=(char)0xAC;break;} // ТМ VULGAR FRACTION ONE QUARTER
case 0xBD: {clientline[j]=(char)0xAB;break;} // ТН VULGAR FRACTION ONE HALF
case 0xBE: {clientline[j]=(char)0xF3;break;} // ТО VULGAR FRACTION THREE QUARTERS
case 0xBF: {clientline[j]=(char)0xA8;break;} // ТП INVERTED QUESTION MARK
case 0xC0: {clientline[j]=(char)0xB7;break;} // У LATIN CAPITAL LETTER A WITH GRAVE
case 0xC1: {clientline[j]=(char)0xB5;break;} // У LATIN CAPITAL LETTER A WITH ACUTE
case 0xC2: {clientline[j]=(char)0xB6;break;} // У LATIN CAPITAL LETTER A WITH CIRCUMFLEX
case 0xC3: {clientline[j]=(char)0xC7;break;} // У LATIN CAPITAL LETTER A WITH TILDE
case 0xC4: {clientline[j]=(char)0x8E;break;} // У LATIN CAPITAL LETTER A WITH DIAERESIS
case 0xC5: {clientline[j]=(char)0x8F;break;} // У LATIN CAPITAL LETTER A WITH RING ABOVE
case 0xC6: {clientline[j]=(char)0x92;break;} // У LATIN CAPITAL LETTER AE
case 0xC7: {clientline[j]=(char)0x80;break;} // У LATIN CAPITAL LETTER C WITH CEDILLA
case 0xC8: {clientline[j]=(char)0xD4;break;} // У LATIN CAPITAL LETTER E WITH GRAVE
case 0xC9: {clientline[j]=(char)0x90;break;} // У LATIN CAPITAL LETTER E WITH ACUTE
case 0xCA: {clientline[j]=(char)0xD2;break;} // У LATIN CAPITAL LETTER E WITH CIRCUMFLEX
case 0xCB: {clientline[j]=(char)0xD3;break;} // У LATIN CAPITAL LETTER E WITH DIAERESIS
case 0xCC: {clientline[j]=(char)0xDE;break;} // У LATIN CAPITAL LETTER I WITH GRAVE
case 0xCD: {clientline[j]=(char)0xD6;break;} // У LATIN CAPITAL LETTER I WITH ACUTE
case 0xCE: {clientline[j]=(char)0xD7;break;} // У LATIN CAPITAL LETTER I WITH CIRCUMFLEX
case 0xCF: {clientline[j]=(char)0xD8;break;} // У LATIN CAPITAL LETTER I WITH DIAERESIS
case 0xD0: {clientline[j]=(char)0xD1;break;} // У LATIN CAPITAL LETTER ETH
case 0xD1: {clientline[j]=(char)0xA5;break;} // У LATIN CAPITAL LETTER N WITH TILDE
case 0xD2: {clientline[j]=(char)0xE3;break;} // У LATIN CAPITAL LETTER O WITH GRAVE
case 0xD3: {clientline[j]=(char)0xE0;break;} // У LATIN CAPITAL LETTER O WITH ACUTE
case 0xD4: {clientline[j]=(char)0xE2;break;} // У LATIN CAPITAL LETTER O WITH CIRCUMFLEX
case 0xD5: {clientline[j]=(char)0xE5;break;} // У LATIN CAPITAL LETTER O WITH TILDE
case 0xD6: {clientline[j]=(char)0x99;break;} // У LATIN CAPITAL LETTER O WITH DIAERESIS
case 0xD7: {clientline[j]=(char)0x9E;break;} // У MULTIPLICATION SIGN
case 0xD8: {clientline[j]=(char)0x9D;break;} // У LATIN CAPITAL LETTER O WITH STROKE
case 0xD9: {clientline[j]=(char)0xEB;break;} // У LATIN CAPITAL LETTER U WITH GRAVE
case 0xDA: {clientline[j]=(char)0xE9;break;} // У LATIN CAPITAL LETTER U WITH ACUTE
case 0xDB: {clientline[j]=(char)0xEA;break;} // У LATIN CAPITAL LETTER U WITH CIRCUMFLEX
case 0xDC: {clientline[j]=(char)0x9A;break;} // У LATIN CAPITAL LETTER U WITH DIAERESIS
case 0xDD: {clientline[j]=(char)0xED;break;} // У LATIN CAPITAL LETTER Y WITH ACUTE
case 0xDE: {clientline[j]=(char)0xE7;break;} // У LATIN CAPITAL LETTER THORN
case 0xDF: {clientline[j]=(char)0xE1;break;} // У LATIN SMALL LETTER SHARP S
case 0xE0: {clientline[j]=(char)0x85;break;} // У  LATIN SMALL LETTER A WITH GRAVE
case 0xE1: {clientline[j]=(char)0xA0;break;} // УЁ LATIN SMALL LETTER A WITH ACUTE
case 0xE2: {clientline[j]=(char)0x83;break;} // УЂ LATIN SMALL LETTER A WITH CIRCUMFLEX
case 0xE3: {clientline[j]=(char)0xC6;break;} // УЃ LATIN SMALL LETTER A WITH TILDE
case 0xE4: {clientline[j]=(char)0x84;break;} // УЄ LATIN SMALL LETTER A WITH DIAERESIS
case 0xE5: {clientline[j]=(char)0x86;break;} // УЅ LATIN SMALL LETTER A WITH RING ABOVE
case 0xE6: {clientline[j]=(char)0x91;break;} // УІ LATIN SMALL LETTER AE
case 0xE7: {clientline[j]=(char)0x87;break;} // УЇ LATIN SMALL LETTER C WITH CEDILLA
case 0xE8: {clientline[j]=(char)0x8A;break;} // УЈ LATIN SMALL LETTER E WITH GRAVE
case 0xE9: {clientline[j]=(char)0x82;break;} // УЉ LATIN SMALL LETTER E WITH ACUTE
case 0xEA: {clientline[j]=(char)0x88;break;} // УЊ LATIN SMALL LETTER E WITH CIRCUMFLEX
case 0xEB: {clientline[j]=(char)0x89;break;} // УЋ LATIN SMALL LETTER E WITH DIAERESIS
case 0xEC: {clientline[j]=(char)0x8D;break;} // УЌ LATIN SMALL LETTER I WITH GRAVE
case 0xED: {clientline[j]=(char)0xA1;break;} // У­ LATIN SMALL LETTER I WITH ACUTE
case 0xEE: {clientline[j]=(char)0x8C;break;} // УЎ LATIN SMALL LETTER I WITH CIRCUMFLEX
case 0xEF: {clientline[j]=(char)0x8B;break;} // УЏ LATIN SMALL LETTER I WITH DIAERESIS
case 0xF0: {clientline[j]=(char)0xD1;break;} // УА LATIN SMALL LETTER ETH
case 0xF1: {clientline[j]=(char)0xA4;break;} // УБ LATIN SMALL LETTER N WITH TILDE
case 0xF2: {clientline[j]=(char)0x95;break;} // УВ LATIN SMALL LETTER O WITH GRAVE
case 0xF3: {clientline[j]=(char)0xA2;break;} // УГ LATIN SMALL LETTER O WITH ACUTE
case 0xF4: {clientline[j]=(char)0x93;break;} // УД LATIN SMALL LETTER O WITH CIRCUMFLEX
case 0xF5: {clientline[j]=(char)0xE4;break;} // УЕ LATIN SMALL LETTER O WITH TILDE
case 0xF6: {clientline[j]=(char)0x94;break;} // УЖ LATIN SMALL LETTER O WITH DIAERESIS
case 0xF7: {clientline[j]=(char)0xF6;break;} // УЗ DIVISION SIGN
case 0xF8: {clientline[j]=(char)0x9B;break;} // УИ LATIN SMALL LETTER O WITH STROKE
case 0xF9: {clientline[j]=(char)0x97;break;} // УЙ LATIN SMALL LETTER U WITH GRAVE
case 0xFA: {clientline[j]=(char)0xA3;break;} // УК LATIN SMALL LETTER U WITH ACUTE
case 0xFB: {clientline[j]=(char)0x96;break;} // УЛ LATIN SMALL LETTER U WITH CIRCUMFLEX
case 0xFC: {clientline[j]=(char)0x81;break;} // УМ LATIN SMALL LETTER U WITH DIAERESIS
case 0xFD: {clientline[j]=(char)0xEC;break;} // УН LATIN SMALL LETTER Y WITH ACUTE
case 0xFE: {clientline[j]=(char)0xE8;break;} // УО LATIN SMALL LETTER THORN
case 0xFF: {clientline[j]=(char)0x98;break;} // УП LATIN SMALL LETTER Y WITH DIAERESIS

                   }
              j++; estado=0;
             } else {
               bien=false; break;
             }
           } // while
           if (!bien) {
             PgmPrint("++Caracteres especiales: Ha fallado");
             HTNLNotFound(client);
           }
           
#ifdef DEBUG_VerConversionUTF_8           
           PgmPrint("++Caracteres especiales: ");Serial.println(clientline);
#endif           
        }
        // La cadena clientline estУЁ para extraer nombres de fichero
        
        if (strstr(clientline,"GET /") != 0) {
          // this time no space after the /, so a sub-file!
          char *filename;
          
          filename = clientline + 4; // look after the "GET /" (4 chars)
          // a little trick, look for the " HTTP/1.1" string and 
          // turn the first character of the substring into a 0 to clear it out.
          (strstr(clientline," HTTP"))[0] = 0;
          
          // print the file we want
          Serial.println(filename);

#ifdef DEBUG_VerConversionUTF_8
// debug de nombre          
    for (int j=0;filename[j]!='\0';j++) {
      Serial.print((char)filename[j],HEX);Serial.print(' ');
    }
    Serial.println();
#endif
          
          if (! file.open(&root, filename, O_READ)) {
            
#ifdef DEBUG_VerConversionUTF_8            
            PgmPrint("++Caracteres especiales: No encontrado");
#endif

            HTNLNotFound(client);
            break;
          }
          
#ifdef DEBUG_FileOpenClose          
          PgmPrintln("Opened!");
#endif          
          
          // Si es directorio, muestra el УЁrbol descendente.
          // Si es fichero lo traslada (write)
          
          if (file.isDir()) {
            htmlStart(client);
            P_P(&client,"<h2>Files: ");
            client.print(filename);
            Pln_P(&client,"</h2>");
            Pln_P(&client,"<pre>");
            // AtenciУГn: aqui hay un truco. Se copia el filename al principio del mismo buffer.
            // El puntero filename no debe volver a usarse.
            PonPath(filename); // Path para enlaces
#ifdef DEBUG_VerCambiosPath            
            PgmPrint("++Path: ");Serial.println(MiBuffer);
#endif
//            ls(&file,&client,LS_R | LS_DATE | LS_SIZE,1);
            ls(&file,&client,LS_DATE | LS_SIZE,1);
            Pln_P(&client,"</pre>");
            htmlEnd(client);
            file.close();
            break;
          }
            
          // Es fichero a descargar
#ifdef DEBUG_FileOpenClose                    
          PgmPrintln("A por la extension.");
#endif          
          char * extension;
          int i=0;
          while (filename[i++]!='\0');
#ifdef DEBUG_FileOpenClose                    
          PgmPrint("LongFilename: ");Serial.println(i);
#endif          
          while (i>0 && filename[i]!='.') i--;
          if (i>0) {
            extension=&(filename[++i]);
#ifdef DEBUG_FileOpenClose               
            PgmPrint("Extension: ");Serial.println(extension);
#endif            

#define Ve3Z(x,y,z,t) Serial.print(x[0]);Serial.print(x[1]);Serial.print(x[2]);Serial.write(' ');Serial.write(y);Serial.write(z);Serial.write(t);Serial.println()
            Ve3Z(extension,'H','T','M');

#define Ve3(x,y,z,t) (((x[0]&0xDF)==y) && ((x[1]&0xDF)==z) && ((x[2]&0xDF)==t))
                 if Ve3(extension,'T','X','T') htmlCab(client,TXT);
            else if Ve3(extension,'R','A','R') htmlCab(client,RAR);
            else if Ve3(extension,'M','P','3') htmlCab(client,MP3);
            else if Ve3(extension,'J','P','G') htmlCab(client,JPG);
            else if Ve3(extension,'X','L','S') htmlCab(client,XLS);
            else if Ve3(extension,'G','I','F') htmlCab(client,GIF);
            else if Ve3(extension,'H','T','M') htmlCab(client,HTM);
            else if Ve3(extension,'Z','I','P') htmlCab(client,ZIP);
            else if Ve3(extension,'P','D','F') htmlCab(client,PDF);
            else if Ve3(extension,'T','A','R') htmlCab(client,TAR);
            else                               htmlCab(client,99);            

          } else htmlCab(client,HTM);
          
          // Aqui hay un truco: el buffer empleado es el mimsmo que tiene filename y extensiУГn.
          // Los punteros no deben volver a usarse
          // Leo en cantidades de BUFFESIZE 
          uint32_t Size=file.fileSize();
          while ((Size>=SIZEBUFFER) && (file.read(MiBuffer,SIZEBUFFER) > -1)) {
              client.write((uint8_t*)MiBuffer,SIZEBUFFER);
              Size-=SIZEBUFFER;
          }
         if ((Size>0) && (Size<SIZEBUFFER) && (file.read(MiBuffer,Size) > -1)) client.write((uint8_t*)MiBuffer,Size);
          file.close();
#ifdef DEBUG_FileOpenClose           
          PgmPrintln("Closed!");
#endif          
        } else {
          // everything else is a 404
          HTNLNotFound(client);
        }
        break;
      }
    }
    // give the web browser time to receive the data
    delay(1);
    client.stop();
  }
}

//
// Escribe una cadena de unsigne int en HTML, caracteres y unicode cuando haga falta
//void PrintUnicode(Print* pr,unsigned int *p) {
void PrintUnicode(Print* pr) {
  unsigned int *p=(unsigned int *)&Bnl;
  while (*p != 0) {
    if ((0x1F < *p) && (*p < 0x80)) pr->write((char)*p);
    else {
      pr->print("&#x");pr->print(*p,HEX);pr->write(';');
    }
    p++;
  }
}

// Nombre del УКltimo fichero encontrado. Si es directorio debe pasarse al Path para
// tener la referencia del siguiente nivel
char FN83[13];

// Listado de subestructura de directorios
void ls(SdBaseFile* This,Print* pr, uint8_t flags, uint8_t indent) {
  This->rewind();
  int8_t status;
  while ((status = lsPrintNext(This, pr, flags, indent))) {
    if (status > 1 && (flags & LS_R)) {
      uint16_t index = This->curPosition()/32 - 1;
      SdBaseFile s;
      PonDir(FN83);
#ifdef DEBUG_VerCambiosPath
      PgmPrint("--Fichero: ");Serial.println(FN83);
      PgmPrint("++Path: ");Serial.println(MiBuffer);
#endif      
      if (s.open(This, index, O_READ)) ls(&s,pr, flags, indent + 2);
      QuitaDir();
#ifdef DEBUG_VerCambiosPath
      PgmPrint("--Path: ");Serial.println(MiBuffer);
#endif      
      This->seekSet(32 * (index + 1));
    }
  }
}

//------------------------------------------------------------------------------
// saves 32 bytes on stack for ls recursion
// return 0 - EOF, 1 - normal file, or 2 - directory
int8_t lsPrintNext(SdBaseFile *This,Print* pr, uint8_t flags, uint8_t indent) {
  dir_t dir;
  uint8_t w = 0;
//  char FN83[13];

  // No sale hasta que encuentra un fichero a mostrar
  // Las lineas de nombres largos rellenan el buffer de nombre largo
  while (1) {

    // No lee lo suficiente
    if (This->read(&dir, sizeof(dir)) != sizeof(dir)) return 0; // No hay mas ficheros

#ifdef DEBUG_VerFiledirHex
// debug d caracteres leУ­dos
    for (int j=0;j<sizeof(dir);j++) {
      pr->print(dir.name[j],HEX);pr->print(' ');
//      Serial.print(dir.name[j],HEX);Serial.print(' ');
    }
    pr->println();
//    Serial.println;
#endif

    // entrada libre en fin de entradas de fichero
    if (dir.name[0] == DIR_NAME_FREE) return 0; // no hay mas ficheros
    // es una entrada borrada
    if (dir.name[0] == DIR_NAME_DELETED) continue; // buscar siguiente
    // salta el directorio . (el .. me interesa para subir)
    if ((dir.name[0] == '.') && (dir.name[1] != '.')) continue; // buscar siguiente
    // Condicion de nombre largo negativa para salis
    if (DIR_IS_FILE_OR_SUBDIR(&dir)) break; // Ficheros y directorios
    if ((dir.attributes & DIR_ATT_LONG_NAME_MASK) != DIR_ATT_LONG_NAME) break; // Sale a impimir linea

    // Nombre largo, se aУБade al buffer    
    if (BnlFila_i == -1) continue; // Error en nombre largo. La rutina de escritura lo debe poner a cero
    // Si es final 0x4X (aparece primero) y no hay nombre largo inicializo nombre largo
    if ((dir.name[0] & 0xF0) == 0x40) {
        if (BnlSi) {BnlFila_i=-1; continue;} // Algo raro en la secuencia
        else BnlSi=true;
        // DeberУ­a ser un else pero no hace falta
        if ((BnlFila_i=(dir.name[0] & 0x3F)) > BNL_FILAS) {BnlFila_i=-1; continue;} // Fila inicial No cabe
        if (BnlFila_i<BNL_FILAS) Bnl[BnlFila_i--][0]=0; // Asegurar de poner un 0 por si las mosca
        else BnlFila_i--;     
    } else {
      if (!(BnlFila_i == (dir.name[0] & 0x0F))) {BnlFila_i=-1; continue;} // Algo raro en la secuencia
      BnlFila_i--;
    }
    // Ahora a rellenar la linea
    int k=0;
    for (int i=1;i<32;i++) {
      if (i==0 || i==11 || i==12 || i==13 || i==26 || i==27) continue;
      Bnl[BnlFila_i][k++]=dir.name[i];
    }

#ifdef DEBUG_NombreLargo
  // Si entrada de nombre largo, mostrarlo en hexa
  if ((dir.attributes & DIR_ATT_LONG_NAME_MASK) == DIR_ATT_LONG_NAME) {
    for (int j=0;j<sizeof(dir);j++) {
      pr->print(dir.name[j],HEX);pr->print(' ');
      Serial.print(dir.name[j],HEX);Serial.print(' ');
    }
    pr->println();
    Serial.println();
  }
#endif

  } // while
  
  // indent for dir level
  for (uint8_t i = 0; i < indent; i++) pr->write(' ');

  // print name
  int j=0;
  for (uint8_t i = 0; i < 11; i++) {
    if (dir.name[i] == ' ')continue;
    if (i == 8) {
      FN83[j++]='.';
      w++;
    }
    FN83[j++]=dir.name[i];
    w++;
  }
  // Termina en 0
  FN83[j]='\0';
  
  // Escribe referencia de fichero //
  if (!(dir.attributes & DIR_ATT_VOLUME_ID)) 
  { // No es etiqueta de volumen
    
  pr->print("<a href=\"");
  pr->print(MiBuffer); // Path termina en /
  pr->print(FN83);
  pr->print("\">");

  } // endif No es etiqueta de volumen
  pr->print(FN83);
  //Serial.print(FN83);
  // Esto solo a la vista, no en el enlace
  if (DIR_IS_SUBDIR(&dir)) {
    pr->write('/');
    w++;
  }

  if (!(dir.attributes & DIR_ATT_VOLUME_ID)) 
  { // No es etiqueta de volumen
  pr->print("</a>");
  } // endif No es etiqueta de volumen
  
  if (flags & (LS_DATE | LS_SIZE)) {
    while (w++ < 14) pr->write(' ');
  }
#ifdef ATRIBUTOS  
  // Mostrar atributos
  pr->write(' ');
  pr->write((dir.attributes & DIR_ATT_READ_ONLY) ? 'R' : ' '); //1
  pr->write((dir.attributes & DIR_ATT_HIDDEN   ) ? 'H' : ' '); //2
  pr->write((dir.attributes & DIR_ATT_SYSTEM   ) ? 'S' : ' '); //4
  pr->write((dir.attributes & DIR_ATT_VOLUME_ID) ? 'V' : ' '); //8
  pr->write((dir.attributes & DIR_ATT_DIRECTORY) ? 'D' : ' '); //16 0x10
  pr->write((dir.attributes & DIR_ATT_ARCHIVE  ) ? 'A' : ' '); //32 0x20  
  pr->write((dir.attributes & 0x40             ) ? '6' : ' '); //64 0x40
  pr->write((dir.attributes & 0x80             ) ? '7' : ' '); //128 0x80 
#endif  
  
  // print modify date/time if requested
  if (flags & LS_DATE) {
    pr->write(' ');
    This->printFatDate(pr, dir.lastWriteDate);
    pr->write(' ');
    This->printFatTime(pr, dir.lastWriteTime);
  }
  // print size if requested
  if (!DIR_IS_SUBDIR(&dir) && !(dir.attributes & DIR_ATT_VOLUME_ID) && (flags & LS_SIZE)) {
    pr->write(' ');
    pr->print(dir.fileSize);
  }
  // nombre largo
  if (BnlSi && (BnlFila_i!=-1)) {
    pr->write(' ');
    PrintUnicode(pr);
//    PrintUnicode(&Serial);
  }
  BnlSi=false;BnlFila_i=0;
//

  pr->println();
  //Serial.println();
  int8_t retorno;
  if (!DIR_IS_SUBDIR(&dir)) retorno=1;
  else retorno=(dir.name[0] == '.') ? 1: 2;
  return retorno;
}

// Pon directorio raiz
void PonRoot() {
  MiBuffer[0]='/';
  MiBuffer[1]='\0'; // inicializo la cadena
#ifdef DEBUG_VerCambiosPath
    PgmPrint("PonRoot: ");Serial.println(MiBuffer);
#endif
}

// Pone el path completo
void PonPath(char *c) {
//  PgmPrint("-- Rutina PonPath: ");Serial.println(c);
  int i=0;
//int j=i;Serial.print(j);PgmPrint(" ");Serial.print((char)c[j]);
  while ((MiBuffer[i]=c[i])!='\0') i++; // {PgmPrint(" ");Serial.println(MiBuffer[j],HEX);j=i;}
  // Si no termina en / la aУБado
  if (MiBuffer[(i-1)]!='/') {
    MiBuffer[i++]='/';
    MiBuffer[i]='\0';
#ifdef DEBUG_VerCambiosPath
    PgmPrint("PonPath: ");Serial.println(MiBuffer);
#endif
  }
}
  
// AУБado nombre al path y pongo barra si falta  
void PonDir(char* c) {
  int i=0,j=0;
  for (i=0;i<SIZEBUFFER;i++) { // Busco el 0
    if (MiBuffer[i]=='\0') break;
  }
  // Si no termina en '/' lo aУБado
  if ((i==0) || (MiBuffer[i-1]!='/')) {
    MiBuffer[i++]='/';
    MiBuffer[i]='/0';
  }
  while ((MiBuffer[i++]=c[j++])!='\0');
  if (MiBuffer[i-2]!='/') {
    MiBuffer[i-1]='/';
    MiBuffer[i]='\0';
  }
}

// Termina en barra, por lo que tengo que buscar la segunda barra
void QuitaDir() {
  int i;
  for (i=0;i<SIZEBUFFER;i++) { // Busco el 0
    if (MiBuffer[i]=='\0') break;  
  }
  if ((i>0) && (MiBuffer[--i]=='/')) i--;
  while ((i>0) && (MiBuffer[i]!='/')) i--; // Busco la barra
  if (i==0) {MiBuffer[i]='/';}; // Directorio raiz
  MiBuffer[++i]='\0'; // Nuevo cero
}
    

 

rene
Offline
Зарегистрирован: 21.01.2014

Ого, сколько тут всего полезного, спасибо!

pastry777
Offline
Зарегистрирован: 16.01.2014

полезный скетч,спасибо...