| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164 |
- /*
- * 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 <cfg/arch.h>
- #include <cfg/http.h>
- #include <string.h>
- #include <io.h>
- #include <fcntl.h>
- #include <ctype.h>
- #include <stdlib.h>
- #include <sys/stat.h>
- #include <memdebug.h>
- #include <sys/heap.h>
- #include <sys/version.h>
- #include <pro/rfctime.h>
- #include <pro/httpd.h>
- #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 = "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD><BODY>%d %s</BODY></HTML>\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);
- }
- /*@}*/
|