/*============================================================================= handler.c =============================================================================== This file contains built-in HTTP request handlers. Copyright information is at end of file =============================================================================*/ #include #include #include #include #include #include #ifdef WIN32 #include #else #include #endif #include #include "xmlrpc_config.h" #include "bool.h" #include "int.h" #include "girmath.h" #include "mallocvar.h" #include "xmlrpc-c/string_int.h" #include "xmlrpc-c/time_int.h" #include "xmlrpc-c/abyss.h" #include "trace.h" #include "session.h" #include "file.h" #include "conn.h" #include "http.h" #include "date.h" #include "abyss_info.h" #include "handler.h" struct BIHandler { const char * filesPath; TList defaultFileNames; MIMEType * mimeTypeP; /* NULL means to use the global MIMEType object */ }; BIHandler * HandlerCreate(void) { struct BIHandler * handlerP; MALLOCVAR(handlerP); if (handlerP) { handlerP->filesPath = strdup(DEFAULT_DOCS); ListInitAutoFree(&handlerP->defaultFileNames); handlerP->mimeTypeP = NULL; } return handlerP; } void HandlerDestroy(BIHandler * const handlerP) { ListFree(&handlerP->defaultFileNames); xmlrpc_strfree(handlerP->filesPath); free(handlerP); } void HandlerSetMimeType(BIHandler * const handlerP, MIMEType * const mimeTypeP) { handlerP->mimeTypeP = mimeTypeP; } void HandlerSetFilesPath(BIHandler * const handlerP, const char * const filesPath) { xmlrpc_strfree(handlerP->filesPath); handlerP->filesPath = strdup(filesPath); } void HandlerAddDefaultFN(BIHandler * const handlerP, const char * const fileName) { ListAdd(&handlerP->defaultFileNames, strdup(fileName)); } typedef int (*TQSortProc)(const void *, const void *); static int cmpfilenames(const TFileInfo **f1,const TFileInfo **f2) { if (((*f1)->attrib & A_SUBDIR) && !((*f2)->attrib & A_SUBDIR)) return (-1); if (!((*f1)->attrib & A_SUBDIR) && ((*f2)->attrib & A_SUBDIR)) return 1; return strcmp((*f1)->name,(*f2)->name); } static int cmpfiledates(const TFileInfo ** const f1PP, const TFileInfo ** const f2PP) { const TFileInfo * const f1P = *f1PP; const TFileInfo * const f2P = *f2PP; int retval; if ((f1P->attrib & A_SUBDIR) && !(f2P->attrib & A_SUBDIR)) retval = -1; else if (!(f1P->attrib & A_SUBDIR) && (f2P->attrib & A_SUBDIR)) retval = 1; else { assert((int)(f1P->time_write - f2P->time_write) == (f1P->time_write - f2P->time_write)); retval = (int)(f1P->time_write - f2P->time_write); } return retval; } static void determineSortType(const char * const query, bool * const ascendingP, uint16_t * const sortP, bool * const textP, const char ** const errorP) { *ascendingP = TRUE; *sortP = 1; *textP = FALSE; *errorP = NULL; if (query) { if (xmlrpc_streq(query, "plain")) *textP = TRUE; else if (xmlrpc_streq(query, "name-up")) { *sortP = 1; *ascendingP = TRUE; } else if (xmlrpc_streq(query, "name-down")) { *sortP = 1; *ascendingP = FALSE; } else if (xmlrpc_streq(query, "date-up")) { *sortP = 2; *ascendingP = TRUE; } else if (xmlrpc_streq(query, "date-down")) { *sortP = 2; *ascendingP = FALSE; } else { xmlrpc_asprintf(errorP, "invalid query value '%s'", query); } } } static void generateListing(TList * const listP, const char * const dirName, const char * const uri, TPool * const poolP, const char ** const errorP, uint16_t * const responseStatusP) { TFileInfo fileinfo; TFileFind * findhandleP; *errorP = NULL; if (!FileFindFirst(&findhandleP, dirName, &fileinfo)) { *responseStatusP = ResponseStatusFromErrno(errno); xmlrpc_asprintf(errorP, "Can't read first entry in directory"); } else { ListInit(listP); do { TFileInfo * fi; /* Files whose names start with a dot are ignored */ /* This includes implicitly the ./ and ../ */ if (*fileinfo.name == '.') { if (xmlrpc_streq(fileinfo.name, "..")) { if (xmlrpc_streq(uri, "/")) continue; } else continue; } fi = (TFileInfo *)PoolAlloc(poolP, sizeof(fileinfo)); if (fi) { bool success; memcpy(fi, &fileinfo, sizeof(fileinfo)); success = ListAdd(listP, fi); if (!success) xmlrpc_asprintf(errorP, "ListAdd() failed"); } else xmlrpc_asprintf(errorP, "PoolAlloc() failed."); } while (!*errorP && FileFindNext(findhandleP, &fileinfo)); if (*errorP) { *responseStatusP = 500; ListFree(listP); } FileFindClose(findhandleP); } } static void sendDirectoryDocument(TList * const listP, bool const ascending, uint16_t const sort, bool const text, const char * const uri, MIMEType * const mimeTypeP, TSession * const sessionP) { char z[4096]; char *p,z1[26],z2[20],z3[9],u; const char * z4; int16_t i; uint32_t k; if (text) { sprintf(z, "Index of %s" CRLF, uri); i = strlen(z)-2; p = z + i + 2; while (i > 0) { *(p++) = '-'; --i; } *p = '\0'; strcat(z, CRLF CRLF "Name Size " "Date-Time Type" CRLF "------------------------------------" "--------------------------------------------"CRLF); } else { sprintf(z, "Index of %s" "

Index of %s

",
                uri, uri);
        strcat(z, "Name                      Size      "
               "Date-Time             Type
"CRLF); } HTTPWriteBodyChunk(sessionP, z, strlen(z)); /* Sort the files */ qsort(listP->item, listP->size, sizeof(void *), (TQSortProc)(sort == 1 ? cmpfilenames : cmpfiledates)); /* Write the listing */ if (ascending) i = 0; else i = listP->size - 1; while ((i < listP->size) && (i >= 0)) { TFileInfo * fi; struct tm ftm; fi = listP->item[i]; if (ascending) ++i; else --i; strcpy(z, fi->name); k = strlen(z); if (fi->attrib & A_SUBDIR) { z[k++] = '/'; z[k] = '\0'; } if (k > 24) { z[10] = '\0'; strcpy(z1, z); strcat(z1, "..."); strcat(z1, z + k - 11); k = 24; p = z1 + 24; } else { strcpy(z1, z); ++k; p = z1 + k; while (k < 25) z1[k++] = ' '; z1[25] = '\0'; } xmlrpc_gmtime(fi->time_write, &ftm); sprintf(z2, "%02u/%02u/%04u %02u:%02u:%02u",ftm.tm_mday,ftm.tm_mon+1, ftm.tm_year+1900,ftm.tm_hour,ftm.tm_min,ftm.tm_sec); if (fi->attrib & A_SUBDIR) { strcpy(z3, " -- "); z4 = "Directory"; } else { if (fi->size < 9999) u = 'b'; else { fi->size /= 1024; if (fi->size < 9999) u = 'K'; else { fi->size /= 1024; if (fi->size < 9999) u = 'M'; else u = 'G'; } } sprintf(z3, "%5" PRIu64 " %c", fi->size, u); if (xmlrpc_streq(fi->name, "..")) z4 = ""; else z4 = MIMETypeFromFileName2(mimeTypeP, fi->name); if (!z4) z4 = "Unknown"; } if (text) sprintf(z, "%s%s %s %s %s"CRLF, z1, p, z3, z2, z4); else sprintf(z, "%s%s %s %s %s"CRLF, fi->name, fi->attrib & A_SUBDIR ? "/" : "", z1, p, z3, z2, z4); HTTPWriteBodyChunk(sessionP, z, strlen(z)); } /* Write the tail of the file */ if (text) strcpy(z, SERVER_PLAIN_INFO); else strcpy(z, "
" SERVER_HTML_INFO "" CRLF CRLF); HTTPWriteBodyChunk(sessionP, z, strlen(z)); } static bool notRecentlyModified(TSession * const sessionP, time_t const fileModTime) { bool retval; const char * imsHdr; imsHdr = RequestHeaderValue(sessionP, "if-modified-since"); if (imsHdr) { bool valid; time_t datetime; DateDecode(imsHdr, &valid, &datetime); if (valid) { if (MIN(fileModTime, sessionP->date) <= datetime) retval = TRUE; else retval = FALSE; } else retval = FALSE; } else retval = FALSE; return retval; } static void addLastModifiedHeader(TSession * const sessionP, time_t const fileModTime) { const char * lastModifiedValue; DateToString(MIN(fileModTime, sessionP->date), &lastModifiedValue); if (lastModifiedValue) { ResponseAddField(sessionP, "Last-Modified", lastModifiedValue); xmlrpc_strfree(lastModifiedValue); } } static void handleDirectory(TSession * const sessionP, const char * const dirName, time_t const fileModTime, MIMEType * const mimeTypeP) { bool text; bool ascending; uint16_t sort; /* 1=by name, 2=by date */ const char * error; determineSortType(sessionP->requestInfo.query, &ascending, &sort, &text, &error); if (error) { ResponseStatus(sessionP, 400); xmlrpc_strfree(error); } else if (notRecentlyModified(sessionP, fileModTime)) { ResponseStatus(sessionP, 304); ResponseWriteStart(sessionP); } else { TPool pool; bool succeeded; succeeded = PoolCreate(&pool, 1024); if (!succeeded) ResponseStatus(sessionP, 500); else { TList list; uint16_t responseStatus; const char * error; generateListing(&list, dirName, sessionP->requestInfo.uri, &pool, &error, &responseStatus); if (error) { ResponseStatus(sessionP, responseStatus); xmlrpc_strfree(error); } else { ResponseStatus(sessionP, 200); ResponseContentType(sessionP, text ? "text/plain" : "text/html"); addLastModifiedHeader(sessionP, fileModTime); ResponseChunked(sessionP); ResponseWriteStart(sessionP); if (sessionP->requestInfo.method!=m_head) sendDirectoryDocument(&list, ascending, sort, text, sessionP->requestInfo.uri, mimeTypeP, sessionP); HTTPWriteEndChunk(sessionP); ListFree(&list); } PoolFree(&pool); } } } static void composeEntityHeader(const char ** const entityHeaderP, const char * const mediatype, uint64_t const start, uint64_t const end, uint64_t const filesize) { xmlrpc_asprintf(entityHeaderP, "Content-type: %s" CRLF "Content-range: " "bytes %" PRIu64 "-%" PRIu64 "/%" PRIu64 CRLF "Content-length: %" PRIu64 CRLF CRLF, mediatype, start, end, filesize, end-start+1); } #define BOUNDARY "##123456789###BOUNDARY" static void sendBody(TSession * const sessionP, const TFile * const fileP, uint64_t const filesize, const char * const mediatype, uint64_t const start0, uint64_t const end0) { /*---------------------------------------------------------------------------- 'start0' and 'end0' are meaningful only if the session has ranges. -----------------------------------------------------------------------------*/ char buffer[4096]; if (sessionP->ranges.size == 0) ConnWriteFromFile(sessionP->conn, fileP, 0, filesize - 1, buffer, sizeof(buffer), 0); else if (sessionP->ranges.size == 1) ConnWriteFromFile(sessionP->conn, fileP, start0, end0, buffer, sizeof(buffer), 0); else { uint64_t i; for (i = 0; i <= sessionP->ranges.size; ++i) { ConnWrite(sessionP->conn, "--", 2); ConnWrite(sessionP->conn, BOUNDARY, strlen(BOUNDARY)); ConnWrite(sessionP->conn, CRLF, 2); if (i < sessionP->ranges.size) { uint64_t start; uint64_t end; bool decoded; decoded = RangeDecode((char *)(sessionP->ranges.item[i]), filesize, &start, &end); if (decoded) { /* Entity header, not response header */ const char * entityHeader; composeEntityHeader(&entityHeader, mediatype, start, end, filesize); ConnWrite(sessionP->conn, entityHeader, strlen(entityHeader)); xmlrpc_strfree(entityHeader); ConnWriteFromFile(sessionP->conn, fileP, start, end, buffer, sizeof(buffer), 0); } } } } } static void sendFileAsResponse(TSession * const sessionP, TFile * const fileP, const char * const fileName, time_t const fileModTime, MIMEType * const mimeTypeP) { uint64_t const filesize = FileSize(fileP); const char * const mediatype = MIMETypeGuessFromFile2(mimeTypeP, fileName); uint64_t start; /* Defined only if session has one range */ uint64_t end; /* Defined only if session has one range */ switch (sessionP->ranges.size) { case 0: ResponseStatus(sessionP, 200); break; case 1: { bool decoded; decoded = RangeDecode((char *)(sessionP->ranges.item[0]), filesize, &start, &end); if (!decoded) { ListFree(&sessionP->ranges); ResponseStatus(sessionP, 200); } else { const char * contentRange; xmlrpc_asprintf(&contentRange, "bytes %" PRIu64 "-%" PRIu64 "/%" PRIu64, start, end, filesize); ResponseAddField(sessionP, "Content-range", contentRange); xmlrpc_strfree(contentRange); ResponseContentLength(sessionP, end - start + 1); ResponseStatus(sessionP, 206); } } break; default: ResponseContentType(sessionP, "multipart/ranges; boundary=" BOUNDARY); ResponseStatus(sessionP, 206); break; } if (sessionP->ranges.size == 0) { ResponseContentLength(sessionP, filesize); ResponseContentType(sessionP, mediatype); } addLastModifiedHeader(sessionP, fileModTime); ResponseWriteStart(sessionP); if (sessionP->requestInfo.method != m_head) sendBody(sessionP, fileP, filesize, mediatype, start, end); } static void handleFile(TSession * const sessionP, const char * const fileName, time_t const fileModTime, MIMEType * const mimeTypeP) { /*---------------------------------------------------------------------------- This is an HTTP request handler for a GET. It does the classic web server thing: send the file named in the URL to the client. -----------------------------------------------------------------------------*/ TFile * fileP; bool success; success = FileOpen(&fileP, fileName, O_BINARY | O_RDONLY); if (!success) ResponseStatusErrno(sessionP); else { if (notRecentlyModified(sessionP, fileModTime)) { ResponseStatus(sessionP, 304); ResponseWriteStart(sessionP); } else sendFileAsResponse(sessionP, fileP, fileName, fileModTime, mimeTypeP); FileClose(fileP); } } static void convertToNativeFileName(char * const fileName ATTR_UNUSED) { #ifdef WIN32 char * p; p = &fileName[0]; while (*p) { if ((*p) == '/') *p= '\\'; ++p; } #endif /* WIN32 */ } abyss_bool HandlerDefaultBuiltin(TSession * const sessionP) { BIHandler * const handlerP = SessionGetDefaultHandlerCtx(sessionP); char * p; char z[4096]; TFileStat fs; bool endingslash; endingslash = FALSE; /* initial value */ if (!RequestValidURIPath(sessionP)) { ResponseStatus(sessionP, 400); return TRUE; } /* Must check for * (asterisk uri) in the future */ if (sessionP->requestInfo.method == m_options) { ResponseAddField(sessionP, "Allow", "GET, HEAD"); ResponseContentLength(sessionP, 0); ResponseStatus(sessionP, 200); return TRUE; } if ((sessionP->requestInfo.method != m_get) && (sessionP->requestInfo.method != m_head)) { ResponseAddField(sessionP, "Allow", "GET, HEAD"); ResponseStatus(sessionP, 405); return TRUE; } strcpy(z, handlerP->filesPath); strcat(z, sessionP->requestInfo.uri); p = z + strlen(z) - 1; if (*p == '/') { endingslash = TRUE; *p = '\0'; } convertToNativeFileName(z); if (!FileStat(z, &fs)) { ResponseStatusErrno(sessionP); return TRUE; } if (fs.st_mode & S_IFDIR) { /* Redirect to the same directory but with the ending slash ** to avoid problems with some browsers (IE for examples) when ** they generate relative urls */ if (!endingslash) { strcpy(z, sessionP->requestInfo.uri); p = z+strlen(z); *p = '/'; *(p+1) = '\0'; ResponseAddField(sessionP, "Location", z); ResponseStatus(sessionP, 302); ResponseWriteStart(sessionP); return TRUE; } *p = DIRECTORY_SEPARATOR[0]; ++p; { unsigned int i; i = handlerP->defaultFileNames.size; while (i-- > 0) { *p = '\0'; strcat(z, (handlerP->defaultFileNames.item[i])); if (FileStat(z, &fs)) { if (!(fs.st_mode & S_IFDIR)) handleFile(sessionP, z, fs.st_mtime, handlerP->mimeTypeP); } } } *(p-1) = '\0'; if (!FileStat(z, &fs)) { ResponseStatusErrno(sessionP); return TRUE; } handleDirectory(sessionP, z, fs.st_mtime, handlerP->mimeTypeP); } else handleFile(sessionP, z, fs.st_mtime, handlerP->mimeTypeP); return TRUE; } /****************************************************************************** ** ** server.c ** ** This file is part of the ABYSS Web server project. ** ** Copyright (C) 2000 by Moez Mahfoudh . ** 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. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. ** ******************************************************************************/