/* * Copyright (C) 2008 by egnite GmbH. All rights reserved. * Copyright (C) 2001-2006 by egnite Software GmbH. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * For additional information see http://www.ethernut.de/ */ /*! * \file pro/httpd.c * \brief HTTP server support routines. * * \verbatim * * $Log$ * Revision 1.26 2009/02/13 14:52:05 haraldkipp * Include memdebug.h for heap management debugging support. * * Revision 1.25 2009/02/06 15:40:29 haraldkipp * Using newly available strdup() and calloc(). * Replaced NutHeap routines by standard malloc/free. * Replaced pointer value 0 by NULL. * * Revision 1.24 2008/08/11 07:00:35 haraldkipp * BSD types replaced by stdint types (feature request #1282721). * * Revision 1.23 2008/07/17 11:36:32 olereinhardt * - Moved some functions used in httpd.c as well as in ssi.c into httpd_p.c * - Implemeted $QUERY_STRING parameter in for CGIs included by a ssi file * * Revision 1.22 2008/07/14 13:11:15 haraldkipp * Added file length check to avoid loading directories when using PHAT. * * Revision 1.21 2008/07/10 12:09:39 haraldkipp * Wrong mime type was returned for default files within subdirectories. * Put duplicate code in a new static function GetMimeEntry. * * Revision 1.20 2008/07/08 13:27:55 haraldkipp * Several HTTP server options are now configurable. * Keepalive is now disabled by default to maintain backward compatibility. * * Revision 1.19 2008/05/21 14:10:19 thiagocorrea * Workaround HTTP stall connections. Details in bug id 1968754 * * Revision 1.18 2008/05/16 03:38:27 thiagocorrea * Revert httpd memory allocation calls to NutHeapAlloc for consistency and * move DestroyRequestInfo to a shared file (reduces code size and remove duplicates * from httpd.c and ssi.c) * * Revision 1.17 2008/04/15 05:13:30 hwmaier * Fixed compilation error with avr-gcc 3.4.6 * * Revision 1.16 2008/04/01 10:11:35 haraldkipp * Added the new, enhanced httpd API library. * Bugs #1839026 and #1839029 fixed. * * Revision 1.15 2006/11/08 08:52:31 haraldkipp * Bugfix, kindly provided by Steve Venroy. Already released request * structure was passed to NutHttpSendError(). * * Revision 1.14 2006/10/08 16:48:22 haraldkipp * Documentation fixed * * Revision 1.13 2006/03/16 15:25:38 haraldkipp * Changed human readable strings from u_char to char to stop GCC 4 from * nagging about signedness. * * Revision 1.12 2006/01/06 09:19:42 haraldkipp * NutHttpURLEncode() no longer encodes everything that isn't alphanumeric. * See RFC2396. Thanks to Lloyd Bailey for this update. * * Revision 1.11 2005/10/24 11:02:28 haraldkipp * Integer division hack for ARM without CRT removed. * * Revision 1.10 2005/08/26 14:12:39 olereinhardt * Added NutHttpProcessPostQuery(FILE *stream, REQUEST * req) * * Revision 1.9 2005/08/05 11:23:11 olereinhardt * Added support to register a custom handler for mime types. * Added Server side include support and ASP support. * * Revision 1.8 2005/04/30 13:08:15 chaac * Added support for parsing Content-Length field in HTTP requests. * * Revision 1.7 2005/04/05 17:58:02 haraldkipp * Avoid integer division on ARM platform as long as we run without crtlib. * * Revision 1.6 2004/12/16 10:17:18 haraldkipp * Added Mikael Adolfsson's excellent parameter parsing routines. * * Revision 1.5 2004/07/30 19:45:48 drsung * Slightly improved handling if socket was closed by peer. * * Revision 1.4 2004/03/02 10:09:59 drsung * Small bugfix in NutHttpSendError. Thanks to Damian Slee. * * Revision 1.3 2003/07/20 16:03:27 haraldkipp * Saved some RAM by moving string literals to program memory. * * Revision 1.2 2003/07/17 12:28:21 haraldkipp * Memory hole bugfix * * Revision 1.1.1.1 2003/05/09 14:41:58 haraldkipp * Initial using 3.2.1 * * Revision 1.14 2003/02/04 18:17:32 harald * Version 3 released * * Revision 1.13 2003/01/14 17:04:20 harald * Using FAT file system and added types * * Revision 1.12 2002/10/31 16:32:45 harald * Mods by troth for Linux * * Revision 1.11 2002/09/15 17:08:44 harald * Allow different character sets * * Revision 1.10 2002/06/26 17:29:49 harald * First pre-release with 2.4 stack * * \endverbatim */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dencode.h" #include "httpd_p.h" /*! \brief Local major HTTP version. */ #ifndef HTTP_MAJOR_VERSION #define HTTP_MAJOR_VERSION 1 #endif /*! \brief Local minor HTTP version. */ #ifndef HTTP_MINOR_VERSION #define HTTP_MINOR_VERSION 1 #endif /*! \brief Maximum number of requests per connection. */ #ifndef HTTP_KEEP_ALIVE_REQ #define HTTP_KEEP_ALIVE_REQ 0 #endif /*! \brief Maximum size of an incoming request line. */ #ifndef HTTP_MAX_REQUEST_SIZE #define HTTP_MAX_REQUEST_SIZE 256 #endif /*! \brief Chunk size while sending files. */ #ifndef HTTP_FILE_CHUNK_SIZE #define HTTP_FILE_CHUNK_SIZE 512 #endif /*! \brief Enable GZIP support. */ #ifndef HTTPD_SUPPORT_GZIP #define HTTPD_SUPPORT_GZIP 0 #endif /*! * \addtogroup xgHTTPD */ /*@{*/ /*! * \brief Structure for table of interpreted header names. */ typedef struct _REQUEST_LOOKUP { uint_fast8_t rlu_len; char *rlu_str; } REQUEST_LOOKUP; /*! * \brief Table for verifying header names. * * Modify with care, it might become a bit tricky. * * In any case the entries must be in sorted order. When inserting * a new entry, you need to adapt the switch/case statement in * function ParseHeaderLines(). If the new entry is larger than * all existing entries, do not forget to update MAX_REQUEST_NAME_SIZE. */ static const REQUEST_LOOKUP req_lookup[] = { { 15, "accept-encoding" }, { 13, "authorization" }, #if HTTP_KEEP_ALIVE_REQ { 10, "connection" }, #else { 0, NULL }, #endif { 14, "content-length" }, { 12, "content-type" }, { 6, "cookie" }, { 4, "host" }, #if defined(HTTPD_EXCLUDE_DATE) { 0, NULL }, #else { 17, "if-modified-since" }, #endif { 7, "referer" }, { 10, "user-agent" } }; /*! * \brief Number of entries in the header name table. */ #define NUM_REQUEST_LOOKUP sizeof(req_lookup) / sizeof(REQUEST_LOOKUP) /*! * \brief Size of the largest entry in the header name table. */ #if defined(HTTPD_EXCLUDE_DATE) #define MAX_REQUEST_NAME_SIZE 15 #else #define MAX_REQUEST_NAME_SIZE 17 #endif /*! * \brief Known mime types. */ MIMETYPES mimeTypes[] = { { ".txt", "text/plain", NULL}, { ".html", "text/html", NULL}, { ".shtml", "text/html", NULL}, { ".asp", "text/html", NULL}, { ".htm", "text/html", NULL}, { ".gif", "image/gif", NULL}, { ".jpg", "image/jpeg", NULL}, { ".png", "image/png", NULL}, { ".bmp", "image/bmp", NULL}, { ".pdf", "application/pdf", NULL}, { ".js", "application/x-javascript", NULL}, { ".jar", "application/x-java-archive", NULL}, { ".css", "text/css", NULL}, { ".xml", "text/xml", NULL}, { ".svg", "image/svg+xml", NULL}, { NULL, NULL, NULL} }; static uint32_t http_optflags; /*! * \brief Send top lines of a standard HTML header. * * Sends HTTP and Server version lines. * * \param stream Stream of the socket connection, previously opened for * binary read and write. * \param req The associated client request. * \param status Response status, error code or 200, if no error occurred. * \param title Error text, or OK, if no error occurred. */ void NutHttpSendHeaderTop(FILE * stream, REQUEST * req, int status, char *title) { static const char fmt_P[] PROGMEM = "HTTP/%d.%d %d %s\r\nServer: Ethernut %s\r\n"; fprintf_P(stream, fmt_P, HTTP_MAJOR_VERSION, HTTP_MINOR_VERSION, status, title, NutVersionString()); #if !defined(HTTPD_EXCLUDE_DATE) if (http_optflags & HTTP_OF_USE_HOST_TIME) { time_t now = time(NULL); fprintf(stream, "Date: %s GMT\r\n", Rfc1123TimeString(gmtime(&now))); } #endif } /*! * \brief Send bottom lines of a standard HTML header. * * Sends Content-Type and Content-Length. * * \deprecated Use NutHttpSendHeaderBottom(). * * \param stream Stream of the socket connection, previously opened * for binary read and write. * \param mime_type Points to a string that specifies the content type. * Examples are "text/html", "image/png", * "image/gif", "video/mpeg" or "text/css". * A null pointer is ignored. * \param bytes Content length of the data following this * header. Ignored, if negative. */ void NutHttpSendHeaderBot(FILE * stream, char *mime_type, long bytes) { NutHttpSendHeaderBottom( stream, 0, mime_type, bytes); } /*! * \brief Send bottom lines of a standard HTML header. * * Sends Content-Type, Content-Lenght and Connection lines. * * \param stream Stream of the socket connection, previously opened * for binary read and write. * \param mime_type Points to a string that specifies the content type. * Examples are "text/html", "image/png", * "image/gif", "video/mpeg" or "text/css". * A null pointer is ignored. * \param bytes Content length of the data following this * header. Ignored, if negative. * \param first2bytes The first two bytes of the file. */ static void NutHttpSendHeaderBottomEx(FILE * stream, REQUEST * req, char *mime_type, long bytes, unsigned short first2bytes) { static const char typ_fmt_P[] PROGMEM = "Content-Type: %s\r\n"; static const char len_fmt_P[] PROGMEM = "Content-Length: %ld\r\n"; static const char enc_fmt_P[] PROGMEM = "Content-Encoding: gzip\r\n"; static const char con_str_P[] PROGMEM = "Connection: "; static const char ccl_str_P[] PROGMEM = "close\r\n\r\n"; #define GZIP_ID 0x8b1f if (mime_type) fprintf_P(stream, typ_fmt_P, mime_type); if (bytes >= 0) fprintf_P(stream, len_fmt_P, bytes); if (first2bytes == GZIP_ID) fputs_P(enc_fmt_P, stream); fputs_P(con_str_P, stream); #if HTTP_KEEP_ALIVE_REQ if ( req && req->req_connection == HTTP_CONN_KEEP_ALIVE) { static const char cka_str_P[] PROGMEM = "Keep-Alive\r\n\r\n"; fputs_P(cka_str_P, stream); } else { fputs_P(ccl_str_P, stream); } #else fputs_P(ccl_str_P, stream); #endif } /*! * \brief Send bottom lines of a standard HTML header. * * Sends Content-Type, Content-Lenght and Connection lines. * * \param stream Stream of the socket connection, previously opened * for binary read and write. * \param mime_type Points to a string that specifies the content type. * Examples are "text/html", "image/png", * "image/gif", "video/mpeg" or "text/css". * A null pointer is ignored. * \param bytes Content length of the data following this * header. Ignored, if negative. */ void NutHttpSendHeaderBottom(FILE * stream, REQUEST * req, char *mime_type, long bytes) { NutHttpSendHeaderBottomEx(stream, req, mime_type, bytes, 0); } /*! * \brief Send a HTTP error response. * * A canned error file is used. * * \param stream Stream of the socket connection, previously opened for * binary read and write. * \param req Contains the HTTP request. * \param status Error code to be returned. */ void NutHttpSendError(FILE * stream, REQUEST * req, int status) { static const char err_fmt_P[] PROGMEM = "%d %s%d %s\r\n"; static const char auth_fmt_P[] PROGMEM = "WWW-Authenticate: Basic realm=\"%s\"\r\n"; char *title; switch (status) { case 304: title = "Not Modified"; break; case 400: title = "Bad Request"; break; case 401: title = "Unauthorized"; break; case 404: title = "Not Found"; break; case 500: title = "Internal Error"; break; case 501: title = "Not Implemented"; break; default: title = "Error"; break; } #if HTTP_KEEP_ALIVE_REQ if (status >= 400) { req->req_connection = HTTP_CONN_CLOSE; } #endif NutHttpSendHeaderTop(stream, req, status, title); if (status == 401) { char *cp = 0; char *realm = req->req_url; if ((cp = strrchr(realm, '/')) != NULL) *cp = 0; else realm = "."; fprintf_P(stream, auth_fmt_P, realm); if (cp) *cp = '/'; } NutHttpSendHeaderBottom(stream, req, "text/html", -1); fprintf_P(stream, err_fmt_P, status, title, status, title); } static MIMETYPES *GetMimeEntry(char *name) { MIMETYPES *rc; int fl; if (name == NULL || (fl = strlen(name)) == 0) { return &mimeTypes[1]; } for (rc = mimeTypes; rc->mtyp_ext; rc++) { if (strcasecmp(&(name[fl - strlen(rc->mtyp_ext)]), rc->mtyp_ext) == 0) { return rc; } } return &mimeTypes[0]; } /*! * \brief Return the mime type description of a specified file name. * * The mime type returned is based on the file extension. * * \param name Name of the file. * * \return A pointer to a static string, containing the * associated mime type description. If the extension * is not registered, "text/plain; charset=iso-8859-1" * is returned. If the filename is empty, then * "text/html; charset=iso-8859-1" is returned. */ char *NutGetMimeType(char *name) { return GetMimeEntry(name)->mtyp_type; } /*! * \brief Return the mime type handler of a specified file name. * * This is the function that handles / sends a specific file type to the * client. Specially used for server side includes (shtml files) * * \param name Name of the file. * * \return A pointer to a function of the type void (u_char * filename) * If the extension is not registered, the handler for * "text/plain; charset=iso-8859-1" is returned. * If the filename is empty, then the handler for * "text/html; charset=iso-8859-1" is returned. */ void *NutGetMimeHandler(char *name) { return GetMimeEntry(name)->mtyp_handler; } /*! * \brief URLDecodes a string * * Takes a url-encoded string and decodes it. * * \param str String to decode. This is overwritten with * the decoded string * * \warning To save RAM, the str parameter will be * overwritten with the encoded string. */ void NutHttpURLDecode(char *str) { register char *ptr1, *ptr2, ch; char hexstr[3] = { 0, 0, 0 }; for (ptr1 = ptr2 = str; *ptr1; ptr1++) { if (*ptr1 == '+') *ptr2++ = ' '; else if (*ptr1 == '%') { hexstr[0] = ptr1[1]; hexstr[1] = ptr1[2]; ch = strtol(hexstr, 0, 16); *ptr2++ = ch; ptr1 += 2; } else *ptr2++ = *ptr1; } *ptr2 = 0; } /*! * \brief Parses the QueryString * * Reads the QueryString from a request, and parses it into * name/value table. To save RAM, this method overwrites the * contents of req_query, and creates a table of pointers * into the req_query buffer. * * \param req Request object to parse */ void NutHttpProcessQueryString(REQUEST * req) { register int i; register char *ptr; if (!req->req_query) return; req->req_numqptrs = 1; for (ptr = req->req_query; *ptr; ptr++) if (*ptr == '&') req->req_numqptrs++; req->req_qptrs = (char **) malloc(sizeof(char *) * (req->req_numqptrs * 2)); if (req->req_qptrs == NULL) { /* Out of memory */ req->req_numqptrs = 0; return; } req->req_qptrs[0] = req->req_query; req->req_qptrs[1] = NULL; for (ptr = req->req_query, i = 2; *ptr; ptr++) { if (*ptr == '&') { req->req_qptrs[i] = ptr + 1; req->req_qptrs[i + 1] = NULL; *ptr = 0; i += 2; } } for (i = 0; i < req->req_numqptrs; i++) { for (ptr = req->req_qptrs[i * 2]; *ptr; ptr++) { if (*ptr == '=') { req->req_qptrs[i * 2 + 1] = ptr + 1; *ptr = 0; NutHttpURLDecode(req->req_qptrs[i * 2 + 1]); break; } } NutHttpURLDecode(req->req_qptrs[i * 2]); } } static void NutHttpProcessFileRequest(FILE * stream, REQUEST * req) { int fd; int n; char *data; long file_len; void (*handler)(FILE *stream, int fd, int file_len, char *http_root, REQUEST *req); char *mime_type; char *filename = NULL; char *modstr = NULL; unsigned short first2bytes = 0; /* * Validate authorization. */ if (NutHttpAuthValidate(req)) { NutHttpSendError(stream, req, 401); return; } /* * Process CGI. */ if (NutCgiCheckRequest(stream, req)) { return; } for (n = 0, fd = -1; default_files[n]; n++) { filename = CreateFilePath(req->req_url, default_files[n]); if (filename == NULL) { NutHttpSendError(stream, req, 500); return; } /* * Note, that simple file systems may not provide stat() or access(), * thus trying to open the file is the only way to check for existence. * Another problem is, that PHAT allows to open directories. We use * the file length to ensure, that we got a normal file. */ if ((fd = _open(filename, _O_BINARY | _O_RDONLY)) != -1) { if (_filelength(fd)) { break; } _close(fd); } free(filename); } if (fd == -1) { NutHttpSendError(stream, req, 404); return; } /* Check for mime type and handler. */ mime_type = NutGetMimeType(filename); handler = NutGetMimeHandler(filename); #if !defined(HTTPD_EXCLUDE_DATE) /* * Optionally process modification time. */ if (handler == NULL && (http_optflags & HTTP_OF_USE_FILE_TIME)) { struct stat s; time_t ftime; char *time_str; if (stat(filename, &s) == 0) { ftime = s.st_mtime; } else { /* Use compile time if stat not available. */ ftime = RfcTimeParse("Fri " __DATE__ " " __TIME__); } /* Check if-modified-since condition. */ if (req->req_ims && s.st_mtime <= req->req_ims) { _close(fd); NutHttpSendError(stream, req, 304); free(filename); return; } /* Save static buffer contents. */ time_str = Rfc1123TimeString(gmtime(&ftime)); modstr = strdup(time_str); } #endif /* HTTPD_EXCLUDE_DATE */ /* Filename no longer used. */ free(filename); NutHttpSendHeaderTop(stream, req, 200, "Ok"); if (modstr) { fprintf(stream, "Last-Modified: %s GMT\r\n", modstr); free(modstr); } file_len = _filelength(fd); /* Use mime handler, if one has been registered. */ if (handler) { NutHttpSendHeaderBottom(stream, req, mime_type, -1); handler(stream, fd, file_len, http_root, req); } /* Use default transfer, if no registered mime handler is available. */ else { #if (HTTPD_SUPPORT_GZIP >= 1) /* Check for Accept-Encoding: gzip support */ if (req->req_encoding != NULL) { if (strstr(req->req_encoding, "gzip") != NULL) { /* Read first two bytes, needed for gzip header check */ _read(fd, &first2bytes, 2); _seek(fd, -2, SEEK_CUR); } } #endif NutHttpSendHeaderBottomEx(stream, req, mime_type, file_len, first2bytes); if (req->req_method != METHOD_HEAD) { size_t size = HTTP_FILE_CHUNK_SIZE; if ((data = malloc(size)) != NULL) { while (file_len) { if (file_len < HTTP_FILE_CHUNK_SIZE) size = (size_t) file_len; n = _read(fd, data, size); if (n <= 0) { /* We can't do much here, the header is out already. */ break; } if (fwrite(data, 1, n, stream) == 0) { break; } file_len -= (long) n; } free(data); } } } _close(fd); } /*! * */ static char *NextWord(char *str) { while (*str && *str != ' ' && *str != '\t') str++; if (*str) *str++ = 0; while (*str == ' ' || *str == '\t') str++; return str; } /*! * \brief Create a new request info structure. * * \return Pointer to an allocated structure or NULL if out of memory. */ static REQUEST *CreateRequestInfo(void) { REQUEST *req; if ((req = calloc(1, sizeof(REQUEST))) != NULL) { req->req_version = HTTP_MAJOR_VERSION * 10 + HTTP_MINOR_VERSION; } return req; } /*! * \brief Register the HTTP server's root directory. * * Only one root directory is supported. Subsequent calls will * override previous settings. * * \param path Pathname of the root directory. Must include the * device name followed by a colon followed by a * directory path followed by a trailing slash. * * \return 0 on success, -1 otherwise. */ int NutRegisterHttpRoot(char *path) { int len; if (http_root) free(http_root); if (path && (len = strlen(path)) != 0) { if ((http_root = malloc(len + 1)) != NULL) strcpy(http_root, path); else return -1; } else http_root = NULL; return 0; } /*! * \brief Set HTTP option flags. * * \param flags Option flags to set. Any of the following may be or'ed: * - HTTP_OF_USE_HOST_TIME Date header will be included in response. * - HTTP_OF_USE_FILE_TIME Handle file modification time. * */ void NutHttpSetOptionFlags(uint32_t flags) { http_optflags = flags; } /*! * \brief Retrieve HTTP option flags. * * \return Option flags. */ uint32_t NutHttpGetOptionFlags(void) { return http_optflags; } /*! * \brief Allocate a buffer for a header field value. * * \param hfvp Points to the character pointer variable that will receive * the pointer to the header field value. If the variable does * not contain a NULL pointer upon entry, the routine will * return immediately and will not extract any value. If it * contains a NULL pointer on entry, the routine will allocate * heap memory to store a copy of the extracted value. In this * case the caller is responsible to release the allocation. * \param str Points into a request line, right after the colon. Leading * spaces will be skipped before creating a copy. * * \return -1 if out of memory, otherwise 0. */ static int HeaderFieldValue(char **hfvp, const char *str) { /* Do not override existing values. */ if (*hfvp == NULL) { /* Skip spaces. */ while (*str == ' ' || *str == '\t') str++; /* Allocate a string copy. */ if ((*hfvp = strdup(str)) == NULL) return -1; } return 0; } /*! * \brief Interpret next header from a given stream. * * This function reads single characters from the stream as long * as they fit with an entry in the req_lookup table. It will * return as soon as an unknown entry had been detected, or when * a colon or linefeed has been read, or if reading from the stream * fails. * * \param stream Stream to read from. * \param idx Pointer to a variable that receives the index * of a known header. This is only valid if the * function returns a colon character. * * \return Last character read from the stream, EOF on error or * timeout, or zero on empty lines. */ static int NextHeaderName(FILE * stream, uint_fast8_t *idx) { uint_fast8_t i = 0; int ch = 0; *idx = 0; /* Read the first character, which is not a carriage returns. */ do { ch = fgetc(stream); } while (ch == '\r'); /* Return 0, if we got the linefeed. This is an empty line, which should be interpreted by the caller as the end of the HTTP header. */ if (ch == '\n') { return 0; } /* Lookup the header line name. */ while (i < MAX_REQUEST_NAME_SIZE && *idx < NUM_REQUEST_LOOKUP) { /* Stop, if the last read failed or if we found the end of the line or the name field. */ if (ch == EOF || ch == '\n') { break; } /* Check if the length is correct */ if (ch == ':') { if (i == req_lookup[*idx].rlu_len) { /* The correct element was found */ break; } else { /* The element does not match */ *idx = -1; break; } } /* Check if all characters read so far fits with any header we are interested in. */ for (; *idx < NUM_REQUEST_LOOKUP; (*idx)++) { if (i < req_lookup[*idx].rlu_len && *(req_lookup[*idx].rlu_str + i) == tolower(ch)) { /* So far, this header fits. */ break; } } /* Read the next character, ignoring carriage returns. */ i++; do { ch = fgetc(stream); } while (ch == '\r'); } return ch; } /*! * \brief Read characters from a given stream until EOL. */ static void SkipLine(FILE * stream) { int ch; do { ch = fgetc(stream); } while (ch != EOF && ch != '\n'); } static int ParserHeaderLines(FILE *stream, REQUEST *req) { char *cp; int ch; uint_fast8_t req_idx; char **strval; char *line; line = malloc(HTTP_MAX_REQUEST_SIZE); if (line) { for (;;) { /* Parse the next header name. */ ch = NextHeaderName(stream, &req_idx); if (ch == EOF) { /* Broken connection, stop parsing. */ break; } if (ch == 0) { /* Empty line marks the end of the request header. */ free(line); return 0; } if (ch != ':' || req_idx >= NUM_REQUEST_LOOKUP) { /* No valid name/value pair or unexpected header line. Skip this line. */ SkipLine(stream); } else { /* At this point we got a header we are interested in. Read the rest of this line, it contains the value. */ if (fgets(line, HTTP_MAX_REQUEST_SIZE, stream) == NULL) { /* Broken connection, stop parsing. */ break; } /* Make sure we got a complete line. */ cp = strchr(line, '\n'); if (cp == NULL) { /* Incomplete line, skip it. */ SkipLine(stream); /* May be we should return an internal error to the browser. */ } else { /* We got the value, chop off any CR-LF. */ *cp = '\0'; if (cp > line && *--cp == '\r') { *cp = '\0'; } //printf("Header '%s: %s'\n", req_lookup[req_idx].rlu_str, line); /* Store the value in the request info structure. Impotant! This switch statement must correspond to the req_lookup table. */ strval = NULL; switch (req_idx) { case 0: /* Accept-encoding */ strval = &req->req_encoding; break; case 1: /* Authorization: Store as string. */ strval = &req->req_auth; break; #if HTTP_KEEP_ALIVE_REQ case 2: /* Connection: Interpret type. */ if (strncasecmp(line, "close", 5) == 0) { req->req_connection = HTTP_CONN_CLOSE; } else if (strncasecmp(line, "Keep-Alive", 10) == 0) { req->req_connection = HTTP_CONN_KEEP_ALIVE; } break; #endif case 3: /* Content-Length: Get size. */ req->req_length = atol(line); break; case 4: /* Content-Type: Store as string. */ strval = &req->req_type; break; case 5: /* Cookie: Store as string. */ strval = &req->req_cookie; break; case 6: /* Host: Store as string. */ strval = &req->req_host; break; #if !defined(HTTPD_EXCLUDE_DATE) case 7: /* If-Modified-Since: Interpret RFC date. */ req->req_ims = RfcTimeParse(line); break; #endif case 8: /* Referer: Store as string. */ strval = &req->req_referer; break; case 9: /* User-Agent: Store as string. */ strval = &req->req_agent; break; } /* Anything to store as a string. */ if (strval && HeaderFieldValue(strval, line)) { break; } } } } /* All header lines processed. */ free(line); } return -1; } /*! * \brief Process the next HTTP request. * * Waits for the next HTTP request on an established connection * and processes it. * * \param stream Stream of the socket connection, previously opened for * binary read and write. */ void NutHttpProcessRequest(FILE * stream) { REQUEST *req = NULL; char *method = NULL; char *path; char *protocol; char *cp; #if HTTP_KEEP_ALIVE_REQ int keep_alive_max = HTTP_KEEP_ALIVE_REQ; #endif for(;;) { /* Release resources used on the previous connect. */ DestroyRequestInfo(req); if ((req = CreateRequestInfo()) == NULL) break; if (method) free(method); /* The first line contains method, path and protocol. */ if ((method = malloc(HTTP_MAX_REQUEST_SIZE)) == NULL) { break; } if (fgets(method, HTTP_MAX_REQUEST_SIZE, stream) == NULL) { break; } if ((cp = strchr(method, '\r')) != NULL) *cp = 0; if ((cp = strchr(method, '\n')) != NULL) *cp = 0; /* Parse remaining request header lines. */ if (ParserHeaderLines(stream, req)) { break; } /* Further break up the first header line. */ path = NextWord(method); protocol = NextWord(path); NextWord(protocol); /* Determine the request method. */ if (strcasecmp(method, "GET") == 0) req->req_method = METHOD_GET; else if (strcasecmp(method, "HEAD") == 0) req->req_method = METHOD_HEAD; else if (strcasecmp(method, "POST") == 0) req->req_method = METHOD_POST; else { NutHttpSendError(stream, req, 501); break; } if (*path == 0 || *protocol == 0) { NutHttpSendError(stream, req, 400); break; } /* Determine the client's HTTP version. */ if (strcasecmp(protocol, "HTTP/1.0") == 0) { req->req_version = 10; #if HTTP_KEEP_ALIVE_REQ if (req->req_connection != HTTP_CONN_KEEP_ALIVE) { req->req_connection = HTTP_CONN_CLOSE; } #endif } #if HTTP_KEEP_ALIVE_REQ else if (req->req_connection != HTTP_CONN_CLOSE) { req->req_connection = HTTP_CONN_KEEP_ALIVE; } /* Limit the number of requests per connection. */ if (keep_alive_max > 0) { keep_alive_max--; } else { req->req_connection = HTTP_CONN_CLOSE; } #else req->req_connection = HTTP_CONN_CLOSE; #endif if ((cp = strchr(path, '?')) != 0) { *cp++ = 0; if ((req->req_query = strdup(cp)) == NULL) { break; } NutHttpProcessQueryString(req); } if ((req->req_url = strdup(path)) == NULL) { break; } if (NutDecodePath(req->req_url) == 0) { NutHttpSendError(stream, req, 400); } else { NutHttpProcessFileRequest(stream, req); } fflush(stream); if (req->req_connection == HTTP_CONN_CLOSE) { break; } } DestroyRequestInfo(req); if (method) free(method); } /*@}*/