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 
00138 #include <cfg/arch.h>
00139 #include <cfg/http.h>
00140 
00141 #include <string.h>
00142 #include <io.h>
00143 #include <fcntl.h>
00144 #include <ctype.h>
00145 #include <stdlib.h>
00146 #include <sys/stat.h>
00147 
00148 #include <sys/heap.h>
00149 #include <sys/version.h>
00150 
00151 #include <pro/rfctime.h>
00152 #include <pro/httpd.h>
00153 
00154 #include "dencode.h"
00155 #include "httpd_p.h"
00156 
00157 
00159 #ifndef HTTP_MAJOR_VERSION  
00160 #define HTTP_MAJOR_VERSION  1
00161 #endif
00162 
00164 #ifndef HTTP_MINOR_VERSION  
00165 #define HTTP_MINOR_VERSION  1
00166 #endif
00167 
00169 #ifndef HTTP_KEEP_ALIVE_REQ
00170 #define HTTP_KEEP_ALIVE_REQ 0
00171 #endif
00172 
00174 #ifndef HTTP_MAX_REQUEST_SIZE
00175 #define HTTP_MAX_REQUEST_SIZE 256
00176 #endif
00177 
00179 #ifndef HTTP_FILE_CHUNK_SIZE
00180 #define HTTP_FILE_CHUNK_SIZE 512
00181 #endif
00182 
00187 
00191 MIMETYPES mimeTypes[] = {
00192     {
00193     ".txt", "text/plain", NULL}, {
00194     ".html", "text/html", NULL}, {
00195     ".shtml", "text/html", NULL}, {    
00196     ".asp", "text/html", NULL}, {
00197     ".htm", "text/html", NULL}, {
00198     ".gif", "image/gif", NULL}, {
00199     ".jpg", "image/jpeg", NULL}, {
00200     ".png", "image/png", NULL}, {    
00201     ".pdf", "application/pdf", NULL}, {
00202     ".js",  "application/x-javascript", NULL}, {
00203     ".jar", "application/x-java-archive", NULL}, {
00204     ".css", "text/css", NULL}, {
00205     ".xml", "text/xml", NULL}, {
00206     NULL, NULL, NULL}
00207 };
00208 
00209 static u_long http_optflags;
00210 
00222 void NutHttpSendHeaderTop(FILE * stream, REQUEST * req, int status, char *title)
00223 {
00224     static prog_char fmt_P[] = "HTTP/%d.%d %d %s\r\nServer: Ethernut %s\r\n";
00225 
00226     fprintf_P(stream, fmt_P, HTTP_MAJOR_VERSION, HTTP_MINOR_VERSION, status, title, NutVersionString());
00227 #if !defined(HTTPD_EXCLUDE_DATE)
00228     if (http_optflags & HTTP_OF_USE_HOST_TIME) {
00229         time_t now = time(NULL);
00230         fprintf(stream, "Date: %s GMT\r\n", Rfc1123TimeString(gmtime(&now)));
00231     }
00232 #endif
00233 }
00234 
00251 void NutHttpSendHeaderBot(FILE * stream, char *mime_type, long bytes)
00252 {
00253     NutHttpSendHeaderBottom( stream, 0, mime_type, bytes );
00254 }
00255 
00270 void NutHttpSendHeaderBottom(FILE * stream, REQUEST * req, char *mime_type, long bytes)
00271 {
00272     static prog_char typ_fmt_P[] = "Content-Type: %s\r\n";
00273     static prog_char len_fmt_P[] = "Content-Length: %ld\r\n";
00274     static prog_char con_str_P[] = "Connection: ";
00275     static prog_char ccl_str_P[] = "close\r\n\r\n";
00276 
00277     if (mime_type)
00278         fprintf_P(stream, typ_fmt_P, mime_type);
00279     if (bytes >= 0)
00280         fprintf_P(stream, len_fmt_P, bytes);
00281     fputs_P(con_str_P, stream);
00282 #if HTTP_KEEP_ALIVE_REQ
00283     if ( req && req->req_connection == HTTP_CONN_KEEP_ALIVE) {
00284         static prog_char cka_str_P[] = "Keep-Alive\r\n\r\n";
00285         fputs_P(cka_str_P, stream);
00286     }
00287     else {
00288         fputs_P(ccl_str_P, stream);
00289     }
00290 #else
00291     fputs_P(ccl_str_P, stream);
00292 #endif
00293 }
00294 
00305 void NutHttpSendError(FILE * stream, REQUEST * req, int status)
00306 {
00307     static prog_char err_fmt_P[] = "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD><BODY>%d %s</BODY></HTML>\r\n";
00308     static prog_char auth_fmt_P[] = "WWW-Authenticate: Basic realm=\"%s\"\r\n";
00309     char *title;
00310 
00311     switch (status) {
00312     case 304:
00313         title = "Not Modified";
00314         break;
00315     case 400:
00316         title = "Bad Request";
00317         break;
00318     case 401:
00319         title = "Unauthorized";
00320         break;
00321     case 404:
00322         title = "Not Found";
00323         break;
00324     case 500:
00325         title = "Internal Error";
00326         break;
00327     case 501:
00328         title = "Not Implemented";
00329         break;
00330     default:
00331         title = "Error";
00332         break;
00333     }
00334 #if HTTP_KEEP_ALIVE_REQ
00335     if (status >= 400) {
00336         req->req_connection = HTTP_CONN_CLOSE;
00337     }
00338 #endif
00339     NutHttpSendHeaderTop(stream, req, status, title);
00340     if (status == 401) {
00341         char *cp = 0;
00342         char *realm = req->req_url;
00343 
00344         if ((cp = strrchr(realm, '/')) != NULL)
00345             *cp = 0;
00346         else
00347             realm = ".";
00348         fprintf_P(stream, auth_fmt_P, realm);
00349         if (cp)
00350             *cp = '/';
00351     }
00352     NutHttpSendHeaderBottom(stream, req, "text/html", -1);
00353     fprintf_P(stream, err_fmt_P, status, title, status, title);
00354 }
00355 
00356 static MIMETYPES *GetMimeEntry(char *name)
00357 {
00358     MIMETYPES *rc;
00359     int fl;
00360 
00361     if (name == NULL || (fl = strlen(name)) == 0) {
00362         return &mimeTypes[1];
00363     }
00364     for (rc = mimeTypes; rc->mtyp_ext; rc++) {
00365         if (strcasecmp(&(name[fl - strlen(rc->mtyp_ext)]), rc->mtyp_ext) == 0) {
00366             return rc;
00367         }
00368     }
00369     return &mimeTypes[0];
00370 }
00371 
00385 char *NutGetMimeType(char *name)
00386 {
00387     return GetMimeEntry(name)->mtyp_type;
00388 }
00389 
00405 void *NutGetMimeHandler(char *name)
00406 {
00407     return GetMimeEntry(name)->mtyp_handler;
00408 }
00409 
00421 void NutHttpURLDecode(char *str)
00422 {
00423     register char *ptr1, *ptr2, ch;
00424     char hexstr[3] = { 0, 0, 0 };
00425     for (ptr1 = ptr2 = str; *ptr1; ptr1++) {
00426         if (*ptr1 == '+')
00427             *ptr2++ = ' ';
00428         else if (*ptr1 == '%') {
00429             hexstr[0] = ptr1[1];
00430             hexstr[1] = ptr1[2];
00431             ch = strtol(hexstr, 0, 16);
00432             *ptr2++ = ch;
00433             ptr1 += 2;
00434         } else
00435             *ptr2++ = *ptr1;
00436     }
00437     *ptr2 = 0;
00438 }
00439 
00450 void NutHttpProcessQueryString(REQUEST * req)
00451 {
00452     register int i;
00453     register char *ptr;
00454 
00455     if (!req->req_query)
00456         return;
00457 
00458     req->req_numqptrs = 1;
00459     for (ptr = req->req_query; *ptr; ptr++)
00460         if (*ptr == '&')
00461             req->req_numqptrs++;
00462 
00463     req->req_qptrs = (char **) NutHeapAlloc(sizeof(char *) * (req->req_numqptrs * 2));
00464     if (req->req_qptrs == NULL) {
00465         /* Out of memory */
00466         req->req_numqptrs = 0;
00467         return;
00468     }
00469     req->req_qptrs[0] = req->req_query;
00470     req->req_qptrs[1] = NULL;
00471     for (ptr = req->req_query, i = 2; *ptr; ptr++) {
00472         if (*ptr == '&') {
00473             req->req_qptrs[i] = ptr + 1;
00474             req->req_qptrs[i + 1] = NULL;
00475             *ptr = 0;
00476             i += 2;
00477         }
00478     }
00479 
00480     for (i = 0; i < req->req_numqptrs; i++) {
00481         for (ptr = req->req_qptrs[i * 2]; *ptr; ptr++) {
00482             if (*ptr == '=') {
00483                 req->req_qptrs[i * 2 + 1] = ptr + 1;
00484                 *ptr = 0;
00485                 NutHttpURLDecode(req->req_qptrs[i * 2 + 1]);
00486                 break;
00487             }
00488         }
00489         NutHttpURLDecode(req->req_qptrs[i * 2]);
00490     }
00491 }
00492 
00493 static void NutHttpProcessFileRequest(FILE * stream, REQUEST * req)
00494 {
00495     int fd;
00496     int n;
00497     char *data;
00498     long file_len;
00499     void (*handler)(FILE *stream, int fd, int file_len, char *http_root, REQUEST *req);
00500     char *mime_type;
00501     char *filename = NULL;
00502     char *modstr = NULL;
00503     
00504     /*
00505      * Validate authorization.
00506      */
00507     if (NutHttpAuthValidate(req)) {
00508         NutHttpSendError(stream, req, 401);
00509         return;
00510     }
00511 
00512     /*
00513      * Process CGI.
00514      */
00515     if (NutCgiCheckRequest(stream, req)) {
00516         return;
00517     }    
00518 
00519     for (n = 0, fd = -1; default_files[n]; n++) {
00520         filename = CreateFilePath(req->req_url, default_files[n]);
00521         if (filename == NULL) {
00522             NutHttpSendError(stream, req, 500);
00523             return;
00524         }
00525         /*
00526          * Note, that simple file systems may not provide stat() or access(),
00527          * thus trying to open the file is the only way to check for existence.
00528          * Another problem is, that PHAT allows to open directories. We use
00529          * the file length to ensure, that we got a normal file.
00530          */
00531         if ((fd = _open(filename, _O_BINARY | _O_RDONLY)) != -1) {
00532             if (_filelength(fd)) {
00533                 break;
00534             }
00535             _close(fd);
00536         }
00537         NutHeapFree(filename);
00538     }
00539     if (fd == -1) {
00540         NutHttpSendError(stream, req, 404);
00541         return;
00542     }
00543 
00544     /* Check for mime type and handler. */
00545     mime_type = NutGetMimeType(filename);
00546     handler = NutGetMimeHandler(filename);
00547 
00548 #if !defined(HTTPD_EXCLUDE_DATE)
00549     /*
00550      * Optionally process modification time.
00551      */
00552     if (handler == NULL && (http_optflags & HTTP_OF_USE_FILE_TIME)) {
00553         struct stat s;
00554         time_t ftime;
00555         char *time_str;
00556 
00557         if (stat(filename, &s) == 0) {
00558             ftime = s.st_mtime;
00559         }
00560         else {
00561             /* Use compile time if stat not available. */
00562             ftime = RfcTimeParse("Fri " __DATE__ " " __TIME__);
00563         }
00564             
00565         /* Check if-modified-since condition. */
00566         if (req->req_ims && s.st_mtime <= req->req_ims) {
00567             _close(fd);
00568             NutHttpSendError(stream, req, 304);
00569             NutHeapFree(filename);
00570             return;
00571         }
00572 
00573         /* Save static buffer contents. */
00574         time_str = Rfc1123TimeString(gmtime(&ftime));
00575         if ((modstr = NutHeapAlloc(strlen(time_str) + 1)) != NULL) {
00576             strcpy(modstr, time_str);
00577         }
00578     }
00579 #endif /* HTTPD_EXCLUDE_DATE */
00580 
00581     /* Filename no longer used. */
00582     NutHeapFree(filename);
00583 
00584     NutHttpSendHeaderTop(stream, req, 200, "Ok");
00585     if (modstr) {
00586         fprintf(stream, "Last-Modified: %s GMT\r\n", modstr);
00587         NutHeapFree(modstr);
00588     }
00589 
00590     file_len = _filelength(fd);
00591     /* Use mime handler, if one has been registered. */
00592     if (handler) {
00593         NutHttpSendHeaderBottom(stream, req, mime_type, -1);
00594         handler(stream, fd, file_len, http_root, req);
00595     } 
00596     /* Use default transfer, if no registered mime handler is available. */
00597     else {
00598         NutHttpSendHeaderBottom(stream, req, mime_type, file_len);
00599         if (req->req_method != METHOD_HEAD) {
00600             size_t size = HTTP_FILE_CHUNK_SIZE;
00601 
00602             if ((data = NutHeapAlloc(size)) != NULL) {
00603                 while (file_len) {
00604                     if (file_len < HTTP_FILE_CHUNK_SIZE)
00605                         size = (size_t) file_len;
00606     
00607                     n = _read(fd, data, size);
00608                     if (n <= 0) {
00609                         /* We can't do much here, the header is out already. */
00610                         break;
00611                     }
00612                     if (fwrite(data, 1, n, stream) == 0) {
00613                         break;
00614                     }
00615                     file_len -= (long) n;
00616                 }
00617                 NutHeapFree(data);
00618             }
00619         }
00620     }
00621     _close(fd);
00622 }
00623 
00627 static char *NextWord(char *str)
00628 {
00629     while (*str && *str != ' ' && *str != '\t')
00630         str++;
00631     if (*str)
00632         *str++ = 0;
00633     while (*str == ' ' || *str == '\t')
00634         str++;
00635     return str;
00636 }
00637 
00643 static REQUEST *CreateRequestInfo(void)
00644 {
00645     REQUEST *req;
00646 
00647     if ((req = NutHeapAllocClear(sizeof(REQUEST))) != NULL) {
00648         req->req_version = HTTP_MAJOR_VERSION * 10 + HTTP_MINOR_VERSION;
00649     }
00650     return req;
00651 }
00652 
00665 int NutRegisterHttpRoot(char *path)
00666 {
00667     int len;
00668 
00669     if (http_root)
00670         NutHeapFree(http_root);
00671     if (path && (len = strlen(path)) != 0) {
00672         if ((http_root = NutHeapAlloc(len + 1)) != NULL)
00673             strcpy(http_root, path);
00674         else
00675             return -1;
00676     } else
00677         http_root = NULL;
00678 
00679     return 0;
00680 }
00681 
00690 void NutHttpSetOptionFlags(u_long flags)
00691 {
00692     http_optflags = flags;
00693 }
00694 
00700 u_long NutHttpGetOptionFlags(void)
00701 {
00702     return http_optflags;
00703 }
00704 
00720 static int HeaderFieldValue(char **hfvp, CONST char *str)
00721 {
00722     /* Do not override existing values. */
00723     if (*hfvp == NULL) {
00724         /* Skip spaces. */
00725         while (*str == ' ' || *str == '\t')
00726             str++;
00727         /* Allocate a string copy. */
00728         if ((*hfvp = NutHeapAlloc(strlen(str) + 1)) == NULL)
00729             return -1;
00730         strcpy(*hfvp, str);
00731     }
00732     return 0;
00733 }
00734 
00744 void NutHttpProcessRequest(FILE * stream)
00745 {
00746     REQUEST *req = NULL;
00747     char *method = NULL;
00748     char *path;
00749     char *line;
00750     char *protocol;
00751     char *cp;
00752 #if HTTP_KEEP_ALIVE_REQ
00753     int keep_alive_max = HTTP_KEEP_ALIVE_REQ;
00754 #endif
00755 
00756     for(;;) {
00757         /* Release resources used on the previous connect. */
00758         DestroyRequestInfo(req);
00759         if ((req = CreateRequestInfo()) == NULL)
00760             break;
00761         if (method)
00762             NutHeapFree(method);
00763 
00764         /* The first line contains method, path and protocol. */
00765         if ((method = NutHeapAlloc(HTTP_MAX_REQUEST_SIZE)) == NULL) {
00766             break;
00767         }
00768         if (fgets(method, HTTP_MAX_REQUEST_SIZE, stream) == NULL) {
00769             break;
00770         }
00771         if ((cp = strchr(method, '\r')) != NULL)
00772             *cp = 0;
00773         if ((cp = strchr(method, '\n')) != NULL)
00774             *cp = 0;
00775 
00776         /*
00777         * Parse remaining request header lines.
00778         */
00779         if ((line = NutHeapAlloc(HTTP_MAX_REQUEST_SIZE)) == NULL) {
00780             break;
00781         }
00782         for (;;) {
00783             /* Read a line and chop off CR/LF. */
00784             if (fgets(line, HTTP_MAX_REQUEST_SIZE, stream) == NULL)
00785                 break;
00786             if ((cp = strchr(line, '\r')) != 0)
00787                 *cp = 0;
00788             if ((cp = strchr(line, '\n')) != 0)
00789                 *cp = 0;
00790             if (*line == 0)
00791                 /* Empty line marks the end of the request header. */
00792                 break;
00793             if (strncasecmp(line, "Authorization:", 14) == 0) {
00794                 if (HeaderFieldValue(&req->req_auth, line + 14))
00795                     break;
00796             } else if (strncasecmp(line, "Content-Length:", 15) == 0) {
00797                 req->req_length = atol(line + 15);
00798             } else if (strncasecmp(line, "Content-Type:", 13) == 0) {
00799                 if (HeaderFieldValue(&req->req_type, line + 13))
00800                     break;
00801             } else if (strncasecmp(line, "Cookie:", 7) == 0) {
00802                 if (HeaderFieldValue(&req->req_cookie, line + 7))
00803                     break;
00804             } else if (strncasecmp(line, "User-Agent:", 11) == 0) {
00805                 if (HeaderFieldValue(&req->req_agent, line + 11))
00806                     break;
00807 #if !defined(HTTPD_EXCLUDE_DATE)
00808             } else if (strncasecmp(line, "If-Modified-Since:", 18) == 0) {
00809                 req->req_ims = RfcTimeParse(line + 18);
00810 #endif
00811             } else if (strncasecmp(line, "Referer:", 8) == 0) {
00812                 if (HeaderFieldValue(&req->req_referer, line + 8))
00813                     break;
00814             } else if (strncasecmp(line, "Host:", 5) == 0) {
00815                 if (HeaderFieldValue(&req->req_host, line + 5))
00816                     break;
00817             }
00818 #if HTTP_KEEP_ALIVE_REQ
00819             else if (strncasecmp(line, "Connection:", 11) == 0) {
00820                 if (strncasecmp(line + 12, "close", 5) == 0) {
00821                     req->req_connection = HTTP_CONN_CLOSE;
00822                 }
00823                 else if (strncasecmp(line + 12, "Keep-Alive", 10) == 0) {
00824                     req->req_connection = HTTP_CONN_KEEP_ALIVE;
00825                 }
00826             }
00827 #endif
00828         }
00829         if (line) {
00830             NutHeapFree(line);
00831         }
00832         path = NextWord(method);
00833         protocol = NextWord(path);
00834         NextWord(protocol);
00835 
00836         /* Determine the request method. */
00837         if (strcasecmp(method, "GET") == 0)
00838             req->req_method = METHOD_GET;
00839         else if (strcasecmp(method, "HEAD") == 0)
00840             req->req_method = METHOD_HEAD;
00841         else if (strcasecmp(method, "POST") == 0)
00842             req->req_method = METHOD_POST;
00843         else {
00844             NutHttpSendError(stream, req, 501);
00845             break;
00846         }
00847         if (*path == 0 || *protocol == 0) {
00848             NutHttpSendError(stream, req, 400);
00849             break;
00850         }
00851 
00852         /* Determine the client's HTTP version. */
00853         if (strcasecmp(protocol, "HTTP/1.0") == 0) {
00854             req->req_version = 10;
00855 #if HTTP_KEEP_ALIVE_REQ
00856             if (req->req_connection != HTTP_CONN_KEEP_ALIVE) {
00857                 req->req_connection = HTTP_CONN_CLOSE;
00858             }
00859 #endif
00860         }
00861 #if HTTP_KEEP_ALIVE_REQ
00862         else if (req->req_connection != HTTP_CONN_CLOSE) {
00863             req->req_connection = HTTP_CONN_KEEP_ALIVE;
00864         }
00865 
00866         /* Limit the number of requests per connection. */
00867         if (keep_alive_max > 0) {
00868             keep_alive_max--;
00869         }
00870         else {
00871             req->req_connection = HTTP_CONN_CLOSE;
00872         }
00873 #else
00874         req->req_connection = HTTP_CONN_CLOSE;
00875 #endif
00876 
00877         if ((cp = strchr(path, '?')) != 0) {
00878             *cp++ = 0;
00879             if ((req->req_query = NutHeapAlloc(strlen(cp) + 1)) == NULL) {
00880                 break;
00881             }
00882             strcpy(req->req_query, cp);
00883 
00884             NutHttpProcessQueryString(req);
00885         }
00886 
00887         if ((req->req_url = NutHeapAlloc(strlen(path) + 1)) == NULL) {
00888             break;
00889         }
00890         strcpy(req->req_url, path);
00891 
00892         if (NutDecodePath(req->req_url) == 0) {
00893             NutHttpSendError(stream, req, 400);
00894         } else {
00895             NutHttpProcessFileRequest(stream, req);
00896         }
00897         fflush(stream);
00898 
00899         if (req->req_connection == HTTP_CONN_CLOSE) {
00900             break;
00901         }
00902     }
00903     DestroyRequestInfo(req);
00904     if (method)
00905         NutHeapFree(method);
00906 }
00907 

© 2000-2007 by egnite Software GmbH - visit http://www.ethernut.de/