httpd.c

Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2008 by egnite GmbH. All rights reserved.
00003  * Copyright (C) 2001-2006 by egnite Software GmbH. All rights reserved.
00004  *
00005  * Redistribution and use in source and binary forms, with or without
00006  * modification, are permitted provided that the following conditions
00007  * are met:
00008  *
00009  * 1. Redistributions of source code must retain the above copyright
00010  *    notice, this list of conditions and the following disclaimer.
00011  * 2. Redistributions in binary form must reproduce the above copyright
00012  *    notice, this list of conditions and the following disclaimer in the
00013  *    documentation and/or other materials provided with the distribution.
00014  * 3. Neither the name of the copyright holders nor the names of
00015  *    contributors may be used to endorse or promote products derived
00016  *    from this software without specific prior written permission.
00017  *
00018  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00019  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00020  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00021  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00022  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00023  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00024  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
00025  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
00026  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
00027  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
00028  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
00029  * SUCH DAMAGE.
00030  *
00031  * For additional information see http://www.ethernut.de/
00032  */
00033 
00149 #include <cfg/arch.h>
00150 #include <cfg/http.h>
00151 
00152 #include <string.h>
00153 #include <io.h>
00154 #include <fcntl.h>
00155 #include <ctype.h>
00156 #include <stdlib.h>
00157 #include <sys/stat.h>
00158 #include <memdebug.h>
00159 
00160 #include <sys/heap.h>
00161 #include <sys/version.h>
00162 
00163 #include <pro/rfctime.h>
00164 #include <pro/httpd.h>
00165 
00166 #include "dencode.h"
00167 #include "httpd_p.h"
00168 
00169 
00171 #ifndef HTTP_MAJOR_VERSION  
00172 #define HTTP_MAJOR_VERSION  1
00173 #endif
00174 
00176 #ifndef HTTP_MINOR_VERSION  
00177 #define HTTP_MINOR_VERSION  1
00178 #endif
00179 
00181 #ifndef HTTP_KEEP_ALIVE_REQ
00182 #define HTTP_KEEP_ALIVE_REQ 0
00183 #endif
00184 
00186 #ifndef HTTP_MAX_REQUEST_SIZE
00187 #define HTTP_MAX_REQUEST_SIZE 256
00188 #endif
00189 
00191 #ifndef HTTP_FILE_CHUNK_SIZE
00192 #define HTTP_FILE_CHUNK_SIZE 512
00193 #endif
00194 
00199 
00203 MIMETYPES mimeTypes[] = {
00204     {
00205     ".txt", "text/plain", NULL}, {
00206     ".html", "text/html", NULL}, {
00207     ".shtml", "text/html", NULL}, {    
00208     ".asp", "text/html", NULL}, {
00209     ".htm", "text/html", NULL}, {
00210     ".gif", "image/gif", NULL}, {
00211     ".jpg", "image/jpeg", NULL}, {
00212     ".png", "image/png", NULL}, {    
00213     ".pdf", "application/pdf", NULL}, {
00214     ".js",  "application/x-javascript", NULL}, {
00215     ".jar", "application/x-java-archive", NULL}, {
00216     ".css", "text/css", NULL}, {
00217     ".xml", "text/xml", NULL}, {
00218     NULL, NULL, NULL}
00219 };
00220 
00221 static uint32_t http_optflags;
00222 
00234 void NutHttpSendHeaderTop(FILE * stream, REQUEST * req, int status, char *title)
00235 {
00236     static prog_char fmt_P[] = "HTTP/%d.%d %d %s\r\nServer: Ethernut %s\r\n";
00237 
00238     fprintf_P(stream, fmt_P, HTTP_MAJOR_VERSION, HTTP_MINOR_VERSION, status, title, NutVersionString());
00239 #if !defined(HTTPD_EXCLUDE_DATE)
00240     if (http_optflags & HTTP_OF_USE_HOST_TIME) {
00241         time_t now = time(NULL);
00242         fprintf(stream, "Date: %s GMT\r\n", Rfc1123TimeString(gmtime(&now)));
00243     }
00244 #endif
00245 }
00246 
00263 void NutHttpSendHeaderBot(FILE * stream, char *mime_type, long bytes)
00264 {
00265     NutHttpSendHeaderBottom( stream, 0, mime_type, bytes );
00266 }
00267 
00282 void NutHttpSendHeaderBottom(FILE * stream, REQUEST * req, char *mime_type, long bytes)
00283 {
00284     static prog_char typ_fmt_P[] = "Content-Type: %s\r\n";
00285     static prog_char len_fmt_P[] = "Content-Length: %ld\r\n";
00286     static prog_char con_str_P[] = "Connection: ";
00287     static prog_char ccl_str_P[] = "close\r\n\r\n";
00288 
00289     if (mime_type)
00290         fprintf_P(stream, typ_fmt_P, mime_type);
00291     if (bytes >= 0)
00292         fprintf_P(stream, len_fmt_P, bytes);
00293     fputs_P(con_str_P, stream);
00294 #if HTTP_KEEP_ALIVE_REQ
00295     if ( req && req->req_connection == HTTP_CONN_KEEP_ALIVE) {
00296         static prog_char cka_str_P[] = "Keep-Alive\r\n\r\n";
00297         fputs_P(cka_str_P, stream);
00298     }
00299     else {
00300         fputs_P(ccl_str_P, stream);
00301     }
00302 #else
00303     fputs_P(ccl_str_P, stream);
00304 #endif
00305 }
00306 
00317 void NutHttpSendError(FILE * stream, REQUEST * req, int status)
00318 {
00319     static prog_char err_fmt_P[] = "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD><BODY>%d %s</BODY></HTML>\r\n";
00320     static prog_char auth_fmt_P[] = "WWW-Authenticate: Basic realm=\"%s\"\r\n";
00321     char *title;
00322 
00323     switch (status) {
00324     case 304:
00325         title = "Not Modified";
00326         break;
00327     case 400:
00328         title = "Bad Request";
00329         break;
00330     case 401:
00331         title = "Unauthorized";
00332         break;
00333     case 404:
00334         title = "Not Found";
00335         break;
00336     case 500:
00337         title = "Internal Error";
00338         break;
00339     case 501:
00340         title = "Not Implemented";
00341         break;
00342     default:
00343         title = "Error";
00344         break;
00345     }
00346 #if HTTP_KEEP_ALIVE_REQ
00347     if (status >= 400) {
00348         req->req_connection = HTTP_CONN_CLOSE;
00349     }
00350 #endif
00351     NutHttpSendHeaderTop(stream, req, status, title);
00352     if (status == 401) {
00353         char *cp = 0;
00354         char *realm = req->req_url;
00355 
00356         if ((cp = strrchr(realm, '/')) != NULL)
00357             *cp = 0;
00358         else
00359             realm = ".";
00360         fprintf_P(stream, auth_fmt_P, realm);
00361         if (cp)
00362             *cp = '/';
00363     }
00364     NutHttpSendHeaderBottom(stream, req, "text/html", -1);
00365     fprintf_P(stream, err_fmt_P, status, title, status, title);
00366 }
00367 
00368 static MIMETYPES *GetMimeEntry(char *name)
00369 {
00370     MIMETYPES *rc;
00371     int fl;
00372 
00373     if (name == NULL || (fl = strlen(name)) == 0) {
00374         return &mimeTypes[1];
00375     }
00376     for (rc = mimeTypes; rc->mtyp_ext; rc++) {
00377         if (strcasecmp(&(name[fl - strlen(rc->mtyp_ext)]), rc->mtyp_ext) == 0) {
00378             return rc;
00379         }
00380     }
00381     return &mimeTypes[0];
00382 }
00383 
00397 char *NutGetMimeType(char *name)
00398 {
00399     return GetMimeEntry(name)->mtyp_type;
00400 }
00401 
00417 void *NutGetMimeHandler(char *name)
00418 {
00419     return GetMimeEntry(name)->mtyp_handler;
00420 }
00421 
00433 void NutHttpURLDecode(char *str)
00434 {
00435     register char *ptr1, *ptr2, ch;
00436     char hexstr[3] = { 0, 0, 0 };
00437     for (ptr1 = ptr2 = str; *ptr1; ptr1++) {
00438         if (*ptr1 == '+')
00439             *ptr2++ = ' ';
00440         else if (*ptr1 == '%') {
00441             hexstr[0] = ptr1[1];
00442             hexstr[1] = ptr1[2];
00443             ch = strtol(hexstr, 0, 16);
00444             *ptr2++ = ch;
00445             ptr1 += 2;
00446         } else
00447             *ptr2++ = *ptr1;
00448     }
00449     *ptr2 = 0;
00450 }
00451 
00462 void NutHttpProcessQueryString(REQUEST * req)
00463 {
00464     register int i;
00465     register char *ptr;
00466 
00467     if (!req->req_query)
00468         return;
00469 
00470     req->req_numqptrs = 1;
00471     for (ptr = req->req_query; *ptr; ptr++)
00472         if (*ptr == '&')
00473             req->req_numqptrs++;
00474 
00475     req->req_qptrs = (char **) malloc(sizeof(char *) * (req->req_numqptrs * 2));
00476     if (req->req_qptrs == NULL) {
00477         /* Out of memory */
00478         req->req_numqptrs = 0;
00479         return;
00480     }
00481     req->req_qptrs[0] = req->req_query;
00482     req->req_qptrs[1] = NULL;
00483     for (ptr = req->req_query, i = 2; *ptr; ptr++) {
00484         if (*ptr == '&') {
00485             req->req_qptrs[i] = ptr + 1;
00486             req->req_qptrs[i + 1] = NULL;
00487             *ptr = 0;
00488             i += 2;
00489         }
00490     }
00491 
00492     for (i = 0; i < req->req_numqptrs; i++) {
00493         for (ptr = req->req_qptrs[i * 2]; *ptr; ptr++) {
00494             if (*ptr == '=') {
00495                 req->req_qptrs[i * 2 + 1] = ptr + 1;
00496                 *ptr = 0;
00497                 NutHttpURLDecode(req->req_qptrs[i * 2 + 1]);
00498                 break;
00499             }
00500         }
00501         NutHttpURLDecode(req->req_qptrs[i * 2]);
00502     }
00503 }
00504 
00505 static void NutHttpProcessFileRequest(FILE * stream, REQUEST * req)
00506 {
00507     int fd;
00508     int n;
00509     char *data;
00510     long file_len;
00511     void (*handler)(FILE *stream, int fd, int file_len, char *http_root, REQUEST *req);
00512     char *mime_type;
00513     char *filename = NULL;
00514     char *modstr = NULL;
00515     
00516     /*
00517      * Validate authorization.
00518      */
00519     if (NutHttpAuthValidate(req)) {
00520         NutHttpSendError(stream, req, 401);
00521         return;
00522     }
00523 
00524     /*
00525      * Process CGI.
00526      */
00527     if (NutCgiCheckRequest(stream, req)) {
00528         return;
00529     }    
00530 
00531     for (n = 0, fd = -1; default_files[n]; n++) {
00532         filename = CreateFilePath(req->req_url, default_files[n]);
00533         if (filename == NULL) {
00534             NutHttpSendError(stream, req, 500);
00535             return;
00536         }
00537         /*
00538          * Note, that simple file systems may not provide stat() or access(),
00539          * thus trying to open the file is the only way to check for existence.
00540          * Another problem is, that PHAT allows to open directories. We use
00541          * the file length to ensure, that we got a normal file.
00542          */
00543         if ((fd = _open(filename, _O_BINARY | _O_RDONLY)) != -1) {
00544             if (_filelength(fd)) {
00545                 break;
00546             }
00547             _close(fd);
00548         }
00549         free(filename);
00550     }
00551     if (fd == -1) {
00552         NutHttpSendError(stream, req, 404);
00553         return;
00554     }
00555 
00556     /* Check for mime type and handler. */
00557     mime_type = NutGetMimeType(filename);
00558     handler = NutGetMimeHandler(filename);
00559 
00560 #if !defined(HTTPD_EXCLUDE_DATE)
00561     /*
00562      * Optionally process modification time.
00563      */
00564     if (handler == NULL && (http_optflags & HTTP_OF_USE_FILE_TIME)) {
00565         struct stat s;
00566         time_t ftime;
00567         char *time_str;
00568 
00569         if (stat(filename, &s) == 0) {
00570             ftime = s.st_mtime;
00571         }
00572         else {
00573             /* Use compile time if stat not available. */
00574             ftime = RfcTimeParse("Fri " __DATE__ " " __TIME__);
00575         }
00576             
00577         /* Check if-modified-since condition. */
00578         if (req->req_ims && s.st_mtime <= req->req_ims) {
00579             _close(fd);
00580             NutHttpSendError(stream, req, 304);
00581             free(filename);
00582             return;
00583         }
00584 
00585         /* Save static buffer contents. */
00586         time_str = Rfc1123TimeString(gmtime(&ftime));
00587         modstr = strdup(time_str);
00588     }
00589 #endif /* HTTPD_EXCLUDE_DATE */
00590 
00591     /* Filename no longer used. */
00592     free(filename);
00593 
00594     NutHttpSendHeaderTop(stream, req, 200, "Ok");
00595     if (modstr) {
00596         fprintf(stream, "Last-Modified: %s GMT\r\n", modstr);
00597         free(modstr);
00598     }
00599 
00600     file_len = _filelength(fd);
00601     /* Use mime handler, if one has been registered. */
00602     if (handler) {
00603         NutHttpSendHeaderBottom(stream, req, mime_type, -1);
00604         handler(stream, fd, file_len, http_root, req);
00605     } 
00606     /* Use default transfer, if no registered mime handler is available. */
00607     else {
00608         NutHttpSendHeaderBottom(stream, req, mime_type, file_len);
00609         if (req->req_method != METHOD_HEAD) {
00610             size_t size = HTTP_FILE_CHUNK_SIZE;
00611 
00612             if ((data = malloc(size)) != NULL) {
00613                 while (file_len) {
00614                     if (file_len < HTTP_FILE_CHUNK_SIZE)
00615                         size = (size_t) file_len;
00616     
00617                     n = _read(fd, data, size);
00618                     if (n <= 0) {
00619                         /* We can't do much here, the header is out already. */
00620                         break;
00621                     }
00622                     if (fwrite(data, 1, n, stream) == 0) {
00623                         break;
00624                     }
00625                     file_len -= (long) n;
00626                 }
00627                 free(data);
00628             }
00629         }
00630     }
00631     _close(fd);
00632 }
00633 
00637 static char *NextWord(char *str)
00638 {
00639     while (*str && *str != ' ' && *str != '\t')
00640         str++;
00641     if (*str)
00642         *str++ = 0;
00643     while (*str == ' ' || *str == '\t')
00644         str++;
00645     return str;
00646 }
00647 
00653 static REQUEST *CreateRequestInfo(void)
00654 {
00655     REQUEST *req;
00656 
00657     if ((req = calloc(1, sizeof(REQUEST))) != NULL) {
00658         req->req_version = HTTP_MAJOR_VERSION * 10 + HTTP_MINOR_VERSION;
00659     }
00660     return req;
00661 }
00662 
00675 int NutRegisterHttpRoot(char *path)
00676 {
00677     int len;
00678 
00679     if (http_root)
00680         free(http_root);
00681     if (path && (len = strlen(path)) != 0) {
00682         if ((http_root = malloc(len + 1)) != NULL)
00683             strcpy(http_root, path);
00684         else
00685             return -1;
00686     } else
00687         http_root = NULL;
00688 
00689     return 0;
00690 }
00691 
00700 void NutHttpSetOptionFlags(uint32_t flags)
00701 {
00702     http_optflags = flags;
00703 }
00704 
00710 uint32_t NutHttpGetOptionFlags(void)
00711 {
00712     return http_optflags;
00713 }
00714 
00730 static int HeaderFieldValue(char **hfvp, CONST char *str)
00731 {
00732     /* Do not override existing values. */
00733     if (*hfvp == NULL) {
00734         /* Skip spaces. */
00735         while (*str == ' ' || *str == '\t')
00736             str++;
00737         /* Allocate a string copy. */
00738         if ((*hfvp = strdup(str)) == NULL)
00739             return -1;
00740     }
00741     return 0;
00742 }
00743 
00753 void NutHttpProcessRequest(FILE * stream)
00754 {
00755     REQUEST *req = NULL;
00756     char *method = NULL;
00757     char *path;
00758     char *line;
00759     char *protocol;
00760     char *cp;
00761 #if HTTP_KEEP_ALIVE_REQ
00762     int keep_alive_max = HTTP_KEEP_ALIVE_REQ;
00763 #endif
00764 
00765     for(;;) {
00766         /* Release resources used on the previous connect. */
00767         DestroyRequestInfo(req);
00768         if ((req = CreateRequestInfo()) == NULL)
00769             break;
00770         if (method)
00771             free(method);
00772 
00773         /* The first line contains method, path and protocol. */
00774         if ((method = malloc(HTTP_MAX_REQUEST_SIZE)) == NULL) {
00775             break;
00776         }
00777         if (fgets(method, HTTP_MAX_REQUEST_SIZE, stream) == NULL) {
00778             break;
00779         }
00780         if ((cp = strchr(method, '\r')) != NULL)
00781             *cp = 0;
00782         if ((cp = strchr(method, '\n')) != NULL)
00783             *cp = 0;
00784 
00785         /*
00786         * Parse remaining request header lines.
00787         */
00788         if ((line = malloc(HTTP_MAX_REQUEST_SIZE)) == NULL) {
00789             break;
00790         }
00791         for (;;) {
00792             /* Read a line and chop off CR/LF. */
00793             if (fgets(line, HTTP_MAX_REQUEST_SIZE, stream) == NULL)
00794                 break;
00795             if ((cp = strchr(line, '\r')) != 0)
00796                 *cp = 0;
00797             if ((cp = strchr(line, '\n')) != 0)
00798                 *cp = 0;
00799             if (*line == 0)
00800                 /* Empty line marks the end of the request header. */
00801                 break;
00802             if (strncasecmp(line, "Authorization:", 14) == 0) {
00803                 if (HeaderFieldValue(&req->req_auth, line + 14))
00804                     break;
00805             } else if (strncasecmp(line, "Content-Length:", 15) == 0) {
00806                 req->req_length = atol(line + 15);
00807             } else if (strncasecmp(line, "Content-Type:", 13) == 0) {
00808                 if (HeaderFieldValue(&req->req_type, line + 13))
00809                     break;
00810             } else if (strncasecmp(line, "Cookie:", 7) == 0) {
00811                 if (HeaderFieldValue(&req->req_cookie, line + 7))
00812                     break;
00813             } else if (strncasecmp(line, "User-Agent:", 11) == 0) {
00814                 if (HeaderFieldValue(&req->req_agent, line + 11))
00815                     break;
00816 #if !defined(HTTPD_EXCLUDE_DATE)
00817             } else if (strncasecmp(line, "If-Modified-Since:", 18) == 0) {
00818                 req->req_ims = RfcTimeParse(line + 18);
00819 #endif
00820             } else if (strncasecmp(line, "Referer:", 8) == 0) {
00821                 if (HeaderFieldValue(&req->req_referer, line + 8))
00822                     break;
00823             } else if (strncasecmp(line, "Host:", 5) == 0) {
00824                 if (HeaderFieldValue(&req->req_host, line + 5))
00825                     break;
00826             }
00827 #if HTTP_KEEP_ALIVE_REQ
00828             else if (strncasecmp(line, "Connection:", 11) == 0) {
00829                 if (strncasecmp(line + 12, "close", 5) == 0) {
00830                     req->req_connection = HTTP_CONN_CLOSE;
00831                 }
00832                 else if (strncasecmp(line + 12, "Keep-Alive", 10) == 0) {
00833                     req->req_connection = HTTP_CONN_KEEP_ALIVE;
00834                 }
00835             }
00836 #endif
00837         }
00838         if (line) {
00839             free(line);
00840         }
00841         path = NextWord(method);
00842         protocol = NextWord(path);
00843         NextWord(protocol);
00844 
00845         /* Determine the request method. */
00846         if (strcasecmp(method, "GET") == 0)
00847             req->req_method = METHOD_GET;
00848         else if (strcasecmp(method, "HEAD") == 0)
00849             req->req_method = METHOD_HEAD;
00850         else if (strcasecmp(method, "POST") == 0)
00851             req->req_method = METHOD_POST;
00852         else {
00853             NutHttpSendError(stream, req, 501);
00854             break;
00855         }
00856         if (*path == 0 || *protocol == 0) {
00857             NutHttpSendError(stream, req, 400);
00858             break;
00859         }
00860 
00861         /* Determine the client's HTTP version. */
00862         if (strcasecmp(protocol, "HTTP/1.0") == 0) {
00863             req->req_version = 10;
00864 #if HTTP_KEEP_ALIVE_REQ
00865             if (req->req_connection != HTTP_CONN_KEEP_ALIVE) {
00866                 req->req_connection = HTTP_CONN_CLOSE;
00867             }
00868 #endif
00869         }
00870 #if HTTP_KEEP_ALIVE_REQ
00871         else if (req->req_connection != HTTP_CONN_CLOSE) {
00872             req->req_connection = HTTP_CONN_KEEP_ALIVE;
00873         }
00874 
00875         /* Limit the number of requests per connection. */
00876         if (keep_alive_max > 0) {
00877             keep_alive_max--;
00878         }
00879         else {
00880             req->req_connection = HTTP_CONN_CLOSE;
00881         }
00882 #else
00883         req->req_connection = HTTP_CONN_CLOSE;
00884 #endif
00885 
00886         if ((cp = strchr(path, '?')) != 0) {
00887             *cp++ = 0;
00888             if ((req->req_query = strdup(cp)) == NULL) {
00889                 break;
00890             }
00891             NutHttpProcessQueryString(req);
00892         }
00893 
00894         if ((req->req_url = strdup(path)) == NULL) {
00895             break;
00896         }
00897 
00898         if (NutDecodePath(req->req_url) == 0) {
00899             NutHttpSendError(stream, req, 400);
00900         } else {
00901             NutHttpProcessFileRequest(stream, req);
00902         }
00903         fflush(stream);
00904 
00905         if (req->req_connection == HTTP_CONN_CLOSE) {
00906             break;
00907         }
00908     }
00909     DestroyRequestInfo(req);
00910     if (method)
00911         free(method);
00912 }
00913 

© 2000-2010 by contributors - visit http://www.ethernut.de/