Added ENABLE_TELNET_SERVER compile time option to the Webserver project to disable...
[pub/USBasp.git] / Projects / Webserver / Lib / HTTPServerApp.c
index 3f8a2c3..08d8494 100644 (file)
 /** \file\r
  *\r
  *  Simple HTTP Webserver Application. When connected to the uIP stack,\r
 /** \file\r
  *\r
  *  Simple HTTP Webserver Application. When connected to the uIP stack,\r
- *  this will serve out files to HTTP clients.\r
+ *  this will serve out files to HTTP clients on port 80.\r
  */\r
  \r
  */\r
  \r
+#define  INCLUDE_FROM_HTTPSERVERAPP_C\r
 #include "HTTPServerApp.h"\r
 \r
 /** HTTP server response header, for transmission before the page contents. This indicates to the host that a page exists at the\r
  *  given location, and gives extra connection information.\r
  */\r
 #include "HTTPServerApp.h"\r
 \r
 /** HTTP server response header, for transmission before the page contents. This indicates to the host that a page exists at the\r
  *  given location, and gives extra connection information.\r
  */\r
-char PROGMEM HTTP200Header[] = "HTTP/1.1 200 OK\r\n"\r
-                               "Server: LUFA RNDIS\r\n"\r
-                               "Connection: close\r\n"\r
-                                                          "MIME-version: 1.0\r\n"\r
-                                                          "Content-Type: ";\r
+const char PROGMEM HTTP200Header[] = "HTTP/1.1 200 OK\r\n"\r
+                                     "Server: LUFA " LUFA_VERSION_STRING "\r\n"\r
+                                     "Connection: close\r\n"\r
+                                     "MIME-version: 1.0\r\n"\r
+                                     "Content-Type: ";\r
 \r
 /** HTTP server response header, for transmission before a resource not found error. This indicates to the host that the given\r
  *  given URL is invalid, and gives extra error information.\r
  */\r
 \r
 /** HTTP server response header, for transmission before a resource not found error. This indicates to the host that the given\r
  *  given URL is invalid, and gives extra error information.\r
  */\r
-char PROGMEM HTTP404Header[] = "HTTP/1.1 404 Not Found\r\n"\r
-                               "Server: LUFA RNDIS\r\n"\r
-                               "Connection: close\r\n"\r
-                                                          "MIME-version: 1.0\r\n"\r
-                                                          "Content-Type: text/plain\r\n\r\n"\r
-                                                          "Error 404: File Not Found";\r
-\r
-/** Default MIME type sent if no other MIME type can be determined */\r
-char PROGMEM DefaultMIMEType[] = "text/plain";\r
-\r
-/** List of MIME types for each supported file extension - must be terminated with \ref END_OF_MIME_LIST entry. */\r
-MIME_Type_t PROGMEM MIMETypes[] =\r
+const char PROGMEM HTTP404Header[] = "HTTP/1.1 404 Not Found\r\n"\r
+                                     "Server: LUFA " LUFA_VERSION_STRING "\r\n"\r
+                                     "Connection: close\r\n"\r
+                                     "MIME-version: 1.0\r\n"\r
+                                     "Content-Type: text/plain\r\n\r\n"\r
+                                     "Error 404: File Not Found: /";\r
+\r
+/** Default filename to fetch when a directory is requested */\r
+const char PROGMEM DefaultDirFileName[] = "index.htm";\r
+\r
+/** Default MIME type sent if no other MIME type can be determined. */\r
+const char PROGMEM DefaultMIMEType[] = "text/plain";\r
+\r
+/** List of MIME types for each supported file extension. */\r
+const MIME_Type_t MIMETypes[] =\r
        {\r
                {.Extension = "htm", .MIMEType = "text/html"},\r
                {.Extension = "jpg", .MIMEType = "image/jpeg"},\r
                {.Extension = "gif", .MIMEType = "image/gif"},\r
                {.Extension = "bmp", .MIMEType = "image/bmp"},\r
                {.Extension = "png", .MIMEType = "image/png"},\r
        {\r
                {.Extension = "htm", .MIMEType = "text/html"},\r
                {.Extension = "jpg", .MIMEType = "image/jpeg"},\r
                {.Extension = "gif", .MIMEType = "image/gif"},\r
                {.Extension = "bmp", .MIMEType = "image/bmp"},\r
                {.Extension = "png", .MIMEType = "image/png"},\r
+               {.Extension = "ico", .MIMEType = "image/x-icon"},\r
                {.Extension = "exe", .MIMEType = "application/octet-stream"},\r
                {.Extension = "gz",  .MIMEType = "application/x-gzip"},\r
                {.Extension = "exe", .MIMEType = "application/octet-stream"},\r
                {.Extension = "gz",  .MIMEType = "application/x-gzip"},\r
-               {.Extension = "ico", .MIMEType = "image/x-icon"},\r
                {.Extension = "zip", .MIMEType = "application/zip"},\r
                {.Extension = "pdf", .MIMEType = "application/pdf"},\r
        };\r
 \r
                {.Extension = "zip", .MIMEType = "application/zip"},\r
                {.Extension = "pdf", .MIMEType = "application/pdf"},\r
        };\r
 \r
-/** FAT Fs structure to hold the internal state of the FAT driver for the dataflash contents. */\r
+/** FATFs structure to hold the internal state of the FAT driver for the dataflash contents. */\r
 FATFS DiskFATState;\r
 \r
 \r
 /** Initialization function for the simple HTTP webserver. */\r
 FATFS DiskFATState;\r
 \r
 \r
 /** Initialization function for the simple HTTP webserver. */\r
-void WebserverApp_Init(void)\r
+void HTTPServerApp_Init(void)\r
 {\r
        /* Listen on port 80 for HTTP connections from hosts */\r
        uip_listen(HTONS(HTTP_SERVER_PORT));\r
 {\r
        /* Listen on port 80 for HTTP connections from hosts */\r
        uip_listen(HTONS(HTTP_SERVER_PORT));\r
@@ -90,147 +94,196 @@ void WebserverApp_Init(void)
 /** uIP stack application callback for the simple HTTP webserver. This function must be called each time the\r
  *  TCP/IP stack needs a TCP packet to be processed.\r
  */\r
 /** uIP stack application callback for the simple HTTP webserver. This function must be called each time the\r
  *  TCP/IP stack needs a TCP packet to be processed.\r
  */\r
-void WebserverApp_Callback(void)\r
+void HTTPServerApp_Callback(void)\r
 {\r
 {\r
-       uip_tcp_appstate_t* const AppState    = &uip_conn->appstate;\r
-       char*                     AppData     = (char*)uip_appdata;\r
-       uint16_t                  AppDataSize = 0;\r
+       uip_tcp_appstate_t* const AppState = &uip_conn->appstate;\r
 \r
 \r
-       if (uip_aborted() || uip_timedout())\r
+       if (uip_aborted() || uip_timedout() || uip_closed())\r
        {\r
        {\r
-               /* Close the file before terminating, if it is open */\r
-               f_close(&AppState->FileToSend);\r
+               /* Lock to the closed state so that no further processing will occur on the connection */\r
+               AppState->HTTPServer.CurrentState  = WEBSERVER_STATE_Closing;\r
+               AppState->HTTPServer.NextState     = WEBSERVER_STATE_Closing;\r
+       }\r
 \r
 \r
-               AppState->CurrentState = WEBSERVER_STATE_Closed;\r
+       if (uip_connected())\r
+       {\r
+               /* New connection - initialize connection state values */\r
+               AppState->HTTPServer.CurrentState  = WEBSERVER_STATE_OpenRequestedFile;\r
+               AppState->HTTPServer.NextState     = WEBSERVER_STATE_OpenRequestedFile;\r
+               AppState->HTTPServer.FileOpen      = false;\r
+               AppState->HTTPServer.ACKedFilePos  = 0;\r
+               AppState->HTTPServer.SentChunkSize = 0;\r
+       }\r
 \r
 \r
-               return;\r
+       if (uip_acked())\r
+       {\r
+               /* Add the amount of ACKed file data to the total sent file bytes counter */\r
+               AppState->HTTPServer.ACKedFilePos += AppState->HTTPServer.SentChunkSize;\r
+\r
+               /* Progress to the next state once the current state's data has been ACKed */\r
+               AppState->HTTPServer.CurrentState = AppState->HTTPServer.NextState;\r
        }\r
        }\r
-       else if (uip_closed())\r
+\r
+       if (uip_rexmit())\r
        {\r
        {\r
-               AppState->CurrentState = WEBSERVER_STATE_Closed;\r
+               /* Return file pointer to the last ACKed position */\r
+               f_lseek(&AppState->HTTPServer.FileHandle, AppState->HTTPServer.ACKedFilePos);   \r
+       }\r
 \r
 \r
+       if (uip_rexmit() || uip_acked() || uip_newdata() || uip_connected() || uip_poll())\r
+       {\r
+               switch (AppState->HTTPServer.CurrentState)\r
+               {\r
+                       case WEBSERVER_STATE_OpenRequestedFile:\r
+                               HTTPServerApp_OpenRequestedFile();\r
+                               break;\r
+                       case WEBSERVER_STATE_SendResponseHeader:\r
+                               HTTPServerApp_SendResponseHeader();\r
+                               break;\r
+                       case WEBSERVER_STATE_SendData:\r
+                               HTTPServerApp_SendData();\r
+                               break;\r
+                       case WEBSERVER_STATE_Closing:\r
+                               /* Connection is being terminated for some reason - close file handle */\r
+                               f_close(&AppState->HTTPServer.FileHandle);\r
+                               AppState->HTTPServer.FileOpen = false;\r
+               \r
+                               /* If connection is not already closed, close it */\r
+                               uip_close();\r
+                               \r
+                               AppState->HTTPServer.CurrentState = WEBSERVER_STATE_Closed;\r
+                               AppState->HTTPServer.NextState    = WEBSERVER_STATE_Closed;\r
+                               break;\r
+               }                 \r
+       }               \r
+}\r
+\r
+/** HTTP Server State handler for the Request Process state. This state manages the processing of incoming HTTP\r
+ *  GET requests to the server from the receiving HTTP client.\r
+ */\r
+static void HTTPServerApp_OpenRequestedFile(void)\r
+{\r
+       uip_tcp_appstate_t* const AppState    = &uip_conn->appstate;\r
+       char*               const AppData     = (char*)uip_appdata;\r
+       \r
+       /* No HTTP header received from the client, abort processing */\r
+       if (!(uip_newdata()))\r
+         return;\r
+         \r
+       char* RequestToken      = strtok(AppData, " ");\r
+       char* RequestedFileName = strtok(NULL, " ");\r
+                       \r
+       /* Must be a GET request, abort otherwise */\r
+       if (strcmp_P(RequestToken, PSTR("GET")) != 0)\r
+       {\r
+               uip_abort();\r
                return;\r
        }\r
                return;\r
        }\r
-       else if (uip_connected())\r
+       \r
+       /* Copy over the requested filename */\r
+       strncpy(AppState->HTTPServer.FileName, &RequestedFileName[1], (sizeof(AppState->HTTPServer.FileName) - 1));\r
+       \r
+       /* Ensure filename is null-terminated */\r
+       AppState->HTTPServer.FileName[sizeof(AppState->HTTPServer.FileName) - 1] = 0x00;\r
+       \r
+       /* Determine the length of the URI so that it can be checked to see if it is a directory */\r
+       uint8_t FileNameLen = strlen(AppState->HTTPServer.FileName);\r
+\r
+       /* If the URI is a directory, append the default filename */\r
+       if (AppState->HTTPServer.FileName[FileNameLen - 1] == '/')\r
        {\r
        {\r
-               /* New connection - initialize connection state and data pointer to the appropriate HTTP header */\r
-               AppState->CurrentState = WEBSERVER_STATE_OpenRequestedFile;\r
+               strncpy_P(&AppState->HTTPServer.FileName[FileNameLen], DefaultDirFileName,\r
+                         (sizeof(AppState->HTTPServer.FileName) - FileNameLen));\r
+\r
+               /* Ensure altered filename is still null-terminated */\r
+               AppState->HTTPServer.FileName[sizeof(AppState->HTTPServer.FileName) - 1] = 0x00;\r
        }\r
        \r
        }\r
        \r
-       switch (AppState->CurrentState)\r
+       /* Try to open the file from the Dataflash disk */\r
+       AppState->HTTPServer.FileOpen     = (f_open(&AppState->HTTPServer.FileHandle, AppState->HTTPServer.FileName,\r
+                                                   (FA_OPEN_EXISTING | FA_READ)) == FR_OK);\r
+\r
+       /* Lock to the SendResponseHeader state until connection terminated */\r
+       AppState->HTTPServer.CurrentState = WEBSERVER_STATE_SendResponseHeader;\r
+       AppState->HTTPServer.NextState    = WEBSERVER_STATE_SendResponseHeader;\r
+}\r
+\r
+/** HTTP Server State handler for the HTTP Response Header Send state. This state manages the transmission of\r
+ *  the HTTP response header to the receiving HTTP client.\r
+ */\r
+static void HTTPServerApp_SendResponseHeader(void)\r
+{\r
+       uip_tcp_appstate_t* const AppState    = &uip_conn->appstate;\r
+       char*               const AppData     = (char*)uip_appdata;\r
+\r
+       char* Extension     = strpbrk(AppState->HTTPServer.FileName, ".");\r
+       bool  FoundMIMEType = false;\r
+\r
+       /* If the file isn't already open, it wasn't found - send back a 404 error response and abort */\r
+       if (!(AppState->HTTPServer.FileOpen))\r
        {\r
        {\r
-               case WEBSERVER_STATE_OpenRequestedFile:\r
-                       /* Wait for the packet containing the request header */\r
-                       if (uip_datalen())\r
-                       {\r
-                               /* Must be a GET request, abort otherwise */\r
-                               if (strncmp(AppData, "GET ", (sizeof("GET ") - 1)) != 0)\r
-                               {\r
-                                       uip_abort();\r
-                                       break;\r
-                               }\r
+               /* Copy over the HTTP 404 response header and send it to the receiving client */\r
+               strcpy_P(AppData, HTTP404Header);\r
+               strcpy(&AppData[strlen(AppData)], AppState->HTTPServer.FileName);               \r
+               uip_send(AppData, strlen(AppData));\r
                \r
                \r
-                               /* Copy over the requested filename from the GET request as all-lowercase */\r
-                               for (uint8_t i = 0; i < (sizeof(AppState->FileName) - 1); i++)\r
-                               {\r
-                                       AppState->FileName[i] = tolower(AppData[sizeof("GET ") + i]);\r
-                                       \r
-                                       if (AppState->FileName[i] == ' ')\r
-                                       {\r
-                                               AppState->FileName[i] = 0x00;\r
-                                               break;\r
-                                       }\r
-                               }\r
-                               \r
-                               /* Ensure requested filename is null-terminated */\r
-                               AppState->FileName[(sizeof(AppState->FileName) - 1)] = 0x00;\r
-                               \r
-                               /* If no filename specified, assume the default of index.htm */\r
-                               if (AppState->FileName[0] == 0x00)\r
-                                 strcpy(AppState->FileName, "index.htm");\r
-                               \r
-                               /* Try to open the file from the Dataflash disk */\r
-                               AppState->FileOpen = (f_open(&AppState->FileToSend, AppState->FileName, FA_OPEN_EXISTING | FA_READ) == FR_OK);\r
-\r
-                               AppState->CurrentState = WEBSERVER_STATE_SendResponseHeader;\r
-                       }\r
+               AppState->HTTPServer.NextState = WEBSERVER_STATE_Closing;\r
+               return;\r
+       }\r
+       \r
+       /* Copy over the HTTP 200 response header and send it to the receiving client */\r
+       strcpy_P(AppData, HTTP200Header);\r
 \r
 \r
-                       break;\r
-               case WEBSERVER_STATE_SendResponseHeader:\r
-                       /* Determine what HTTP header should be sent to the client */\r
-                       if (AppState->FileOpen)\r
-                       {\r
-                               AppDataSize = strlen_P(HTTP200Header);\r
-                               strncpy_P(AppData, HTTP200Header, AppDataSize);\r
-                       }\r
-                       else\r
-                       {\r
-                               AppDataSize = strlen_P(HTTP404Header);\r
-                               strncpy_P(AppData, HTTP404Header, AppDataSize);\r
-                       }\r
-                       \r
-                       uip_send(AppData, AppDataSize);\r
-                       \r
-                       AppState->CurrentState = WEBSERVER_STATE_SendMIMETypeHeader;\r
-                       break;\r
-               case WEBSERVER_STATE_SendMIMETypeHeader:\r
-                       /* File must have been found and opened for MIME header to be sent */\r
-                       if (AppState->FileOpen)\r
-                       {\r
-                               char* Extension = strpbrk(AppState->FileName, ".");\r
-                               \r
-                               /* Check to see if a file extension was found for the requested filename */\r
-                               if (Extension != NULL)\r
-                               {\r
-                                       /* Look through the MIME type list, copy over the required MIME type if found */\r
-                                       for (int i = 0; i < (sizeof(MIMETypes) / sizeof(MIMETypes[0])); i++)\r
-                                       {\r
-                                               if (strcmp_P(&Extension[1], MIMETypes[i].Extension) == 0)\r
-                                               {\r
-                                                       AppDataSize = strlen_P(MIMETypes[i].MIMEType);\r
-                                                       strncpy_P(AppData, MIMETypes[i].MIMEType, AppDataSize);                                         \r
-                                                       break;\r
-                                               }\r
-                                       } \r
-                               }\r
-\r
-                               /* Check if a MIME type was found and copied to the output buffer */\r
-                               if (!(AppDataSize))\r
-                               {\r
-                                       /* MIME type not found - copy over the default MIME type */\r
-                                       AppDataSize = strlen_P(DefaultMIMEType);\r
-                                       strncpy_P(AppData, DefaultMIMEType, AppDataSize);                               \r
-                               }\r
-                               \r
-                               /* Add the end-of line terminator and end-of-headers terminator after the MIME type */\r
-                               strncpy(&AppData[AppDataSize], "\r\n\r\n", sizeof("\r\n\r\n"));\r
-                               AppDataSize += (sizeof("\r\n\r\n") - 1);\r
-                               \r
-                               uip_send(AppData, AppDataSize);\r
-                       }\r
-                               \r
-                       AppState->CurrentState = WEBSERVER_STATE_SendData;                              \r
-                       break;\r
-               case WEBSERVER_STATE_SendData:\r
-                       /* If end of file/file not open, progress to the close state */\r
-                       if (!(AppState->FileOpen))\r
+       /* Check to see if a MIME type for the requested file's extension was found */\r
+       if (Extension != NULL)\r
+       {\r
+               /* Look through the MIME type list, copy over the required MIME type if found */\r
+               for (uint8_t i = 0; i < (sizeof(MIMETypes) / sizeof(MIMETypes[0])); i++)\r
+               {\r
+                       if (strcmp(&Extension[1], MIMETypes[i].Extension) == 0)\r
                        {\r
                        {\r
-                               f_close(&AppState->FileToSend);\r
-                               uip_close();\r
-                               AppState->CurrentState = WEBSERVER_STATE_Closed;\r
+                               strcpy(&AppData[strlen(AppData)], MIMETypes[i].MIMEType);                                               \r
+                               FoundMIMEType = true;\r
                                break;\r
                        }\r
                                break;\r
                        }\r
+               } \r
+       }\r
 \r
 \r
-                       uint16_t MaxSegSize = uip_mss();\r
-                       \r
-                       /* Read the next chunk of data from the open file */\r
-                       f_read(&AppState->FileToSend, AppData, MaxSegSize, &AppDataSize);\r
-                       AppState->FileOpen = (MaxSegSize == AppDataSize);\r
-\r
-                       /* If data was read, send it to the client */\r
-                       if (AppDataSize)\r
-                         uip_send(AppData, AppDataSize);\r
-                                       \r
-                       break;\r
+       /* Check if a MIME type was found and copied to the output buffer */\r
+       if (!(FoundMIMEType))\r
+       {\r
+               /* MIME type not found - copy over the default MIME type */\r
+               strcpy_P(&AppData[strlen(AppData)], DefaultMIMEType);\r
        }\r
        }\r
+       \r
+       /* Add the end-of-line terminator and end-of-headers terminator after the MIME type */\r
+       strcpy_P(&AppData[strlen(AppData)], PSTR("\r\n\r\n"));\r
+       \r
+       /* Send the MIME header to the receiving client */\r
+       uip_send(AppData, strlen(AppData));\r
+       \r
+       /* When the MIME header is ACKed, progress to the data send stage */\r
+       AppState->HTTPServer.NextState = WEBSERVER_STATE_SendData;\r
+}\r
+\r
+/** HTTP Server State handler for the Data Send state. This state manages the transmission of file chunks\r
+ *  to the receiving HTTP client.\r
+ */\r
+static void HTTPServerApp_SendData(void)\r
+{\r
+       uip_tcp_appstate_t* const AppState    = &uip_conn->appstate;\r
+       char*               const AppData     = (char*)uip_appdata;\r
+\r
+       /* Get the maximum segment size for the current packet */\r
+       uint16_t MaxChunkSize = uip_mss();\r
+\r
+       /* Read the next chunk of data from the open file */\r
+       f_read(&AppState->HTTPServer.FileHandle, AppData, MaxChunkSize, &AppState->HTTPServer.SentChunkSize);\r
+       \r
+       /* Send the next file chunk to the receiving client */\r
+       uip_send(AppData, AppState->HTTPServer.SentChunkSize);\r
+                       \r
+       /* Check if we are at the last chunk of the file, if so next ACK should close the connection */\r
+       if (MaxChunkSize != AppState->HTTPServer.SentChunkSize)\r
+         AppState->HTTPServer.NextState = WEBSERVER_STATE_Closing;\r
 }\r
 }\r