Switch the HTTP Webserver project to use the safer strlcpy() instead of strncpy(...
[pub/USBasp.git] / Projects / Webserver / Lib / HTTPServerApp.c
index e781beb..37377d0 100644 (file)
-/*\r
-             LUFA Library\r
-     Copyright (C) Dean Camera, 2010.\r
-              \r
-  dean [at] fourwalledcubicle [dot] com\r
-      www.fourwalledcubicle.com\r
-*/\r
-\r
-/*\r
-  Copyright 2010  Dean Camera (dean [at] fourwalledcubicle [dot] com)\r
-\r
-  Permission to use, copy, modify, distribute, and sell this \r
-  software and its documentation for any purpose is hereby granted\r
-  without fee, provided that the above copyright notice appear in \r
-  all copies and that both that the copyright notice and this\r
-  permission notice and warranty disclaimer appear in supporting \r
-  documentation, and that the name of the author not be used in \r
-  advertising or publicity pertaining to distribution of the \r
-  software without specific, written prior permission.\r
-\r
-  The author disclaim all warranties with regard to this\r
-  software, including all implied warranties of merchantability\r
-  and fitness.  In no event shall the author be liable for any\r
-  special, indirect or consequential damages or any damages\r
-  whatsoever resulting from loss of use, data or profits, whether\r
-  in an action of contract, negligence or other tortious action,\r
-  arising out of or in connection with the use or performance of\r
-  this software.\r
-*/\r
-\r
-/** \file\r
- *\r
- *  Simple HTTP Webserver Application. When connected to the uIP stack,\r
- *  this will serve out files to HTTP clients.\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
-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
-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 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
-               {.Extension = "ico", .MIMEType = "image/x-icon"},\r
-               {.Extension = "exe", .MIMEType = "application/octet-stream"},\r
-               {.Extension = "gz",  .MIMEType = "application/x-gzip"},\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 DiskFATState;\r
-\r
-\r
-/** Initialization function for the simple HTTP webserver. */\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
-       /* Mount the dataflash disk via FatFS */\r
-       f_mount(0, &DiskFATState);\r
-}\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 HTTPServerApp_Callback(void)\r
-{\r
-       uip_tcp_appstate_t* const AppState = &uip_conn->appstate;\r
-\r
-       if (uip_aborted() || uip_timedout() || uip_closed())\r
-       {\r
-               /* Connection is being terminated for some reason - close file handle */\r
-               f_close(&AppState->HTTPServer.FileHandle);\r
-               AppState->HTTPServer.FileOpen = false;\r
-               \r
-               /* Lock to the closed state so that no further processing will occur on the connection */\r
-               AppState->HTTPServer.CurrentState  = WEBSERVER_STATE_Closed;\r
-               AppState->HTTPServer.NextState     = WEBSERVER_STATE_Closed;\r
-       }\r
-\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
-       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
-       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_SendMIMETypeHeader:\r
-                               HTTPServerApp_SendMIMETypeHeader();     \r
-                               break;\r
-                       case WEBSERVER_STATE_SendData:\r
-                               HTTPServerApp_SendData();\r
-                               break;\r
-                       case WEBSERVER_STATE_Closing:\r
-                               uip_close();\r
-                               \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
-                       \r
-       /* Must be a GET request, abort otherwise */\r
-       if (strcmp(RequestToken, "GET") != 0)\r
-       {\r
-               uip_abort();\r
-               return;\r
-       }\r
-\r
-       char* RequestedFileName = strtok(NULL, " ");\r
-       \r
-       /* If the requested filename has more that just the leading '/' path in it, copy it over */\r
-       if (strlen(RequestedFileName) > 1)\r
-         strncpy(AppState->HTTPServer.FileName, &RequestedFileName[1], (sizeof(AppState->HTTPServer.FileName) - 1));\r
-       else\r
-         strcpy(AppState->HTTPServer.FileName, "index.htm");\r
-\r
-       /* Ensure filename is null-terminated */\r
-       AppState->HTTPServer.FileName[(sizeof(AppState->HTTPServer.FileName) - 1)] = 0x00;\r
-       \r
-       /* Try to open the file from the Dataflash disk */\r
-       AppState->HTTPServer.FileOpen     = (f_open(&AppState->HTTPServer.FileHandle, AppState->HTTPServer.FileName, 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
-       const char* HeaderToSend;\r
-\r
-       /* Determine which HTTP header should be sent to the client */\r
-       if (AppState->HTTPServer.FileOpen)\r
-       {\r
-               HeaderToSend = HTTP200Header;\r
-               AppState->HTTPServer.NextState = WEBSERVER_STATE_SendMIMETypeHeader;\r
-       }\r
-       else\r
-       {\r
-               HeaderToSend = HTTP404Header;\r
-               AppState->HTTPServer.NextState = WEBSERVER_STATE_Closing;\r
-       }\r
-\r
-       /* Copy over the HTTP response header and send it to the receiving client */\r
-       strcpy_P(AppData, HeaderToSend);\r
-       uip_send(AppData, strlen(AppData));\r
-}\r
-\r
-/** HTTP Server State handler for the MIME Header Send state. This state manages the transmission of the file\r
- *  MIME type header for the requested file to the receiving HTTP client.\r
- */\r
-static void HTTPServerApp_SendMIMETypeHeader(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
-       uint16_t MIMEHeaderLength = 0;\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(&Extension[1], MIMETypes[i].Extension) == 0)\r
-                       {\r
-                               MIMEHeaderLength = strlen(MIMETypes[i].MIMEType);\r
-                               strncpy(AppData, MIMETypes[i].MIMEType, MIMEHeaderLength);                                              \r
-                               break;\r
-                       }\r
-               } \r
-       }\r
-\r
-       /* Check if a MIME type was found and copied to the output buffer */\r
-       if (!(MIMEHeaderLength))\r
-       {\r
-               /* MIME type not found - copy over the default MIME type */\r
-               MIMEHeaderLength = strlen_P(DefaultMIMEType);\r
-               strncpy_P(AppData, DefaultMIMEType, MIMEHeaderLength);\r
-       }\r
-       \r
-       /* Add the end-of line terminator and end-of-headers terminator after the MIME type */\r
-       strncpy(&AppData[MIMEHeaderLength], "\r\n\r\n", sizeof("\r\n\r\n"));\r
-       MIMEHeaderLength += (sizeof("\r\n\r\n") - 1);\r
-       \r
-       /* Send the MIME header to the receiving client */\r
-       uip_send(AppData, MIMEHeaderLength);\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
-       /* Must determine the maximum segment size to determine maximum file chunk size */\r
-       uint16_t MaxSegmentSize = uip_mss();\r
-\r
-       /* Return file pointer to the last ACKed position */\r
-       f_lseek(&AppState->HTTPServer.FileHandle, AppState->HTTPServer.ACKedFilePos);\r
-       \r
-       /* Read the next chunk of data from the open file */\r
-       f_read(&AppState->HTTPServer.FileHandle, AppData, MaxSegmentSize, &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
-       AppState->HTTPServer.NextState = (MaxSegmentSize != AppState->HTTPServer.SentChunkSize) ? WEBSERVER_STATE_Closing : WEBSERVER_STATE_SendData;\r
-}\r
+/*
+             LUFA Library
+     Copyright (C) Dean Camera, 2011.
+
+  dean [at] fourwalledcubicle [dot] com
+           www.lufa-lib.org
+*/
+
+/*
+  Copyright 2011  Dean Camera (dean [at] fourwalledcubicle [dot] com)
+
+  Permission to use, copy, modify, distribute, and sell this
+  software and its documentation for any purpose is hereby granted
+  without fee, provided that the above copyright notice appear in
+  all copies and that both that the copyright notice and this
+  permission notice and warranty disclaimer appear in supporting
+  documentation, and that the name of the author not be used in
+  advertising or publicity pertaining to distribution of the
+  software without specific, written prior permission.
+
+  The author disclaim all warranties with regard to this
+  software, including all implied warranties of merchantability
+  and fitness.  In no event shall the author be liable for any
+  special, indirect or consequential damages or any damages
+  whatsoever resulting from loss of use, data or profits, whether
+  in an action of contract, negligence or other tortious action,
+  arising out of or in connection with the use or performance of
+  this software.
+*/
+
+/** \file
+ *
+ *  Simple HTTP Webserver Application. When connected to the uIP stack,
+ *  this will serve out files to HTTP clients on port 80.
+ */
+
+#define  INCLUDE_FROM_HTTPSERVERAPP_C
+#include "HTTPServerApp.h"
+
+/** HTTP server response header, for transmission before the page contents. This indicates to the host that a page exists at the
+ *  given location, and gives extra connection information.
+ */
+const char PROGMEM HTTP200Header[] = "HTTP/1.1 200 OK\r\n"
+                                     "Server: LUFA " LUFA_VERSION_STRING "\r\n"
+                                     "Connection: close\r\n"
+                                     "MIME-version: 1.0\r\n"
+                                     "Content-Type: ";
+
+/** HTTP server response header, for transmission before a resource not found error. This indicates to the host that the given
+ *  URL is invalid, and gives extra error information.
+ */
+const char PROGMEM HTTP404Header[] = "HTTP/1.1 404 Not Found\r\n"
+                                     "Server: LUFA " LUFA_VERSION_STRING "\r\n"
+                                     "Connection: close\r\n"
+                                     "MIME-version: 1.0\r\n"
+                                     "Content-Type: text/plain\r\n\r\n"
+                                     "Error 404: File Not Found: /";
+
+/** Default filename to fetch when a directory is requested */
+const char PROGMEM DefaultDirFileName[] = "index.htm";
+
+/** Default MIME type sent if no other MIME type can be determined. */
+const char PROGMEM DefaultMIMEType[] = "text/plain";
+
+/** List of MIME types for each supported file extension. */
+const MIME_Type_t MIMETypes[] =
+       {
+               {.Extension = "htm", .MIMEType = "text/html"},
+               {.Extension = "jpg", .MIMEType = "image/jpeg"},
+               {.Extension = "gif", .MIMEType = "image/gif"},
+               {.Extension = "bmp", .MIMEType = "image/bmp"},
+               {.Extension = "png", .MIMEType = "image/png"},
+               {.Extension = "ico", .MIMEType = "image/x-icon"},
+               {.Extension = "exe", .MIMEType = "application/octet-stream"},
+               {.Extension = "gz",  .MIMEType = "application/x-gzip"},
+               {.Extension = "zip", .MIMEType = "application/zip"},
+               {.Extension = "pdf", .MIMEType = "application/pdf"},
+       };
+
+/** FATFs structure to hold the internal state of the FAT driver for the Dataflash contents. */
+FATFS DiskFATState;
+
+
+/** Initialization function for the simple HTTP webserver. */
+void HTTPServerApp_Init(void)
+{
+       /* Listen on port 80 for HTTP connections from hosts */
+       uip_listen(HTONS(HTTP_SERVER_PORT));
+
+       /* Mount the Dataflash disk via FatFS */
+       f_mount(0, &DiskFATState);
+}
+
+/** uIP stack application callback for the simple HTTP webserver. This function must be called each time the
+ *  TCP/IP stack needs a TCP packet to be processed.
+ */
+void HTTPServerApp_Callback(void)
+{
+       uip_tcp_appstate_t* const AppState = &uip_conn->appstate;
+
+       if (uip_aborted() || uip_timedout() || uip_closed())
+       {
+               /* Lock to the closed state so that no further processing will occur on the connection */
+               AppState->HTTPServer.CurrentState  = WEBSERVER_STATE_Closing;
+               AppState->HTTPServer.NextState     = WEBSERVER_STATE_Closing;
+       }
+
+       if (uip_connected())
+       {
+               /* New connection - initialize connection state values */
+               AppState->HTTPServer.CurrentState  = WEBSERVER_STATE_OpenRequestedFile;
+               AppState->HTTPServer.NextState     = WEBSERVER_STATE_OpenRequestedFile;
+               AppState->HTTPServer.FileOpen      = false;
+               AppState->HTTPServer.ACKedFilePos  = 0;
+               AppState->HTTPServer.SentChunkSize = 0;
+       }
+
+       if (uip_acked())
+       {
+               /* Add the amount of ACKed file data to the total sent file bytes counter */
+               AppState->HTTPServer.ACKedFilePos += AppState->HTTPServer.SentChunkSize;
+
+               /* Progress to the next state once the current state's data has been ACKed */
+               AppState->HTTPServer.CurrentState = AppState->HTTPServer.NextState;
+       }
+
+       if (uip_rexmit())
+       {
+               /* Return file pointer to the last ACKed position */
+               f_lseek(&AppState->HTTPServer.FileHandle, AppState->HTTPServer.ACKedFilePos);
+       }
+
+       if (uip_rexmit() || uip_acked() || uip_newdata() || uip_connected() || uip_poll())
+       {
+               switch (AppState->HTTPServer.CurrentState)
+               {
+                       case WEBSERVER_STATE_OpenRequestedFile:
+                               HTTPServerApp_OpenRequestedFile();
+                               break;
+                       case WEBSERVER_STATE_SendResponseHeader:
+                               HTTPServerApp_SendResponseHeader();
+                               break;
+                       case WEBSERVER_STATE_SendData:
+                               HTTPServerApp_SendData();
+                               break;
+                       case WEBSERVER_STATE_Closing:
+                               /* Connection is being terminated for some reason - close file handle */
+                               f_close(&AppState->HTTPServer.FileHandle);
+                               AppState->HTTPServer.FileOpen = false;
+
+                               /* If connection is not already closed, close it */
+                               uip_close();
+
+                               AppState->HTTPServer.CurrentState = WEBSERVER_STATE_Closed;
+                               AppState->HTTPServer.NextState    = WEBSERVER_STATE_Closed;
+                               break;
+               }
+       }
+}
+
+/** HTTP Server State handler for the Request Process state. This state manages the processing of incoming HTTP
+ *  GET requests to the server from the receiving HTTP client.
+ */
+static void HTTPServerApp_OpenRequestedFile(void)
+{
+       uip_tcp_appstate_t* const AppState    = &uip_conn->appstate;
+       char*               const AppData     = (char*)uip_appdata;
+
+       /* No HTTP header received from the client, abort processing */
+       if (!(uip_newdata()))
+         return;
+
+       char* RequestToken      = strtok(AppData, " ");
+       char* RequestedFileName = strtok(NULL, " ");
+
+       /* Must be a GET request, abort otherwise */
+       if (strcmp_P(RequestToken, PSTR("GET")) != 0)
+       {
+               uip_abort();
+               return;
+       }
+
+       /* Copy over the requested filename */
+       strlcpy(AppState->HTTPServer.FileName, &RequestedFileName[1], sizeof(AppState->HTTPServer.FileName));
+
+       /* Determine the length of the URI so that it can be checked to see if it is a directory */
+       uint8_t FileNameLen = strlen(AppState->HTTPServer.FileName);
+
+       /* If the URI is a directory, append the default filename */
+       if ((AppState->HTTPServer.FileName[FileNameLen - 1] == '/') || !(FileNameLen))
+       {
+               strlcpy_P(&AppState->HTTPServer.FileName[FileNameLen], DefaultDirFileName,
+                         (sizeof(AppState->HTTPServer.FileName) - FileNameLen));
+       }
+
+       /* Try to open the file from the Dataflash disk */
+       AppState->HTTPServer.FileOpen     = (f_open(&AppState->HTTPServer.FileHandle, AppState->HTTPServer.FileName,
+                                                   (FA_OPEN_EXISTING | FA_READ)) == FR_OK);
+
+       /* Lock to the SendResponseHeader state until connection terminated */
+       AppState->HTTPServer.CurrentState = WEBSERVER_STATE_SendResponseHeader;
+       AppState->HTTPServer.NextState    = WEBSERVER_STATE_SendResponseHeader;
+}
+
+/** HTTP Server State handler for the HTTP Response Header Send state. This state manages the transmission of
+ *  the HTTP response header to the receiving HTTP client.
+ */
+static void HTTPServerApp_SendResponseHeader(void)
+{
+       uip_tcp_appstate_t* const AppState    = &uip_conn->appstate;
+       char*               const AppData     = (char*)uip_appdata;
+
+       char* Extension     = strpbrk(AppState->HTTPServer.FileName, ".");
+       bool  FoundMIMEType = false;
+
+       /* If the file isn't already open, it wasn't found - send back a 404 error response and abort */
+       if (!(AppState->HTTPServer.FileOpen))
+       {
+               /* Copy over the HTTP 404 response header and send it to the receiving client */
+               strcpy_P(AppData, HTTP404Header);
+               strcpy(&AppData[strlen(AppData)], AppState->HTTPServer.FileName);
+               uip_send(AppData, strlen(AppData));
+
+               AppState->HTTPServer.NextState = WEBSERVER_STATE_Closing;
+               return;
+       }
+
+       /* Copy over the HTTP 200 response header and send it to the receiving client */
+       strcpy_P(AppData, HTTP200Header);
+
+       /* Check to see if a MIME type for the requested file's extension was found */
+       if (Extension != NULL)
+       {
+               /* Look through the MIME type list, copy over the required MIME type if found */
+               for (uint8_t i = 0; i < (sizeof(MIMETypes) / sizeof(MIMETypes[0])); i++)
+               {
+                       if (strcmp(&Extension[1], MIMETypes[i].Extension) == 0)
+                       {
+                               strcpy(&AppData[strlen(AppData)], MIMETypes[i].MIMEType);
+                               FoundMIMEType = true;
+                               break;
+                       }
+               }
+       }
+
+       /* Check if a MIME type was found and copied to the output buffer */
+       if (!(FoundMIMEType))
+       {
+               /* MIME type not found - copy over the default MIME type */
+               strcpy_P(&AppData[strlen(AppData)], DefaultMIMEType);
+       }
+
+       /* Add the end-of-line terminator and end-of-headers terminator after the MIME type */
+       strcpy_P(&AppData[strlen(AppData)], PSTR("\r\n\r\n"));
+
+       /* Send the MIME header to the receiving client */
+       uip_send(AppData, strlen(AppData));
+
+       /* When the MIME header is ACKed, progress to the data send stage */
+       AppState->HTTPServer.NextState = WEBSERVER_STATE_SendData;
+}
+
+/** HTTP Server State handler for the Data Send state. This state manages the transmission of file chunks
+ *  to the receiving HTTP client.
+ */
+static void HTTPServerApp_SendData(void)
+{
+       uip_tcp_appstate_t* const AppState    = &uip_conn->appstate;
+       char*               const AppData     = (char*)uip_appdata;
+
+       /* Get the maximum segment size for the current packet */
+       uint16_t MaxChunkSize = uip_mss();
+
+       /* Read the next chunk of data from the open file */
+       f_read(&AppState->HTTPServer.FileHandle, AppData, MaxChunkSize, &AppState->HTTPServer.SentChunkSize);
+
+       /* Send the next file chunk to the receiving client */
+       uip_send(AppData, AppState->HTTPServer.SentChunkSize);
+
+       /* Check if we are at the last chunk of the file, if so next ACK should close the connection */
+       if (MaxChunkSize != AppState->HTTPServer.SentChunkSize)
+         AppState->HTTPServer.NextState = WEBSERVER_STATE_Closing;
+}
+