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

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