Nut/OS  4.10.3
API Reference
ssi.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2001-2004 by Ole Reinhardt <ole.reinhardt@embedded-it.de>. 
00003  * 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 EGNITE SOFTWARE GMBH 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 EGNITE
00022  * SOFTWARE GMBH 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  
00034 /*
00035  * $Log$
00036  * Revision 1.14  2009/02/13 14:43:11  haraldkipp
00037  * Memory overflow bug fixed.
00038  *
00039  * Revision 1.13  2009/02/06 15:40:29  haraldkipp
00040  * Using newly available strdup() and calloc().
00041  * Replaced NutHeap routines by standard malloc/free.
00042  * Replaced pointer value 0 by NULL.
00043  *
00044  * Revision 1.12  2008/08/22 09:23:57  haraldkipp
00045  * GCC specific implementation removed.
00046  *
00047  * Revision 1.11  2008/08/18 11:00:39  olereinhardt
00048  * Fixed a memory leak. A filename was never freed again
00049  *
00050  * Revision 1.10  2008/08/11 07:00:36  haraldkipp
00051  * BSD types replaced by stdint types (feature request #1282721).
00052  *
00053  * Revision 1.9  2008/07/26 14:09:29  haraldkipp
00054  * Fixed another problem with ICCAVR.
00055  *
00056  * Revision 1.8  2008/07/25 12:17:26  olereinhardt
00057  * Imagecraft compilers does not support alloca (to allocate memory from the
00058  * stack). So we use NutHeapAlloc / NutHeapClear instead for Imagecraft.
00059  *
00060  * Revision 1.7  2008/07/17 11:36:34  olereinhardt
00061  * - Moved some functions used in httpd.c as well as in ssi.c into httpd_p.c
00062  * - Implemeted $QUERY_STRING parameter in for CGIs included by a ssi file
00063  *
00064  * Revision 1.6  2008/05/13 21:24:55  thiagocorrea
00065  * Fix a few documentation typos.
00066  *
00067  * Revision 1.5  2008/04/21 22:16:25  olereinhardt
00068  * 2008-04-21  Ole Reinhardt <ole.reinhardt@embedded-it.de>
00069  *         * pro/ssi.c: Nicer Implement query string feature and save some
00070  *           memory
00071  *
00072  * Revision 1.4  2008/04/18 13:22:57  haraldkipp
00073  * Fixed ICCAVR compile errors. No chance to implement GCC's PSTR().
00074  *
00075  * Revision 1.3  2008/02/15 17:09:03  haraldkipp
00076  * Quick hack provided by Niels. No idea what this is for, but
00077  * according to the author it is a dirty solution. We urgently
00078  * need it to get the Elektor Radio working. Sorry in advance
00079  * for any trouble this change may cause.
00080  *
00081  * Revision 1.2  2006/03/16 15:25:39  haraldkipp
00082  * Changed human readable strings from u_char to char to stop GCC 4 from
00083  * nagging about signedness.
00084  *
00085  * Revision 1.1  2005/08/05 11:22:14  olereinhardt
00086  * Added Server side include support. Initial checkin
00087  *
00088  */  
00089 
00094 
00095 #include <cfg/arch.h>
00096 #include <sys/types.h>
00097 
00098 #include <memdebug.h>
00099 #include <string.h>
00100 #include <stdlib.h>
00101 
00102 #include <io.h>
00103 #include <ctype.h>
00104 #include <stdio.h>
00105 #include <unistd.h>
00106 #include <fcntl.h>
00107 
00108 #include <sys/heap.h>
00109 #include <sys/version.h>
00110 
00111 #include <pro/httpd.h>
00112 #include <pro/ssi.h>
00113 #include "httpd_p.h"
00114 #include "dencode.h"
00115 
00116 #define BUFSIZE 512
00117 
00118 #define MIN(a,b) (a<b?a:b)
00119 
00120 #define SSI_TYPE_FILE    0x01
00121 #define SSI_TYPE_VIRTUAL 0x02
00122 #define SSI_TYPE_EXEC    0x03
00123 #define SSI_TYPE_ECHO    0x04
00124 
00125 static char * (*ssivar_handler)(char *, REQUEST *);
00126 
00127 static prog_char rsp_not_found_P[] = "404 Not found: %s\r\n";
00128 static prog_char rsp_intern_err_P[] = "500 Internal error\r\n";
00129 static prog_char rsp_bad_req_P[] = "400 Bad request\r\n";
00130 
00131 extern char *cgiBinPath;
00132 
00143 static void NutSsiProcessFile(FILE * stream, char *filename)
00144 {
00145     int fd;
00146     int n;
00147     char *data;
00148     int size;
00149     long file_len;
00150 
00151     fd = _open(filename, _O_BINARY | _O_RDONLY);
00152 
00153     if (fd == -1) {                     // No such file found... send a 404 string.
00154         fprintf_P(stream, rsp_not_found_P, filename);
00155         return;
00156     }
00157     
00158     file_len = _filelength(fd);
00159     
00160     size = 512;
00161     if ((data = malloc(size)) != NULL) {
00162         while (file_len) {
00163             if (file_len < 512L)
00164                 size = (int) file_len;
00165 
00166             n = _read(fd, data, size);
00167             if (fwrite(data, 1, n, stream) == 0)
00168                 break;
00169             file_len -= (long) n;
00170         }
00171         free(data);
00172     }
00173 
00174     _close(fd);
00175 }
00176 
00177 
00191 static void NutSsiProcessVirtual(FILE * stream, char *url, char* http_root, REQUEST *orig_req)
00192 {
00193     int fd;
00194     int i;
00195     int n;
00196     char *data;
00197     int size;
00198     long file_len;
00199     char *filename = NULL;
00200     void (*handler)(FILE *stream, int fd, int file_len, char *http_root, REQUEST *req);
00201     char *cp;
00202     REQUEST * req;
00203     CONST char *cgi_bin = cgiBinPath ? cgiBinPath : "cgi-bin/";
00204     CONST char * tmp;
00205     size_t len;
00206 
00207     tmp = cgi_bin;
00208     if (NutDecodePath(url) == 0) {
00209         fprintf_P(stream, rsp_bad_req_P);
00210         return;
00211     }
00212   
00213     /*
00214      * Process CGI.
00215      */
00216     while (tmp) {
00217         /* Skip leading path separators. */
00218         while (*cgi_bin == ';')
00219             cgi_bin++;
00220         /* Determine the length of the next path component. */
00221         for (len = 0, cp = (char *)cgi_bin; *cp && *cp != ';'; len++, cp++);
00222         tmp = cgi_bin;
00223         if (len && strncasecmp(url, tmp, len) == 0) {
00224             if ((req = calloc(1, sizeof(REQUEST))) == 0) {
00225                 fprintf_P(stream, rsp_intern_err_P);
00226                 return;
00227             }
00228             req->req_method = METHOD_GET;
00229             req->req_version = orig_req->req_version;
00230             req->req_length = 0;
00231             
00232             if (orig_req->req_agent != NULL) {
00233                 if ((req->req_agent = strdup(orig_req->req_agent)) == NULL) {
00234                     fprintf_P(stream, rsp_intern_err_P);
00235                     DestroyRequestInfo(req);
00236                     return;
00237                 }
00238             }
00239             if (orig_req->req_cookie!= NULL) {
00240                 if ((req->req_cookie = strdup(orig_req->req_cookie)) == NULL) {
00241                     fprintf_P(stream, rsp_intern_err_P);
00242                     DestroyRequestInfo(req);
00243                     return;
00244                 }
00245             }
00246             if ((cp = strchr(url, '?')) != 0) {
00247                 *cp++ = 0;
00248                 if (strcmp(cp, "$QUERY_STRING") == 0) {
00249                     uint16_t size;
00250                     size = 1; /* At least 1 for empty requests. */
00251                     for (i = 0; i < orig_req->req_numqptrs*2; i ++) {
00252                         size += strlen(orig_req->req_qptrs[i]) + 1;
00253                     }
00254                     if ((req->req_query = malloc(size)) == NULL) {
00255                         fprintf_P(stream, rsp_intern_err_P);
00256                         DestroyRequestInfo(req);
00257                         return;
00258                     }
00259                     req->req_query[0] = 0;
00260                     for (i = 0; i < (orig_req->req_numqptrs * 2); i++) {
00261                         if(i) {
00262                             strcat (req->req_query, "&");
00263                         }
00264                         strcat (req->req_query, orig_req->req_qptrs[i]);
00265                         strcat (req->req_query, "=");
00266                         i++;
00267                         strcat (req->req_query, orig_req->req_qptrs[i]);
00268                     }
00269 
00270                 } else {
00271                     if ((req->req_query = strdup(cp)) == NULL) {
00272                         fprintf_P(stream, rsp_intern_err_P);
00273                         DestroyRequestInfo(req);
00274                         return;
00275                     }
00276                 }
00277                 NutHttpProcessQueryString(req);
00278             }
00279             if ((req->req_url = strdup(url)) == NULL) {
00280                 fprintf_P(stream, rsp_intern_err_P);
00281                 DestroyRequestInfo(req);
00282                 return;
00283             }
00284             NutCgiProcessRequest(stream, req, len);
00285             DestroyRequestInfo(req);
00286             return;
00287         }
00288         cgi_bin += len;
00289         if (*cgi_bin == '\0') {
00290             break;
00291         }
00292     }
00293     
00294     /*
00295      * Process file.
00296      */
00297     
00298     for (n = 0, fd = -1; default_files[n]; n++) {
00299         filename = CreateFilePath(url, default_files[n]);
00300         if (filename == NULL) {
00301             fprintf_P(stream, rsp_intern_err_P);
00302             return;
00303         }
00304         /*
00305          * Note, that simple file systems may not provide stat() or access(),
00306          * thus trying to open the file is the only way to check for existence.
00307          * Another problem is, that PHAT allows to open directories. We use
00308          * the file length to ensure, that we got a normal file.
00309          */
00310         if ((fd = _open(filename, _O_BINARY | _O_RDONLY)) != -1) {
00311             if (_filelength(fd)) {
00312                 break;
00313             }
00314             _close(fd);
00315         }
00316         free(filename);
00317     }
00318     if (fd == -1) {
00319         fprintf_P(stream, rsp_not_found_P, url);
00320         return;
00321     }
00322     
00323     file_len = _filelength(fd);
00324     handler = NutGetMimeHandler(filename);
00325     free(filename);
00326     
00327     if (handler == NULL) {
00328         size = 512;                 // If we have not registered a mime handler handle default.
00329         if ((data = malloc(size)) != NULL) {
00330             while (file_len) {
00331                 if (file_len < 512L) {
00332                     size = (int) file_len;
00333                 }
00334 
00335                 n = _read(fd, data, size);
00336                 if (fwrite(data, 1, n, stream) == 0) {
00337                     break;
00338                 }
00339                 file_len -= (long) n;
00340             }
00341             free(data);
00342         }
00343     } else handler(stream, fd, file_len, http_root, orig_req);
00344     _close(fd);
00345     return;
00346 }
00347 
00355 static void NutSsiProcessEcho(FILE * stream, char *name, REQUEST *req)
00356 {
00357     if (ssivar_handler) {
00358         char *value = (*ssivar_handler)(name, req);
00359 
00360         if (value) {
00361             fputs(value, stream);
00362         }
00363     }
00364 }
00365 
00378 static void NutSsiSkipWhitespace(char *buffer, uint16_t *pos, uint16_t end)
00379 {
00380     while ((*pos < end) && (
00381            (buffer[*pos] == '\n') || (buffer[*pos] == '\r') ||
00382            (buffer[*pos] == '\t') || (buffer[*pos] == ' ')))
00383         (*pos) ++;
00384 }
00385 
00404 static uint8_t NutSsiCheckForSsi(FILE *stream, char *buffer, uint16_t end, char* http_root, REQUEST *req)
00405 {
00406     uint16_t pos = 4; // First character after comment start
00407     char * filename;
00408     uint8_t type;
00409 
00410     pos = 4;
00411     NutSsiSkipWhitespace(buffer, &pos, end);        // Skip whitespaces after comment start
00412     if (pos == end) return 0;
00413     
00414     if (strncasecmp(&buffer[pos], "#include", 8) == 0) { // Search include directive
00415         pos += 8;
00416         type = SSI_TYPE_VIRTUAL;
00417     } else 
00418     if (strncasecmp(&buffer[pos], "#exec", 5) == 0) { // Search include or exec directive
00419         pos += 5;
00420         type = SSI_TYPE_EXEC;
00421     } else 
00422     if (strncasecmp(&buffer[pos], "#echo", 5) == 0) { // Search echo directive
00423         pos += 5;
00424         type = SSI_TYPE_ECHO;
00425     } else return 0;                                // No include or exec found. Skip the rest of this comment...
00426     if (pos >= end) return 0;
00427 
00428     NutSsiSkipWhitespace(buffer, &pos, end);        // Skip whitespaces after #include directive
00429     if (pos == end) return 0;
00430     
00431     if (type == SSI_TYPE_VIRTUAL) {
00432         if (strncasecmp(&buffer[pos], "virtual", 7) == 0) {  // Search virtual directive
00433             pos += 7;
00434         } else                                      // No virtual found. Test for file...
00435         if (strncasecmp(&buffer[pos], "file", 4) == 0) {  // Search file directive
00436             pos += 4;
00437             type = SSI_TYPE_FILE;
00438         } else return 0;                            // No file found. Test for file...
00439     }
00440     else if (type == SSI_TYPE_ECHO) {
00441         if (strncasecmp(&buffer[pos], "var", 3) == 0) {  // Search var directive
00442             pos += 3;
00443         } else return 0;                            // No file found. Test for file...
00444     } else {
00445         if (strncasecmp(&buffer[pos], "cgi", 3) == 0) {  // Search cgi directive
00446             pos += 3;
00447         } else return 0;                            // No cgi found. return...
00448     }
00449     if (pos >= end) return 0;
00450     
00451     NutSsiSkipWhitespace(buffer, &pos, end);        // Skip whitespaces after virtual, file or cgi directive
00452     if (pos == end) return 0;
00453 
00454     if (buffer[pos] != '=') return 0;               // check for assertion
00455     pos ++; 
00456 
00457     NutSsiSkipWhitespace(buffer, &pos, end);        // Skip whitespaces after assertion
00458     if (pos == end) return 0;    
00459     
00460     if (buffer[pos] == '"') {                       // Search for filename and pass to output function
00461         pos ++;
00462         if (pos == end) return 0;
00463         filename = &buffer[pos];
00464         while (buffer[pos] != '"') {
00465             pos ++;
00466             if (pos == end) return 0;
00467         }
00468         buffer[pos] = '\0';
00469         switch (type) {
00470             case SSI_TYPE_FILE:
00471                 NutSsiProcessFile(stream, filename);
00472                 break;
00473             case SSI_TYPE_VIRTUAL:
00474                 NutSsiProcessVirtual(stream, filename, http_root, req);
00475                 break;
00476             case SSI_TYPE_EXEC:
00477                 NutSsiProcessVirtual(stream, filename, http_root, req);
00478                 break;
00479             case SSI_TYPE_ECHO:
00480                 NutSsiProcessEcho(stream, filename, req);
00481                 break;
00482         }
00483     }
00484     return 1;
00485 }
00486 
00506 static void NutHttpProcessSHTML(FILE * stream, int fd, int file_len, char* http_root, REQUEST *req)
00507 {
00508     char * buffer;
00509     uint8_t in_comment;
00510     int buffsize;
00511     int fpos;
00512     int n;
00513     char *index;
00514     uint8_t found;
00515     buffsize = MIN(BUFSIZE, file_len);
00516     buffer = malloc(buffsize+1);
00517     in_comment = 0;
00518     fpos = 0;
00519     while (file_len != fpos) {
00520         memset(buffer, 0, buffsize+1);
00521         n = _read(fd, buffer, MIN(buffsize, file_len-fpos));
00522         
00523         if (!in_comment) {        
00524             
00525             index = strstr(buffer, "<!--");
00526             if (index == NULL) {                    // Nothing found. print next 412 characters, seek to next startpoint.
00527                 if (file_len > buffsize) {
00528                     fwrite(buffer, 1, MIN(buffsize-100, n), stream);
00529                     fpos += MIN(buffsize-100, n);
00530                     _seek(fd, fpos, SEEK_SET);
00531                 } else {
00532                     fwrite(buffer, 1, n, stream);
00533                     fpos += n;
00534                 }
00535                 
00536             } else {
00537                 found = (int)index - (int)buffer;   // We have found a comment initializer. Seek to the startpoint and print the beginning of the buffer.
00538                 fwrite (buffer, 1, found, stream);
00539                 fpos += found;
00540                 _seek(fd, fpos, SEEK_SET);
00541                 in_comment = 1;
00542             }
00543         } else {                                    // Ok, we assume we are "into" a comment.    
00544             index = strstr(buffer, "-->");
00545             if (index == NULL) {                    // We have not found the end of the comment in the next 512 characters. Byepass this comment.
00546                 fwrite(buffer, 1, MIN(buffsize, n), stream);
00547                 fpos += MIN(buffsize, n);
00548                 in_comment = 0;
00549             } else {                                // Ok. This seems to be a comment with maximum length of 512 bytes. We now search for ssi code.    
00550                 found = (int)index - (int)buffer;
00551                 if (!NutSsiCheckForSsi(stream, buffer, found, http_root, req)) {
00552                     fwrite(buffer, 1, found+3, stream);
00553                 }
00554                 fpos += found+3;
00555                 _seek(fd, fpos, SEEK_SET);
00556                 in_comment = 0;
00557             }
00558         }
00559     }
00560     
00561     free(buffer);
00562 }
00563 
00575 void NutRegisterSsi(void)
00576 {
00577     NutSetMimeHandler(".shtml", NutHttpProcessSHTML);
00578 }
00579 
00583 int NutRegisterSsiVarHandler(char * (*handler)(char *name, REQUEST *req))
00584 {
00585     ssivar_handler = handler;
00586 
00587     return 0;
00588 }
00589