/*********************************************************************
*
* HyperText Transfer Protocol (HTTP) Server
* Module for Microchip TCP/IP Stack
* -Serves dynamic pages to web browsers such as Microsoft Internet
* Explorer, Mozilla Firefox, etc.
* -Reference: RFC 2616
*
**********************************************************************
* FileName: HTTP2.c
* Dependencies: TCP, MPFS2, Tick, CustomHTTPApp.c callbacks
* Processor: PIC18, PIC24F, PIC24H, dsPIC30F, dsPIC33F, PIC32
* Compiler: Microchip C32 v1.05 or higher
* Microchip C30 v3.12 or higher
* Microchip C18 v3.30 or higher
* HI-TECH PICC-18 PRO 9.63PL2 or higher
* Company: Microchip Technology, Inc.
*
* Software License Agreement
*
* Copyright (C) 2002-2009 Microchip Technology Inc. All rights
* reserved.
*
* Microchip licenses to you the right to use, modify, copy, and
* distribute:
* (i) the Software when embedded on a Microchip microcontroller or
* digital signal controller product ("Device") which is
* integrated into Licensee's product; or
* (ii) ONLY the Software driver source files ENC28J60.c, ENC28J60.h,
* ENCX24J600.c and ENCX24J600.h ported to a non-Microchip device
* used in conjunction with a Microchip ethernet controller for
* the sole purpose of interfacing with the ethernet controller.
*
* You should refer to the license agreement accompanying this
* Software for additional information regarding your rights and
* obligations.
*
* THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT
* WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
* LIMITATION, ANY WARRANTY OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL
* MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF
* PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS
* BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE
* THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER
* SIMILAR COSTS, WHETHER ASSERTED ON THE BASIS OF CONTRACT, TORT
* (INCLUDING NEGLIGENCE), BREACH OF WARRANTY, OR OTHERWISE.
*
*
* Author Date Comment
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Nilesh Rajbharti 8/14/01 Original
* Elliott Wood 6/4/07 Complete rewrite, known as HTTP2
********************************************************************/
#define __HTTP2_C
#include "TCPIP Stack/TCPIP.h"
#if defined(STACK_USE_HTTP2_SERVER)
#include "HTTPPrint.h"
/****************************************************************************
Section:
String Constants
***************************************************************************/
static ROM BYTE HTTP_CRLF[] = "\r\n"; // New line sequence
#define HTTP_CRLF_LEN 2 // Length of above string
/****************************************************************************
Section:
File and Content Type Settings
***************************************************************************/
// File type extensions corresponding to HTTP_FILE_TYPE
static ROM char * ROM httpFileExtensions[HTTP_UNKNOWN+1] =
{
"txt", // HTTP_TXT
"htm", // HTTP_HTM
"html", // HTTP_HTML
"cgi", // HTTP_CGI
"xml", // HTTP_XML
"css", // HTTP_CSS
"gif", // HTTP_GIF
"png", // HTTP_PNG
"jpg", // HTTP_JPG
"cla", // HTTP_JAVA
"wav", // HTTP_WAV
"\0\0\0" // HTTP_UNKNOWN
};
// Content-type strings corresponding to HTTP_FILE_TYPE
static ROM char * ROM httpContentTypes[HTTP_UNKNOWN+1] =
{
"text/plain", // HTTP_TXT
"text/html", // HTTP_HTM
"text/html", // HTTP_HTML
"text/html", // HTTP_CGI
"text/xml", // HTTP_XML
"text/css", // HTTP_CSS
"image/gif", // HTTP_GIF
"image/png", // HTTP_PNG
"image/jpeg", // HTTP_JPG
"application/java-vm", // HTTP_JAVA
"audio/x-wave", // HTTP_WAV
"" // HTTP_UNKNOWN
};
/****************************************************************************
Section:
Commands and Server Responses
***************************************************************************/
// Initial response strings (Corresponding to HTTP_STATUS)
static ROM char * ROM HTTPResponseHeaders[] =
{
"HTTP/1.1 200 OK\r\nConnection: close\r\n",
"HTTP/1.1 200 OK\r\nConnection: close\r\n",
"HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n400 Bad Request: can't handle Content-Length\r\n",
"HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"Protected\"\r\nConnection: close\r\n\r\n401 Unauthorized: Password required\r\n",
#if defined(HTTP_MPFS_UPLOAD)
"HTTP/1.1 404 Not found\r\nConnection: close\r\nContent-Type: text/html\r\n\r\n404: File not found<br>Use <a href=\"/" HTTP_MPFS_UPLOAD "\">MPFS Upload</a> to program web pages\r\n",
#else
"HTTP/1.1 404 Not found\r\nConnection: close\r\n\r\n404: File not found\r\n",
#endif
"HTTP/1.1 414 Request-URI Too Long\r\nConnection: close\r\n\r\n414 Request-URI Too Long: Buffer overflow detected\r\n",
"HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n500 Internal Server Error: Expected data not present\r\n",
"HTTP/1.1 501 Not Implemented\r\nConnection: close\r\n\r\n501 Not Implemented: Only GET and POST supported\r\n",
#if defined(HTTP_MPFS_UPLOAD)
"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/html\r\n\r\n<html><body style=\"margin:100px\"><form method=post action=\"/" HTTP_MPFS_UPLOAD "\" enctype=\"multipart/form-data\"><b>MPFS Image Upload</b><p><input type=file name=i size=40> <input type=submit value=\"Upload\"></form></body></html>",
"",
"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/html\r\n\r\n<html><body style=\"margin:100px\"><b>MPFS Update Successful</b><p><a href=\"/\">Site main page</a></body></html>",
"HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\nContent-Type: text/html\r\n\r\n<html><body style=\"margin:100px\"><b>MPFS Image Corrupt or Wrong Version</b><p><a href=\"/" HTTP_MPFS_UPLOAD "\">Try again?</a></body></html>",
#endif
"HTTP/1.1 302 Found\r\nConnection: close\r\nLocation: ",
"HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n403 Forbidden: SSL Required - use HTTPS\r\n"
};
/****************************************************************************
Section:
Header Parsing Configuration
***************************************************************************/
// Header strings for which we'd like to parse
static ROM char * ROM HTTPRequestHeaders[] =
{
"Cookie:",
"Authorization:",
"Content-Length:"
};
// Set to length of longest string above
#define HTTP_MAX_HEADER_LEN (15u)
/****************************************************************************
Section:
HTTP Connection State Global Variables
***************************************************************************/
#if defined(__18CXX) && !defined(HI_TECH_C)
#pragma udata HTTP_CONNECTION_STATES
#endif
HTTP_CONN curHTTP; // Current HTTP connection state
HTTP_STUB httpStubs[MAX_HTTP_CONNECTIONS]; // HTTP stubs with state machine and socket
BYTE curHTTPID; // ID of the currently loaded HTTP_CONN
#if defined(__18CXX) && !defined(HI_TECH_C)
#pragma udata
#endif
/****************************************************************************
Section:
Function Prototypes
***************************************************************************/
static void HTTPHeaderParseLookup(BYTE i);
#if defined(HTTP_USE_COOKIES)
static void HTTPHeaderParseCookie(void);
#endif
#if defined(HTTP_USE_AUTHENTICATION)
static void HTTPHeaderParseAuthorization(void);
#endif
#if defined(HTTP_USE_POST)
static void HTTPHeaderParseContentLength(void);
static HTTP_READ_STATUS HTTPReadTo(BYTE delim, BYTE* buf, WORD len);
#endif
static void HTTPProcess(void);
static BOOL HTTPSendFile(void);
static void HTTPLoadConn(BYTE hHTTP);
#if defined(HTTP_MPFS_UPLOAD)
static HTTP_IO_RESULT HTTPMPFSUpload(void);
#endif
#define mMIN(a, b) ((a<b)?a:b)
#define smHTTP httpStubs[curHTTPID].sm // Access the current state machine
/*****************************************************************************
Function:
void HTTPInit(void)
Summary:
Initializes the HTTP server module.
Description:
Sets all HTTP sockets to the listening state, and initializes the
state machine and file handles for each connection. If SSL is
enabled, opens a socket on that port as well.
Precondition:
TCP must already be initialized.
Parameters:
None
Returns:
None
Remarks:
This function is called only one during lifetime of the application.
***************************************************************************/
void HTTPInit(void)
{
PTR_BASE oldPtr;
// Make sure the file handles are invalidated
curHTTP.file = MPFS_INVALID_HANDLE;
curHTTP.offsets = MPFS_INVALID_HANDLE;
for(curHTTPID = 0; curHTTPID < MAX_HTTP_CONNECTIONS; curHTTPID++)
{
smHTTP = SM_HTTP_IDLE;
sktHTTP = TCPOpen(0, TCP_OPEN_SERVER, HTTP_PORT, TCP_PURPOSE_HTTP_SERVER);
#if defined(STACK_USE_SSL_SERVER)
TCPAddSSLListener(sktHTTP, HTTPS_PORT);
#endif
// Save the default record (just invalid file handles)
oldPtr = MACSetWritePtr(BASE_HTTPB_ADDR + curHTTPID*sizeof(HTTP_CONN));
MACPutArray((BYTE*)&curHTTP, sizeof(HTTP_CONN));
MACSetWritePtr(oldPtr);
}
// Set curHTTPID to zero so that first call to HTTPLoadConn() doesn't write
// dummy data outside reserved HTTP memory.
curHTTPID = 0;
}
/*****************************************************************************
Function:
void HTTPServer(void)
Summary:
Performs periodic tasks for the HTTP2 module.
Description:
Browses through each open connection and attempts to process any
pending operations.
Precondition:
HTTPInit() must already be called.
Parameters:
None
Returns:
None
Remarks:
This function acts as a task (similar to one in an RTOS). It
performs its task in a co-operative manner, and the main application
must call this function repeatedly to ensure that all open or new
connections are served in a timely fashion.
***************************************************************************/
void HTTPServer(void)
{
BYTE conn;
for(conn = 0; conn < MAX_HTTP_CONNECTIONS; conn++)
{
if(httpStubs[conn].socket == INVALID_SOCKET)
continue;
// If a socket is disconnected at any time
// forget about it and return to idle state.
// Must do this here, otherwise we will wait until a new
// connection arrives, which causes problems with Linux and with SSL
if(TCPWasReset(httpStubs[conn].socket))
{
HTTPLoadConn(conn);
smHTTP = SM_HTTP_IDLE;
// Make sure any opened files are closed
if(curHTTP.file != MPFS_INVALID_HANDLE)
{
MPFSClose(curHTTP.file);
curHTTP.file = MPFS_INVALID_HANDLE;
}
if(curHTTP.offsets != MPFS_INVALID_HANDLE)
{
MPFSClose(curHTTP.offsets);
curHTTP.offsets = MPFS_INVALID_HANDLE;
}
// Adjust FIFO sizes to half and half. Default state must remain
// here so that SSL handshakes, if required, can proceed
TCPAdjustFIFOSize(sktHTTP, 1, 0, TCP_ADJUST_PRESERVE_RX);
}
// Determine if this connection is eligible for processing
if(httpStubs[conn].sm != SM_HTTP_IDLE || TCPIsGetReady(httpStubs[conn].socket))
{
HTTPLoadConn(conn);
HTTPProcess();
}
}
}
/*****************************************************************************
Function:
static void HTTPLoadConn(BYTE hHTTP)
Summary:
Switches the currently loaded connection for the HTTP2 module.
Description:
Saves the currently loaded HTTP connection back to Ethernet buffer
RAM, then loads the selected connection into curHTTP in local RAM
for processing.
Precondition:
None
Parameters:
hHTTP - the connection ID to load
Returns:
None
***************************************************************************/
static void HTTPLoadConn(BYTE hHTTP)
{
WORD oldPtr;
// Return if already loaded
if(hHTTP == curHTTPID)
return;
// Save the old one
oldPtr = MACSetWritePtr(BASE_HTTPB_ADDR + curHTTPID*sizeof(HTTP_CONN));
MACPutArray((BYTE*)&curHTTP, sizeof(HTTP_CONN));
MACSetWritePtr(oldPtr);
// Load the new one
oldPtr = MACSetReadPtr(BASE_HTTPB_ADDR + hHTTP*sizeof(HTTP_CONN));
MACGetArray((BYTE*)&curHTTP, sizeof(HTTP_CONN));
MACSetReadPtr(oldPtr);
// Remember which one is loaded
curHTTPID = hHTTP;
}
/*****************************************************************************
Function:
static void HTTPProcess(void)
Description:
Performs any pending operations for the currently loaded HTTP connection.
Precondition:
HTTPInit() and HTTPLoadConn() have been called.
Parameters:
None
Returns:
None
***************************************************************************/
static void HTTPProcess(void)
{
WORD lenA, lenB;
BYTE c, i;
BOOL isDone;
BYTE *ext;
BYTE buffer[HTTP_MAX_HEADER_LEN+1];
do
{
isDone = TRUE;
switch(smHTTP)
{
case SM_HTTP_IDLE:
// Check how much data is waiting
lenA = TCPIsGetReady(sktHTTP);
// If a connection has been made, then process the request
if(lenA)
{// Clear out state info and move to next state
curHTTP.ptrData = curHTTP.data;
smHTTP = SM_HTTP_PARSE_REQUEST;
curHTTP.isAuthorized = 0xff;
curHTTP.hasArgs = FALSE;
curHTTP.callbackID = TickGet() + HTTP_TIMEOUT*TICK_SECOND;
curHTTP.callbackPos = 0xffffffff;
curHTTP.byteCount = 0;
#if defined(HTTP_USE_POST)
curHTTP.smPost = 0x00;
#endif
// Adjust the TCP FIFOs for optimal reception of
// the next HTTP request from the browser
TCPAdjustFIFOSize(sktHTTP, 1, 0, TCP_ADJUST_PRESERVE_RX | TCP_ADJUST_GIVE_REST_TO_RX);
}
else
// Don't break for new connections. There may be
// an entire request in the buffer already.
break;
case SM_HTTP_PARSE_REQUEST:
// Verify the entire first line is in the FIFO
if(TCPFind(sktHTTP, '\n', 0, FALSE) == 0xffff)
{// First line isn't here yet
if(TCPGetRxFIFOFree(sktHTTP) == 0u)
{// If the FIFO is full, we overflowed
curHTTP.httpStatus = HTTP_OVERFLOW;
smHTTP = SM_HTTP_SERVE_HEADERS;
isDone = FALSE;
}
if((LONG)(TickGet() - curHTTP.callbackID) > (LONG)0)
{// A timeout has occurred
TCPDisconnect(sktHTTP);
smHTTP = SM_HTTP_DISCONNECT;
isDone = FALSE;
}
break;
}
// Reset the watchdog timer
curHTTP.callbackID = TickGet() + HTTP_TIMEOUT*TICK_SECOND;
// Determine the request method
lenA = TCPFind(sktHTTP, ' ', 0, FALSE);
if(lenA > 5u)
lenA = 5;
TCPGetArray(sktHTTP, curHTTP.data, lenA+1);
if ( memcmppgm2ram(curHTTP.data, (ROM void*)"GET", 3) == 0)
curHTTP.httpStatus = HTTP_GET;
#if defined(HTTP_USE_POST)
else if ( memcmppgm2ram(curHTTP.data, (ROM void*)"POST", 4) == 0)
curHTTP.httpStatus = HTTP_POST;
#endif
else
{// Unrecognized method, so return not implemented
curHTTP.httpStatus = HTTP_NOT_IMPLEMENTED;
smHTTP = SM_HTTP_SERVE_HEADERS;
isDone = FALSE;
break;
}
// Find end of filename
lenA = TCPFind(sktHTTP, ' ', 0, FALSE);
lenB = TCPFindEx(sktHTTP, '?', 0, lenA, FALSE);
lenA = mMIN(lenA, lenB);
// If the file name is too long, then reject the request
if(lenA > HTTP_MAX_DATA_LEN - HTTP_DEFAULT_LEN - 1)
{
curHTTP.httpStatus = HTTP_OVERFLOW;
smHTTP = SM_HTTP_SERVE_HEADERS;
isDone = FALSE;
break;
}
// Read in the filename and decode
lenB = TCPGetArray(sktHTTP, curHTTP.data, lenA);
curHTTP.data[lenB] = '\0';
HTTPURLDecode(curHTTP.data);
// Decode may have changed the string length - update it here
lenB = strlen((char*)curHTTP.data);
// Check if this is an MPFS Upload
#if defined(HTTP_MPFS_UPLOAD)
if(memcmppgm2ram(&curHTTP.data[1], HTTP_MPFS_UPLOAD, sizeof(HTTP_MPFS_UPLOAD)) == 0)
{// Read remainder of line, and bypass all file opening, etc.
#if defined(HTTP_USE_AUTHENTICATION)
curHTTP.isAuthorized = HTTPNeedsAuth(&curHTTP.data[1]);
#endif
if(curHTTP.httpStatus == HTTP_GET)
curHTTP.httpStatus = HTTP_MPFS_FORM;
else
curHTTP.httpStatus = HTTP_MPFS_UP;
smHTTP = SM_HTTP_PARSE_HEADERS;
isDone = FALSE;
break;
}
#endif
// If the last character is a not a directory delimiter, then try to open the file
// String starts at 2nd character, because the first is always a '/'
if(curHTTP.data[lenB-1] != '/')
curHTTP.file = MPFSOpen(&curHTTP.data[1]);
// If the open fails, then add our default name and try again
if(curHTTP.file == MPFS_INVALID_HANDLE)
{
// Add the directory delimiter if needed
if(curHTTP.data[lenB-1] != '/')
curHTTP.data[lenB++] = '/';
// Add our default file name
#if defined(STACK_USE_SSL_SERVER)
if(TCPIsSSL(sktHTTP))
{
strcpypgm2ram((void*)&curHTTP.data[lenB], HTTPS_DEFAULT_FILE);
lenB += strlenpgm(HTTPS_DEFAULT_FILE);
}
else
#endif
{
strcpypgm2ram((void*)&curHTTP.data[lenB], HTTP_DEFAULT_FILE);
lenB += strlenpgm(HTTP_DEFAULT_FILE);
}
// Try to open again
curHTTP.file = MPFSOpen(&curHTTP.data[1]);
}
// Find the extension in the filename
for(ext = curHTTP.data + lenB-1; ext != curHTTP.data; ext--)
if(*ext == '.')
break;
// Compare to known extensions to determine Content-Type
ext++;
for(curHTTP.fileType = HTTP_TXT; curHTTP.fileType < HTTP_UNKNOWN; curHTTP.fileType++)
if(!stricmppgm2ram(ext, (ROM void*)httpFileExtensions[curHTTP.fileType]))
break;
// Perform first round authentication (pass file name only)
#if defined(HTTP_USE_AUTHENTICATION)
curHTTP.isAuthorized = HTTPNeedsAuth(&curHTTP.data[1]);
#endif
// If the file was found, see if it has an index
if(curHTTP.file != MPFS_INVALID_HANDLE &&
(MPFSGetFlags(curHTTP.file) & MPFS2_FLAG_HASINDEX) )
{
curHTTP.offsets = MPFSOpenID(MPFSGetID(curHTTP.file) + 1);
}
// Read GET args, up to buffer size - 1
lenA = TCPFind(sktHTTP, ' ', 0, FALSE);
if(lenA != 0u)
{
curHTTP.hasArgs = TRUE;
// Trash the '?'
TCPGet(sktHTTP, &c);
// Verify there's enough space
lenA--;
if(lenA >= HTTP_MAX_DATA_LEN - 2)
{
curHTTP.httpStatus = HTTP_OVERFLOW;
smHTTP = SM_HTTP_SERVE_HEADERS;
isDone = FALSE;
break;
}
// Read in the arguments and '&'-terminate in anticipation of cookies
curHTTP.ptrData += TCPGetArray(sktHTTP, curHTTP.data, lenA);
*(curHTTP.ptrData++) = '&';
}
// Clear the rest of the line
lenA = TCPFind(sktHTTP, '\n', 0, FALSE);
TCPGetArray(sktHTTP, NULL, lenA + 1);
// Move to parsing the headers
smHTTP = SM_HTTP_PARSE_HEADERS;
// No break, continue to parsing headers
case SM_HTTP_PARSE_HEADERS:
// Loop over all the headers
while(1)
{
// Make sure entire line is in the FIFO
lenA = TCPFind(sktHTTP, '\n', 0, FALSE);
if(lenA == 0xffff)
{// If not, make sure we can receive more data
if(TCPGetRxFIFOFree(sktHTTP) == 0u)
{// Overflow
curHTTP.httpStatus = HTTP_OVERFLOW;
smHTTP = SM_HTTP_SERVE_HEADERS;
isDone = FALSE;
}
if((LONG)(TickGet() - curHTTP.callbackID) > (LONG)0)
{// A timeout has occured
TCPDisconnect(sktHTTP);
smHTTP = SM_HTTP_DISCONNECT;
isDone = FALSE;
}
break;
}
// Reset the watchdog timer
curHTTP.callbackID = TickGet() + HTTP_TIMEOUT*TICK_SECOND;
// If a CRLF is immediate, then headers are done
if(lenA == 1u)
{// Remove the CRLF and move to next state
TCPGetArray(sktHTTP, NULL, 2);
smHTTP = SM_HTTP_AUTHENTICATE;
isDone = FALSE;
break;
}
// Find the header name, and use isDone as a flag to indicate a match
lenB = TCPFindEx(sktHTTP, ':', 0, lenA, FALSE) + 2;
isDone = FALSE;
// If name is too long or this line isn't a header, ignore it
if(lenB > sizeof(buffer))
{
TCPGetArray(sktHTTP, NULL, lenA+1);
continue;
}
// Read in the header name
TCPGetArray(sktHTTP, buffer, lenB);
buffer[lenB-1] = '\0';
lenA -= lenB;
// Compare header read to ones we're interested in
for(i = 0; i < sizeof(HTTPRequestHeaders)/sizeof(HTTPRequestHeaders[0]); i++)
{
if(strcmppgm2ram((char*)buffer, (ROM char *)HTTPRequestHeaders[i]) == 0)
{// Parse the header and stop the loop
HTTPHeaderParseLookup(i);
isDone = TRUE;
break;
}
}
// Clear the rest of the line, and call the loop again
if(isDone)
{// We already know how much to remove unless a header was found
lenA = TCPFind(sktHTTP, '\n', 0, FALSE);
}
TCPGetArray(sktHTTP, NULL, lenA+1);
}
break;
case SM_HTTP_AUTHENTICATE:
#if defined(HTTP_USE_AUTHENTICATION)
// Check current authorization state
if(curHTTP.isAuthorized < 0x80)
{// 401 error
curHTTP.httpStatus = HTTP_UNAUTHORIZED;
smHTTP = SM_HTTP_SERVE_HEADERS;
isDone = FALSE;
#if defined(HTTP_NO_AUTH_WITHOUT_SSL)
if(!TCPIsSSL(sktHTTP))
curHTTP.httpStatus = HTTP_SSL_REQUIRED;
#endif
break;
}
#endif
// Parse the args string
*curHTTP.ptrData = '\0';
curHTTP.ptrData = HTTPURLDecode(curHTTP.data);
// If this is an MPFS upload form request, bypass to headers
#if defined(HTTP_MPFS_UPLOAD)
if(curHTTP.httpStatus == HTTP_MPFS_FORM)
{
smHTTP = SM_HTTP_SERVE_HEADERS;
isDone = FALSE;
break;
}
#endif
// Move on to GET args, unless there are none
smHTTP = SM_HTTP_PROCESS_GET;
if(!curHTTP.hasArgs)
smHTTP = SM_HTTP_PROCESS_POST;
isDone = FALSE;
curHTTP.hasArgs = FALSE;
break;
case SM_HTTP_PROCESS_GET:
// Run the application callback HTTPExecuteGet()
if(HTTPExecuteGet() == HTTP_IO_WAITING)
{// If waiting for asynchronous process, return to main app
break;
}
// Move on to POST data
smHTTP = SM_HTTP_PROCESS_POST;
case SM_HTTP_PROCESS_POST:
#if defined(HTTP_USE_POST)
// See if we have any new data
if(TCPIsGetReady(sktHTTP) == curHTTP.callbackPos)
{
if((LONG)(TickGet() - curHTTP.callbackID) > (LONG)0)
{// If a timeout has occured, disconnect
TCPDisconnect(sktHTTP);
smHTTP = SM_HTTP_DISCONNECT;
isDone = FALSE;
break;
}
}
if(curHTTP.httpStatus == HTTP_POST
#if defined(HTTP_MPFS_UPLOAD)
|| (curHTTP.httpStatus >= HTTP_MPFS_UP && curHTTP.httpStatus <= HTTP_MPFS_ERROR)
#endif
)
{
// Run the application callback HTTPExecutePost()
#if defined(HTTP_MPFS_UPLOAD)
if(curHTTP.httpStatus >= HTTP_MPFS_UP && curHTTP.httpStatus <= HTTP_MPFS_ERROR)
{
c = HTTPMPFSUpload();
if(c == (BYTE)HTTP_IO_DONE)
{
smHTTP = SM_HTTP_SERVE_HEADERS;
isDone = FALSE;
break;
}
}
else
#endif
c = HTTPExecutePost();
// If waiting for asynchronous process, return to main app
if(c == (BYTE)HTTP_IO_WAITING)
{// return to main app and make sure we don't get stuck by the watchdog
curHTTP.callbackPos = TCPIsGetReady(sktHTTP) - 1;
break;
}
else if(c == (BYTE)HTTP_IO_NEED_DATA)
{// If waiting for more data
curHTTP.callbackPos = TCPIsGetReady(sktHTTP);
curHTTP.callbackID = TickGet() + HTTP_TIMEOUT*TICK_SECOND;
// If more is expected and space is available, return to main app
if(curHTTP.byteCount > curHTTP.callbackPos && TCPGetRxFIFOFree(sktHTTP) != 0u)
break;
// Handle cases where application ran out of data or buffer space
curHTTP.httpStatus = HTTP_INTERNAL_SERVER_ERROR;
smHTTP = SM_HTTP_SERVE_HEADERS;
isDone = FALSE;
break;
}
}
#endif
// We're done with POST
smHTTP = SM_HTTP_PROCESS_REQUEST;
// No break, continue to sending request
case SM_HTTP_PROCESS_REQUEST:
// Check for 404
if(curHTTP.file == MPFS_INVALID_HANDLE)
{
curHTTP.httpStatus = HTTP_NOT_FOUND;
smHTTP = SM_HTTP_SERVE_HEADERS;
isDone = FALSE;
break;
}
// Set up the dynamic substitutions
curHTTP.byteCount = 0;
if(curHTTP.offsets == MPFS_INVALID_HANDLE)
{// If no index file, then set next offset to huge
curHTTP.nextCallback = 0xffffffff;
}
else
{// Read in the next callback index
MPFSGetLong(curHTTP.offsets, &(curHTTP.nextCallback));
}
// Move to next state
smHTTP = SM_HTTP_SERVE_HEADERS;
case SM_HTTP_SERVE_HEADERS:
// We're in write mode now:
// Adjust the TCP FIFOs for optimal transmission of
// the HTTP response to the browser
TCPAdjustFIFOSize(sktHTTP, 1, 0, TCP_ADJUST_GIVE_REST_TO_TX);
// Send headers
TCPPutROMString(sktHTTP, (ROM BYTE*)HTTPResponseHeaders[curHTTP.httpStatus]);
// If this is a redirect, print the rest of the Location: header
if(curHTTP.httpStatus == HTTP_REDIRECT)
{
TCPPutString(sktHTTP, curHTTP.data);
TCPPutROMString(sktHTTP, (ROM BYTE*)"\r\n\r\n304 Redirect: ");
TCPPutString(sktHTTP, curHTTP.data);
TCPPutROMString(sktHTTP, (ROM BYTE*)HTTP_CRLF);
}
// If not GET or POST, we're done
if(curHTTP.httpStatus != HTTP_GET && curHTTP.httpStatus != HTTP_POST)
{// Disconnect
smHTTP = SM_HTTP_DISCONNECT;
break;
}
// Output the content type, if known
if(curHTTP.fileType != HTTP_UNKNOWN)
{
TCPPutROMString(sktHTTP, (ROM BYTE*)"Content-Type: ");
TCPPutROMString(sktHTTP, (ROM BYTE*)httpContentTypes[curHTTP.fileType]);
TCPPutROMString(sktHTTP, HTTP_CRLF);
}
// Output the gzip encoding header if needed
if(MPFSGetFlags(curHTTP.file) & MPFS2_FLAG_ISZIPPED)
{
TCPPutROMString(sktHTTP, (ROM BYTE*)"Content-Encoding: gzip\r\n");
}
// Output the cache-control
TCPPutROMString(sktHTTP, (ROM BYTE*)"Cache-Control: ");
if(curHTTP.httpStatus == HTTP_POST || curHTTP.nextCallback != 0xffffffff)
{// This is a dynamic page or a POST request, so no cache
TCPPutROMString(sktHTTP, (ROM BYTE*)"no-cache");
}
else
{// This is a static page, so save it for the specified amount of time
TCPPutROMString(sktHTTP, (ROM BYTE*)"max-age=");
TCPPutROMString(sktHTTP, (ROM BYTE*)HTTP_CACHE_LEN);
}
TCPPutROMString(sktHTTP, HTTP_CRLF);
// Check if we should output cookies
if(curHTTP.hasArgs)
smHTTP = SM_HTTP_SERVE_COOKIES;
else
{// Terminate the headers
TCPPutROMString(sktHTTP, HTTP_CRLF);
smHTTP = SM_HTTP_SERVE_BODY;
}
// Move to next stage
isDone = FALSE;
break;
case SM_HTTP_SERVE_COOKIES:
#if defined(HTTP_USE_COOKIES)
// If the TX FIFO runs out of space, the client will never get CRLFCRLF
// Avoid writing huge cookies - keep it under a hundred bytes max
// Write cookies one at a time as space permits
for(curHTTP.ptrRead = curHTTP.data; curHTTP.hasArgs != 0u; curHTTP.hasArgs--)
{
// Write the header
TCPPutROMString(sktHTTP, (ROM BYTE*)"Set-Cookie: ");
// Write the name, URL encoded, one character at a time
while((c = *(curHTTP.ptrRead++)))
{
if(c == ' ')
TCPPut(sktHTTP, '+');
else if(c < '0' || (c > '9' && c < 'A') || (c > 'Z' && c < 'a') || c > 'z')
{
TCPPut(sktHTTP, '%');
TCPPut(sktHTTP, btohexa_high(c));
TCPPut(sktHTTP, btohexa_low(c));
}
else
TCPPut(sktHTTP, c);
}
TCPPut(sktHTTP, '=');
// Write the value, URL encoded, one character at a time
while((c = *(curHTTP.ptrRead++)))
{
if(c == ' ')
TCPPut(sktHTTP, '+');
else if(c < '0' || (c > '9' && c < 'A') || (c > 'Z' && c < 'a') || c > 'z')
{
TCPPut(sktHTTP, '%');
TCPPut(sktHTTP, btohexa_high(c));
TCPPut(sktHTTP, btohexa_low(c));
}
else
TCPPut(sktHTTP, c);
}
// Finish the line
TCPPutROMString(sktHTTP, HTTP_CRLF);
}
#endif
// We're done, move to next state
TCPPutROMString(sktHTTP, HTTP_CRLF);
smHTTP = SM_HTTP_SERVE_BODY;
case SM_HTTP_SERVE_BODY:
isDone = FALSE;
// Try to send next packet
if(HTTPSendFile())
{// If EOF, then we're done so close and disconnect
MPFSClose(curHTTP.file);
curHTTP.file = MPFS_INVALID_HANDLE;
smHTTP = SM_HTTP_DISCONNECT;
isDone = TRUE;
}
// If the TX FIFO is full, then return to main app loop
if(TCPIsPutReady(sktHTTP) == 0u)
isDone = TRUE;
break;
case SM_HTTP_SEND_FROM_CALLBACK:
isDone = TRUE;
// Check that at least the minimum bytes are free
if(TCPIsPutReady(sktHTTP) < HTTP_MIN_CALLBACK_FREE)
break;
// Fill TX FIFO from callback
HTTPPrint(curHTTP.callbackID);
if(curHTTP.callbackPos == 0u)
{// Callback finished its output, so move on
isDone = FALSE;
smHTTP = SM_HTTP_SERVE_BODY;
}// Otherwise, callback needs more buffer space, so return and wait
break;
case SM_HTTP_DISCONNECT:
// Make sure any opened files are closed
if(curHTTP.file != MPFS_INVALID_HANDLE)
{
MPFSClose(curHTTP.file);
curHTTP.file = MPFS_INVALID_HANDLE;
}
if(curHTTP.offsets != MPFS_INVALID_HANDLE)
{
MPFSClose(curHTTP.offsets);
curHTTP.offsets = MPFS_INVALID_HANDLE;
}
TCPDisconnect(sktHTTP);
smHTTP = SM_HTTP_IDLE;
break;
}
} while(!isDone);
}
/*****************************************************************************
Function:
static BOOL HTTPSendFile(void)
Description:
Serves up the next chunk of curHTTP's file, up to a) available TX FIFO
space or b) the next callback index, whichever comes first.
Precondition:
curHTTP.file and curHTTP.offsets have both been opened for reading.
Parameters:
None
Return Values:
TRUE - the end of the file was reached and reading is done
FALSE - more data remains to be read
***************************************************************************/
static BOOL HTTPSendFile(void)
{
WORD numBytes, len;
BYTE c, data[64];
// Determine how many bytes we can read right now
len = TCPIsPutReady(sktHTTP);
numBytes = mMIN(len, curHTTP.nextCallback - curHTTP.byteCount);
// Get/put as many bytes as possible
curHTTP.byteCount += numBytes;
while(numBytes > 0u)
{
len = MPFSGetArray(curHTTP.file, data, mMIN(numBytes, 64u));
if(len == 0u)
return TRUE;
else
TCPPutArray(sktHTTP, data, len);
numBytes -= len;
}
// Check if a callback index was reached
if(curHTTP.byteCount == curHTTP.nextCallback)
{
// Update the state machine
smHTTP = SM_HTTP_SEND_FROM_CALLBACK;
curHTTP.callbackPos = 0;
// Read past the variable name and close the MPFS
MPFSGet(curHTTP.file, NULL);
do
{
if(!MPFSGet(curHTTP.file, &c))
break;
curHTTP.byteCount++;
} while(c != '~');
curHTTP.byteCount++;
// Read in the callback address and next offset
MPFSGetLong(curHTTP.offsets, &(curHTTP.callbackID));
if(!MPFSGetLong(curHTTP.offsets, &(curHTTP.nextCallback)))
{
curHTTP.nextCallback = 0xffffffff;
MPFSClose(curHTTP.offsets);
curHTTP.offsets = MPFS_INVALID_HANDLE;
}
}
// We are not done sending a file yet...
return FALSE;
}
/*****************************************************************************
Function:
static void HTTPHeaderParseLookup(BYTE i)
Description:
Calls the appropriate header parser based on the index of the header
that was read from the request.
Precondition:
None
Parameters:
i - the index of the string found in HTTPRequestHeaders
Return Values:
TRUE - the end of the file was reached and reading is done
FALSE - more data remains to be read
***************************************************************************/
static void HTTPHeaderParseLookup(BYTE i)
{
// i corresponds to an index in HTTPRequestHeaders
#if defined(HTTP_USE_COOKIES)
if(i == 0u)
{
HTTPHeaderParseCookie();
return;
}
#endif
#if defined(HTTP_USE_AUTHENTICATION)
if(i == 1u)
{
HTTPHeaderParseAuthorization();
return;
}
#endif
#if defined(HTTP_USE_POST)
if(i == 2u)
{
HTTPHeaderParseContentLength();
return;
}
#endif
}
/*****************************************************************************
Function:
static void HTTPHeaderParseAuthorization(void)
Summary:
Parses the "Authorization:" header for a request and verifies the
credentials.
Description:
Parses the "Authorization:" header for a request. For example,
"BASIC YWRtaW46cGFzc3dvcmQ=" is decoded to a user name of "admin" and
a password of "password". Once read, HTTPCheckAuth is called from
CustomHTTPApp.c to determine if the credentials are acceptable.
The return value of HTTPCheckAuth is saved in curHTTP.isAuthorized for
later use by the application.
Precondition:
None
Parameters:
None
Returns:
None
Remarks:
This function is ony available when HTTP_USE_AUTHENTICATION is defined.
***************************************************************************/
#if defined(HTTP_USE_AUTHENTICATION)
static void HTTPHeaderParseAuthorization(void)
{
WORD len;
BYTE buf[40];
BYTE *ptrBuf;
// If auth processing is not required, return
if(curHTTP.isAuthorized & 0x80)
return;
// Clear the auth type ("BASIC ")
TCPGetArray(sktHTTP, NULL, 6);
// Find the terminating CRLF and make sure it's a multiple of four
len = TCPFindROMArray(sktHTTP, HTTP_CRLF, HTTP_CRLF_LEN, 0, FALSE);
len += 3;
len &= 0xfc;
len = mMIN(len, sizeof(buf)-4);
// Read in 4 bytes at a time and decode (slower, but saves RAM)
for(ptrBuf = buf; len > 0u; len-=4, ptrBuf+=3)
{
TCPGetArray(sktHTTP, ptrBuf, 4);
Base64Decode(ptrBuf, 4, ptrBuf, 3);
}
// Null terminate both, and make sure there's at least two terminators
*ptrBuf = '\0';
for(len = 0, ptrBuf = buf; len < sizeof(buf); len++, ptrBuf++)
if(*ptrBuf == ':')
break;
*(ptrBuf++) = '\0';
// Verify credentials
curHTTP.isAuthorized = HTTPCheckAuth(buf, ptrBuf);
return;
}
#endif
/*****************************************************************************
Function:
static void HTTPHeaderParseCookie(void)
Summary:
Parses the "Cookie:" headers for a request and stores them as GET
variables.
Description:
Parses the "Cookie:" headers for a request. For example,
"Cookie: name=Wile+E.+Coyote; order=ROCKET_LAUNCHER" is decoded to
"name=Wile+E.+Coyote&order=ROCKET_LAUNCHER&" and stored as any other
GET variable in curHTTP.data.
The user application can easily access these values later using the
HTTPGetArg() and HTTPGetROMArg() functions.
Precondition:
None
Parameters:
None
Returns:
None
Remarks:
This function is ony available when HTTP_USE_COOKIES is defined.
***************************************************************************/
#if defined(HTTP_USE_COOKIES)
static void HTTPHeaderParseCookie(void)
{
WORD lenA, lenB;
// Verify there's enough space
lenB = TCPFindROMArray(sktHTTP, HTTP_CRLF, HTTP_CRLF_LEN, 0, FALSE);
if(lenB >= (WORD)(curHTTP.data + HTTP_MAX_DATA_LEN - curHTTP.ptrData - 2))
{// If not, overflow
curHTTP.httpStatus = HTTP_OVERFLOW;
smHTTP = SM_HTTP_SERVE_HEADERS;
return;
}
// While a CRLF is not immediate, grab a cookie value
while(lenB != 0u)
{
// Look for a ';' and use the shorter of that or a CRLF
lenA = TCPFind(sktHTTP, ';', 0, FALSE);
// Read to the terminator
curHTTP.ptrData += TCPGetArray(sktHTTP, curHTTP.ptrData, mMIN(lenA, lenB));
// Insert an & to anticipate another cookie
*(curHTTP.ptrData++) = '&';
// If semicolon, trash it and whitespace
if(lenA < lenB)
{
TCPGet(sktHTTP, NULL);
while(TCPFind(sktHTTP, ' ', 0, FALSE) == 0u)
TCPGet(sktHTTP, NULL);
}
// Find the new distance to the CRLF
lenB = TCPFindROMArray(sktHTTP, HTTP_CRLF, HTTP_CRLF_LEN, 0, FALSE);
}
return;
}
#endif
/*****************************************************************************
Function:
static void HTTPHeaderParseContentLength(void)
Summary:
Parses the "Content-Length:" header for a request.
Description:
Parses the "Content-Length:" header to determine how many bytes of
POST data to expect after the request. This value is stored in
curHTTP.byteCount.
Precondition:
None
Parameters:
None
Returns:
None
Remarks:
This function is ony available when HTTP_USE_POST is defined.
***************************************************************************/
#if defined(HTTP_USE_POST)
static void HTTPHeaderParseContentLength(void)
{
WORD len;
BYTE buf[10];
// Read up to the CRLF (max 9 bytes or ~1GB)
len = TCPFindROMArray(sktHTTP, HTTP_CRLF, HTTP_CRLF_LEN, 0, FALSE);
if(len >= sizeof(buf))
{
curHTTP.httpStatus = HTTP_BAD_REQUEST;
curHTTP.byteCount = 0;
return;
}
len = TCPGetArray(sktHTTP, buf, len);
buf[len] = '\0';
curHTTP.byteCount = atol((char*)buf);
}
#endif
/*****************************************************************************
Function:
BYTE* HTTPURLDecode(BYTE* cData)
Summary:
Parses a string from URL encoding to plain-text.
Description:
Parses a string from URL encoding to plain-text. The following
conversions are made: = to \0, & to \0, + to , and
%xx to a single hex byte.
After completion, the data has been decoded and a null terminator
signifies the end of a name or value. A second null terminator (or a
null name parameter) indicates the end of all the data.
Precondition:
The data parameter is null terminated and has at least one extra
byte free.
Parameters:
cData - The string which is to be decoded in place.
Returns:
A pointer to the last null terminator in data, which is also the
first free byte for new data.
Remarks:
This function is called by the stack to parse GET arguments and
cookie data. User applications can use this function to decode POST
data, but first need to verify that the string is null-terminated.
***************************************************************************/
BYTE* HTTPURLDecode(BYTE* cData)
{
BYTE *pRead, *pWrite;
WORD wLen;
BYTE c;
WORD hex;
// Determine length of input
wLen = strlen((char*)cData);
// Read all characters in the string
for(pRead = pWrite = cData; wLen != 0u; )
{
c = *pRead++;
wLen--;
if(c == '=' || c == '&')
*pWrite++ = '\0';
else if(c == '+')
*pWrite++ = ' ';
else if(c == '%')
{
if(wLen < 2u)
wLen = 0;
else
{
((BYTE*)&hex)[1] = *pRead++;
((BYTE*)&hex)[0] = *pRead++;
wLen--;
wLen--;
*pWrite++ = hexatob(*((WORD_VAL*)&hex));
}
}
else
*pWrite++ = c;
}
// Double null terminate the last value
*pWrite++ = '\0';
*pWrite = '\0';
return pWrite;
}
/*****************************************************************************
Function:
BYTE* HTTPGetArg(BYTE* cData, BYTE* cArg)
Summary:
Locates a form field value in a given data array.
Description:
Searches through a data array to find the value associated with a
given argument. It can be used to find form field values in data
received over GET or POST.
The end of data is assumed to be reached when a null name parameter is
encountered. This requires the string to have an even number of
null-terminated strings, followed by an additional null terminator.
Precondition:
The data array has a valid series of null terminated name/value pairs.
Parameters:
data - the buffer to search
arg - the name of the argument to find
Returns:
A pointer to the argument value, or NULL if not found.
***************************************************************************/
BYTE* HTTPGetArg(BYTE* cData, BYTE* cArg)
{
// Search through the array while bytes remain
while(*cData != '\0')
{
// Look for arg at current position
if(!strcmp((char*)cArg, (char*)cData))
{// Found it, so return parameter
return cData + strlen((char*)cArg) + 1;
}
// Skip past two strings (NUL bytes)
cData += strlen((char*)cData) + 1;
cData += strlen((char*)cData) + 1;
}
// Return NULL if not found
return NULL;
}
/*****************************************************************************
Function:
BYTE* HTTPGetROMArg(BYTE* cData, ROM BYTE* cArg)
Summary:
Locates a form field value in a given data array.
Description:
Searches through a data array to find the value associated with a
given argument. It can be used to find form field values in data
received over GET or POST.
The end of data is assumed to be reached when a null name parameter is
encountered. This requires the string to have an even number of
null-terminated strings, followed by an additional null terminator.
Precondition:
The data array has a valid series of null terminated name/value pairs.
Parameters:
data - the buffer to search
arg - the name of the argument to find
Returns:
A pointer to the argument value, or NULL if not found.
Remarks:
This function is aliased to HTTPGetArg on non-PIC18 platforms.
***************************************************************************/
#if defined(__18CXX)
BYTE* HTTPGetROMArg(BYTE* cData, ROM BYTE* cArg)
{
// Search through the array while bytes remain
while(*cData != '\0')
{
// Look for arg at current position
if(!memcmppgm2ram(cData, (ROM void*)cArg, strlenpgm((ROM char*)cArg) + 1))
{// Found it, so skip to next string
return cData + strlenpgm((ROM char*)cArg) + 1;
}
// Skip past two strings (NUL bytes)
cData += strlen((char*)cData) + 1;
cData += strlen((char*)cData) + 1;
}
// Return NULL if not found
return NULL;
}
#endif
/*****************************************************************************
Function:
HTTP_READ_STATUS HTTPReadPostName(BYTE* cData, WORD wLen)
Summary:
Reads a name from a URL encoded string in the TCP buffer.
Description:
Reads a name from a URL encoded string in the TCP buffer. This function
is meant to be called from an HTTPExecutePost callback to facilitate
easier parsing of incoming data. This function also prevents buffer
overflows by forcing the programmer to indicate how many bytes are
expected. At least 2 extra bytes are needed in cData over the maximum
length of data expected to be read.
This function will read until the next '=' character, which indicates the
end of a name parameter. It assumes that the front of the buffer is
the beginning of the name paramter to be read.
This function properly updates curHTTP.byteCount by decrementing it
by the number of bytes read. It also removes the delimiting '=' from
the buffer.
Precondition:
Front of TCP buffer is the beginning of a name parameter, and the rest of
the TCP buffer contains a URL-encoded string with a name parameter
terminated by a '=' character.
Parameters:
cData - where to store the name once it is read
wLen - how many bytes can be written to cData
Return Values:
HTTP_READ_OK - name was successfully read
HTTP_READ_TRUNCTATED - entire name could not fit in the buffer, so the
value was truncated and data has been lost
HTTP_READ_INCOMPLETE - entire name was not yet in the buffer, so call
this function again later to retrieve
***************************************************************************/
#if defined(HTTP_USE_POST)
HTTP_READ_STATUS HTTPReadPostName(BYTE* cData, WORD wLen)
{
HTTP_READ_STATUS status;
status = HTTPReadTo('=', cData, wLen);
// Decode the data (if not reading to null or blank) and return
if(cData && *cData)
HTTPURLDecode(cData);
return status;
}
#endif
/*****************************************************************************
Function:
HTTP_READ_STATUS HTTPReadPostValue(BYTE* cData, WORD wLen)
Summary:
Reads a value from a URL encoded string in the TCP buffer.
Description:
Reads a value from a URL encoded string in the TCP buffer. This function
is meant to be called from an HTTPExecutePost callback to facilitate
easier parsing of incoming data. This function also prevents buffer
overflows by forcing the programmer to indicate how many bytes are
expected. At least 2 extra bytes are needed in cData above the maximum
length of data expected to be read.
This function will read until the next '&' character, which indicates the
end of a value parameter. It assumes that the front of the buffer is
the beginning of the value paramter to be read. If curHTTP.byteCount
indicates that all expected bytes are in the buffer, it assumes that
all remaining data is the value and acts accordingly.
This function properly updates curHTTP.byteCount by decrementing it
by the number of bytes read. The terminating '&' character is also
removed from the buffer.
Precondition:
Front of TCP buffer is the beginning of a name parameter, and the rest of
the TCP buffer contains a URL-encoded string with a name parameter
terminated by a '=' character.
Parameters:
cData - where to store the value once it is read
wLen - how many bytes can be written to cData
Return Values:
HTTP_READ_OK - value was successfully read
HTTP_READ_TRUNCTATED - entire value could not fit in the buffer, so the
value was truncated and data has been lost
HTTP_READ_INCOMPLETE - entire value was not yet in the buffer, so call
this function again later to retrieve
***************************************************************************/
#if defined(HTTP_USE_POST)
HTTP_READ_STATUS HTTPReadPostValue(BYTE* cData, WORD wLen)
{
HTTP_READ_STATUS status;
// Try to read the value
status = HTTPReadTo('&', cData, wLen);
// If read was incomplete, check if we're at the end
if(status == HTTP_READ_INCOMPLETE)
{
// If all data has arrived, read all remaining data
if(curHTTP.byteCount == TCPIsGetReady(sktHTTP))
status = HTTPReadTo('\0', cData, wLen);
}
// Decode the data (if not reading to null or blank) and return
if(cData && *cData)
HTTPURLDecode(cData);
return status;
}
#endif
/*****************************************************************************
Function:
static HTTP_READ_STATUS HTTPReadTo(BYTE cDelim, BYTE* cData, WORD wLen)
Summary:
Reads to a buffer until a specified delimiter character.
Description:
Reads from the TCP buffer to cData until either cDelim is reached, or
until wLen - 2 bytes have been read. The value read is saved to cData and
null terminated. (wLen - 2 is used so that the value can be passed to
HTTPURLDecode later, which requires a null terminator plus one extra free
byte.)
The delimiter character is removed from the buffer, but not saved to
cData. If all data cannot fit into cData, it will still be removed from
the buffer but will not be saved anywhere.
This function properly updates curHTTP.byteCount by decrementing it
by the number of bytes read.
Precondition:
None
Parameters:
cDelim - the character at which to stop reading, or NULL to read to
the end of the buffer
cData - where to store the data being read
wLen - how many bytes can be written to cData
Return Values:
HTTP_READ_OK - data was successfully read
HTTP_READ_TRUNCTATED - entire data could not fit in the buffer, so the
data was truncated and data has been lost
HTTP_READ_INCOMPLETE - delimiter character was not found
***************************************************************************/
#if defined(HTTP_USE_POST)
static HTTP_READ_STATUS HTTPReadTo(BYTE cDelim, BYTE* cData, WORD wLen)
{
HTTP_READ_STATUS status;
WORD wPos;
// Either look for delimiter, or read all available data
if(cDelim)
wPos = TCPFind(sktHTTP, cDelim, 0, FALSE);
else
wPos = TCPIsGetReady(sktHTTP);
// If not found, return incomplete
if(wPos == 0xffff)
return HTTP_READ_INCOMPLETE;
// Read the value
if(wLen < 2u && cData != NULL)
{// Buffer is too small, so read to NULL instead
curHTTP.byteCount -= TCPGetArray(sktHTTP, NULL, wPos);
status = HTTP_READ_TRUNCATED;
}
else if(cData == NULL)
{// Just remove the data
curHTTP.byteCount -= TCPGetArray(sktHTTP, NULL, wPos);
status = HTTP_READ_OK;
}
else if(wPos > wLen - 2)
{// Read data, but truncate at max length
curHTTP.byteCount -= TCPGetArray(sktHTTP, cData, wLen - 2);
curHTTP.byteCount -= TCPGetArray(sktHTTP, NULL, wPos - (wLen - 2));
cData[wLen - 2] = '\0';
status = HTTP_READ_TRUNCATED;
}
else
{// Read the data normally
curHTTP.byteCount -= TCPGetArray(sktHTTP, cData, wPos);
cData[wPos] = '\0';
status = HTTP_READ_OK;
}
// Remove the delimiter
if(cDelim)
curHTTP.byteCount -= TCPGet(sktHTTP, NULL);
return status;
}
#endif
/*****************************************************************************
Function:
HTTP_IO_RESULT HTTPMPFSUpload(void)
Summary:
Saves a file uploaded via POST as the new MPFS image in EEPROM or
external Flash.
Description:
Allows the MPFS image in EEPROM or external Flash to be updated via a
web page by accepting a file upload and storing it to the external memory.
Precondition:
MPFSFormat() has been called.
Parameters:
None
Return Values:
HTTP_IO_DONE - on success
HTTP_IO_NEED_DATA - if more data is still expected
Remarks:
This function is only available when MPFS uploads are enabled and
the MPFS image is stored in EEPROM.
Internal:
After the headers, the first line from the form will be the MIME
separator. Following that is more headers about the file, which
are discarded. After another CRLFCRLF pair the file data begins,
which is read 16 bytes at a time and written to external memory.
***************************************************************************/
#if defined(HTTP_MPFS_UPLOAD)
static HTTP_IO_RESULT HTTPMPFSUpload(void)
{
BYTE c[16];
WORD lenA, lenB;
switch(curHTTP.httpStatus)
{
// New upload, so look for the CRLFCRLF
case HTTP_MPFS_UP:
lenA = TCPFindROMArray(sktHTTP, (ROM BYTE*)"\r\n\r\n", 4, 0, FALSE);
if(lenA != 0xffff)
{// Found it, so remove all data up to and including
lenA = TCPGetArray(sktHTTP, NULL, lenA);
curHTTP.byteCount -= lenA;
// Make sure first 6 bytes are also in
if(TCPIsGetReady(sktHTTP) < (4u + 6u) )
{
lenA++;
return HTTP_IO_NEED_DATA;
}
// Make sure it's an MPFS of the correct version
lenA = TCPGetArray(sktHTTP, c, 10);
curHTTP.byteCount -= lenA;
if(memcmppgm2ram(c, (ROM void*)"\r\n\r\nMPFS\x02\x01", 10) == 0)
{// Read as Ver 2.1
curHTTP.httpStatus = HTTP_MPFS_OK;
// Format MPFS storage and put 6 byte tag
curHTTP.file = MPFSFormat();
MPFSPutArray(curHTTP.file, &c[4], 6);
}
else
{// Version is wrong
curHTTP.httpStatus = HTTP_MPFS_ERROR;
}
return HTTP_IO_WAITING;
}
else
{// Otherwise, remove as much as possible
lenA = TCPGetArray(sktHTTP, NULL, TCPIsGetReady(sktHTTP) - 4);
curHTTP.byteCount -= lenA;
}
break;
// Received file is invalid
case HTTP_MPFS_ERROR:
curHTTP.byteCount -= TCPIsGetReady(sktHTTP);
TCPDiscard(sktHTTP);
if(curHTTP.byteCount < 100u || curHTTP.byteCount > 0x80000000u)
{// If almost all data was read, or if we overflowed, then return
smHTTP = SM_HTTP_SERVE_HEADERS;
return HTTP_IO_DONE;
}
break;
// File is verified, so write the data
case HTTP_MPFS_OK:
// Determine how much to read
lenA = TCPIsGetReady(sktHTTP);
if(lenA > curHTTP.byteCount)
lenA = curHTTP.byteCount;
while(lenA > 0u)
{
lenB = TCPGetArray(sktHTTP, c, mMIN(lenA,16u));
curHTTP.byteCount -= lenB;
lenA -= lenB;
MPFSPutArray(curHTTP.file, c, lenB);
}
// If we've read all the data
if(curHTTP.byteCount == 0u)
{
MPFSPutEnd(TRUE);
smHTTP = SM_HTTP_SERVE_HEADERS;
return HTTP_IO_DONE;
}
// Other states are not valid here
default:
break;
}
// Ask for more data
return HTTP_IO_NEED_DATA;
}
#endif
/*****************************************************************************
Function:
void HTTPIncFile(ROM BYTE* cFile)
Summary:
Writes a file byte-for-byte to the currently loaded TCP socket.
Description:
Allows an entire file to be included as a dynamic variable, providing
a basic templating system for HTML web pages. This reduces unneeded
duplication of visual elements such as headers, menus, etc.
When curHTTP.callbackPos is 0, the file is opened and as many bytes
as possible are written. The current position is then saved to
curHTTP.callbackPos and the file is closed. On subsequent calls,
reading begins at the saved location and continues. Once the end of
the input file is reached, curHTTP.callbackPos is set back to 0 to
indicate completion.
Precondition:
None
Parameters:
cFile - the name of the file to be sent
Returns:
None
Remarks:
Users should not call this function directly, but should instead add
dynamic variables in the form of ~inc:filename.ext~ in their HTML code
to include (for example) the file "filename.ext" at that specified
location. The MPFS2 Generator utility will handle the rest.
***************************************************************************/
void HTTPIncFile(ROM BYTE* cFile)
{
WORD wCount, wLen;
BYTE data[64];
MPFS_HANDLE fp;
// Check if this is a first round call
if(curHTTP.callbackPos == 0x00u)
{// On initial call, open the file and save its ID
fp = MPFSOpenROM(cFile);
if(fp == MPFS_INVALID_HANDLE)
{// File not found, so abort
return;
}
((DWORD_VAL*)&curHTTP.callbackPos)->w[0] = MPFSGetID(fp);
}
else
{// The file was already opened, so load up its ID and seek
fp = MPFSOpenID(((DWORD_VAL*)&curHTTP.callbackPos)->w[0]);
if(fp == MPFS_INVALID_HANDLE)
{// No file handles available, so wait for now
return;
}
MPFSSeek(fp, ((DWORD_VAL*)&curHTTP.callbackPos)->w[1], MPFS_SEEK_FORWARD);
}
// Get/put as many bytes as possible
wCount = TCPIsPutReady(sktHTTP);
while(wCount > 0u)
{
wLen = MPFSGetArray(fp, data, mMIN(wCount, 64u));
if(wLen == 0u)
{// If no bytes were read, an EOF was reached
MPFSClose(fp);
curHTTP.callbackPos = 0x00;
return;
}
else
{// Write the bytes to the socket
TCPPutArray(sktHTTP, data, wLen);
wCount -= wLen;
}
}
// Save the new address and close the file
((DWORD_VAL*)&curHTTP.callbackPos)->w[1] = MPFSTell(fp);
MPFSClose(fp);
return;
}
#endif
|