abyss_response.c

00001 /*=============================================================================
00002                              response
00003 ===============================================================================
00004   This module contains callbacks from and services for a request handler.
00005 
00006   Copyright information is at the end of the file
00007 =============================================================================*/
00008 
00009 #include <ctype.h>
00010 #include <assert.h>
00011 #include <stdlib.h>
00012 #include <stdio.h>
00013 #include <string.h>
00014 #include <errno.h>
00015 #include <time.h>
00016 
00017 #include <xmlrpc-c/config.h>
00018 #include "abyss_mallocvar.h"
00019 #include "abyss_xmlrpc_int.h"
00020 #include <xmlrpc-c/abyss.h>
00021 
00022 #include "abyss_server.h"
00023 #include "abyss_session.h"
00024 #include "abyss_conn.h"
00025 #include "abyss_token.h"
00026 #include "abyss_date.h"
00027 #include "abyss_data.h"
00028 #include "abyss_info.h"
00029 #include "abyss_http.h"
00030 
00031 
00032 
00033 void
00034 ResponseError(TSession * const sessionP) {
00035 
00036     const char * const reason = HTTPReasonByStatus(sessionP->status);
00037     const char * errorDocument;
00038 
00039     ResponseAddField(sessionP, "Content-type", "text/html");
00040 
00041     ResponseWriteStart(sessionP);
00042     
00043     xmlrpc_asprintf(&errorDocument,
00044                     "<HTML><HEAD><TITLE>Error %d</TITLE></HEAD>"
00045                     "<BODY><H1>Error %d</H1><P>%s</P>" SERVER_HTML_INFO 
00046                     "</BODY></HTML>",
00047                     sessionP->status, sessionP->status, reason);
00048     
00049     ConnWrite(sessionP->conn, errorDocument, strlen(errorDocument)); 
00050 
00051     xmlrpc_strfree(errorDocument);
00052 }
00053 
00054 
00055 
00056 abyss_bool
00057 ResponseChunked(TSession * const sessionP) {
00058     /* This is only a hope, things will be real only after a call of
00059        ResponseWriteStart()
00060     */
00061     assert(!sessionP->responseStarted);
00062 
00063     sessionP->chunkedwrite =
00064         (sessionP->version.major > 1) ||
00065         (sessionP->version.major == 1 && (sessionP->version.minor >= 1));
00066 
00067     sessionP->chunkedwritemode = TRUE;
00068 
00069     return TRUE;
00070 }
00071 
00072 
00073 
00074 void
00075 ResponseStatus(TSession * const sessionP,
00076                uint16_t   const code) {
00077 
00078     sessionP->status = code;
00079 }
00080 
00081 
00082 
00083 uint16_t
00084 ResponseStatusFromErrno(int const errnoArg) {
00085 
00086     uint16_t code;
00087 
00088     switch (errnoArg) {
00089     case EACCES:
00090         code=403;
00091         break;
00092     case ENOENT:
00093         code=404;
00094         break;
00095     default:
00096         code=500;
00097     }
00098     return code;
00099 }
00100 
00101 
00102 
00103 void
00104 ResponseStatusErrno(TSession * const sessionP) {
00105 
00106     ResponseStatus(sessionP, ResponseStatusFromErrno(errno));
00107 }
00108 
00109 
00110 
00111 abyss_bool
00112 ResponseAddField(TSession *   const sessionP,
00113                  const char * const name,
00114                  const char * const value) {
00115 
00116     return TableAdd(&sessionP->response_headers, name, value);
00117 }
00118 
00119 
00120 
00121 static void
00122 addDateHeader(TSession * const sessionP) {
00123 
00124     char dateValue[64];
00125     abyss_bool validDate;
00126 
00127     validDate = DateToString(&sessionP->date, dateValue);
00128 
00129     if (sessionP->status >= 200 && validDate)
00130         ResponseAddField(sessionP, "Date", dateValue);
00131 }
00132 
00133 
00134 
00135 void
00136 ResponseWriteStart(TSession * const sessionP) {
00137 
00138     struct _TServer * const srvP = ConnServer(sessionP->conn)->srvP;
00139 
00140     unsigned int i;
00141 
00142     assert(!sessionP->responseStarted);
00143 
00144     if (sessionP->status == 0) {
00145         // Handler hasn't set status.  That's an error
00146         sessionP->status = 500;
00147     }
00148 
00149     sessionP->responseStarted = TRUE;
00150 
00151     {
00152         const char * const reason = HTTPReasonByStatus(sessionP->status);
00153         const char * line;
00154         xmlrpc_asprintf(&line,"HTTP/1.1 %u %s\r\n", sessionP->status, reason);
00155         ConnWrite(sessionP->conn, line, strlen(line));
00156         xmlrpc_strfree(line);
00157     }
00158 
00159     if (HTTPKeepalive(sessionP)) {
00160         const char * keepaliveValue;
00161         
00162         ResponseAddField(sessionP, "Connection", "Keep-Alive");
00163 
00164         xmlrpc_asprintf(&keepaliveValue, "timeout=%u, max=%u",
00165                         srvP->keepalivetimeout, srvP->keepalivemaxconn);
00166 
00167         ResponseAddField(sessionP, "Keep-Alive", keepaliveValue);
00168 
00169         xmlrpc_strfree(keepaliveValue);
00170     } else
00171         ResponseAddField(sessionP, "Connection", "close");
00172     
00173     if (sessionP->chunkedwrite && sessionP->chunkedwritemode)
00174         ResponseAddField(sessionP, "Transfer-Encoding", "chunked");
00175 
00176     addDateHeader(sessionP);
00177 
00178     /* Generation of the server field */
00179     if (srvP->advertise)
00180         ResponseAddField(sessionP, "Server", SERVER_HVERSION);
00181 
00182     /* send all the fields */
00183     for (i = 0; i < sessionP->response_headers.size; ++i) {
00184         TTableItem * const ti = &sessionP->response_headers.item[i];
00185         const char * line;
00186         xmlrpc_asprintf(&line, "%s: %s\r\n", ti->name, ti->value);
00187         ConnWrite(sessionP->conn, line, strlen(line));
00188         xmlrpc_strfree(line);
00189     }
00190 
00191     ConnWrite(sessionP->conn, "\r\n", 2);  
00192 }
00193 
00194 
00195 
00196 abyss_bool
00197 ResponseWriteBody(TSession *   const sessionP,
00198                   const char * const data,
00199                   uint32_t     const len) {
00200 
00201     return HTTPWriteBodyChunk(sessionP, data, len);
00202 }
00203 
00204 
00205 
00206 abyss_bool
00207 ResponseWriteEnd(TSession * const sessionP) {
00208 
00209     return HTTPWriteEndChunk(sessionP);
00210 }
00211 
00212 
00213 
00214 abyss_bool
00215 ResponseContentType(TSession *   const serverP,
00216                     const char * const type) {
00217 
00218     return ResponseAddField(serverP, "Content-type", type);
00219 }
00220 
00221 
00222 
00223 abyss_bool
00224 ResponseContentLength(TSession * const sessionP,
00225                       uint64_t   const len) {
00226     char contentLengthValue[32];
00227     
00228     sprintf(contentLengthValue, "%llu", (long long unsigned int)len);
00229 
00230     return ResponseAddField(sessionP, "Content-length", contentLengthValue);
00231 }
00232 
00233 
00234 /*********************************************************************
00235 ** MIMEType
00236 *********************************************************************/
00237 
00238 struct MIMEType {
00239     TList typeList;
00240     TList extList;
00241     TPool pool;
00242 };
00243 
00244 
00245 static MIMEType * globalMimeTypeP = NULL;
00246 
00247 
00248 
00249 MIMEType *
00250 MIMETypeCreate(void) {
00251  
00252     MIMEType * MIMETypeP;
00253 
00254     MALLOCVAR(MIMETypeP);
00255 
00256     if (MIMETypeP) {
00257         ListInit(&MIMETypeP->typeList);
00258         ListInit(&MIMETypeP->extList);
00259         PoolCreate(&MIMETypeP->pool, 1024);
00260     }
00261     return MIMETypeP;
00262 }
00263 
00264 
00265 
00266 void
00267 MIMETypeDestroy(MIMEType * const MIMETypeP) {
00268 
00269     PoolFree(&MIMETypeP->pool);
00270 }
00271 
00272 
00273 
00274 void
00275 MIMETypeInit(void) {
00276 
00277     if (globalMimeTypeP != NULL)
00278         abort();
00279 
00280     globalMimeTypeP = MIMETypeCreate();
00281 }
00282 
00283 
00284 
00285 void
00286 MIMETypeTerm(void) {
00287 
00288     if (globalMimeTypeP == NULL)
00289         abort();
00290 
00291     MIMETypeDestroy(globalMimeTypeP);
00292 
00293     globalMimeTypeP = NULL;
00294 }
00295 
00296 
00297 
00298 static void
00299 mimeTypeAdd(MIMEType *   const MIMETypeP,
00300             const char * const type,
00301             const char * const ext,
00302             abyss_bool * const successP) {
00303     
00304     uint16_t index;
00305     void * mimeTypesItem;
00306     abyss_bool typeIsInList;
00307 
00308     assert(MIMETypeP != NULL);
00309 
00310     typeIsInList = ListFindString(&MIMETypeP->typeList, type, &index);
00311     if (typeIsInList)
00312         mimeTypesItem = MIMETypeP->typeList.item[index];
00313     else
00314         mimeTypesItem = (void*)PoolStrdup(&MIMETypeP->pool, type);
00315 
00316     if (mimeTypesItem) {
00317         abyss_bool extIsInList;
00318         extIsInList = ListFindString(&MIMETypeP->extList, ext, &index);
00319         if (extIsInList) {
00320             MIMETypeP->typeList.item[index] = mimeTypesItem;
00321             *successP = TRUE;
00322         } else {
00323             void * extItem = (void*)PoolStrdup(&MIMETypeP->pool, ext);
00324             if (extItem) {
00325                 abyss_bool addedToMimeTypes;
00326 
00327                 addedToMimeTypes =
00328                     ListAdd(&MIMETypeP->typeList, mimeTypesItem);
00329                 if (addedToMimeTypes) {
00330                     abyss_bool addedToExt;
00331                     
00332                     addedToExt = ListAdd(&MIMETypeP->extList, extItem);
00333                     *successP = addedToExt;
00334                     if (!*successP)
00335                         ListRemove(&MIMETypeP->typeList);
00336                 } else
00337                     *successP = FALSE;
00338                 if (!*successP)
00339                     PoolReturn(&MIMETypeP->pool, extItem);
00340             } else
00341                 *successP = FALSE;
00342         }
00343     } else
00344         *successP = FALSE;
00345 }
00346 
00347 
00348 
00349 
00350 abyss_bool
00351 MIMETypeAdd2(MIMEType *   const MIMETypeArg,
00352              const char * const type,
00353              const char * const ext) {
00354 
00355     MIMEType * MIMETypeP = MIMETypeArg ? MIMETypeArg : globalMimeTypeP;
00356 
00357     abyss_bool success;
00358 
00359     if (MIMETypeP == NULL)
00360         success = FALSE;
00361     else 
00362         mimeTypeAdd(MIMETypeP, type, ext, &success);
00363 
00364     return success;
00365 }
00366 
00367 
00368 
00369 abyss_bool
00370 MIMETypeAdd(const char * const type,
00371             const char * const ext) {
00372 
00373     return MIMETypeAdd2(globalMimeTypeP, type, ext);
00374 }
00375 
00376 
00377 
00378 static const char *
00379 mimeTypeFromExt(MIMEType *   const MIMETypeP,
00380                 const char * const ext) {
00381 
00382     const char * retval;
00383     uint16_t extindex;
00384     abyss_bool extIsInList;
00385 
00386     assert(MIMETypeP != NULL);
00387 
00388     extIsInList = ListFindString(&MIMETypeP->extList, ext, &extindex);
00389     if (!extIsInList)
00390         retval = NULL;
00391     else
00392         retval = MIMETypeP->typeList.item[extindex];
00393     
00394     return retval;
00395 }
00396 
00397 
00398 
00399 const char *
00400 MIMETypeFromExt2(MIMEType *   const MIMETypeArg,
00401                  const char * const ext) {
00402 
00403     const char * retval;
00404 
00405     MIMEType * MIMETypeP = MIMETypeArg ? MIMETypeArg : globalMimeTypeP;
00406 
00407     if (MIMETypeP == NULL)
00408         retval = NULL;
00409     else
00410         retval = mimeTypeFromExt(MIMETypeP, ext);
00411 
00412     return retval;
00413 }
00414 
00415 
00416 
00417 const char *
00418 MIMETypeFromExt(const char * const ext) {
00419 
00420     return MIMETypeFromExt2(globalMimeTypeP, ext);
00421 }
00422 
00423 
00424 
00425 static void
00426 findExtension(const char *  const fileName,
00427               const char ** const extP) {
00428 
00429     unsigned int extPos = 0;  /* stifle unset variable warning */
00430         /* Running estimation of where in fileName[] the extension starts */
00431     abyss_bool extFound;
00432     unsigned int i;
00433 
00434     /* We're looking for the last dot after the last slash */
00435     for (i = 0, extFound = FALSE; fileName[i]; ++i) {
00436         char const c = fileName[i];
00437         
00438         if (c == '.') {
00439             extFound = TRUE;
00440             extPos = i + 1;
00441         }
00442         if (c == '/')
00443             extFound = FALSE;
00444     }
00445 
00446     if (extFound)
00447         *extP = &fileName[extPos];
00448     else
00449         *extP = NULL;
00450 }
00451 
00452 
00453 
00454 static const char *
00455 mimeTypeFromFileName(MIMEType *   const MIMETypeP,
00456                      const char * const fileName) {
00457 
00458     const char * retval;
00459     const char * ext;
00460 
00461     assert(MIMETypeP != NULL);
00462     
00463     findExtension(fileName, &ext);
00464 
00465     if (ext)
00466         retval = MIMETypeFromExt2(MIMETypeP, ext);
00467     else
00468         retval = "application/octet-stream";
00469 
00470     return retval;
00471 }
00472 
00473 
00474 
00475 const char *
00476 MIMETypeFromFileName2(MIMEType *   const MIMETypeArg,
00477                       const char * const fileName) {
00478 
00479     const char * retval;
00480     
00481     MIMEType * MIMETypeP = MIMETypeArg ? MIMETypeArg : globalMimeTypeP;
00482 
00483     if (MIMETypeP == NULL)
00484         retval = NULL;
00485     else
00486         retval = mimeTypeFromFileName(MIMETypeP, fileName);
00487 
00488     return retval;
00489 }
00490 
00491 
00492 
00493 const char *
00494 MIMETypeFromFileName(const char * const fileName) {
00495 
00496     return MIMETypeFromFileName2(globalMimeTypeP, fileName);
00497 }
00498 
00499 
00500 
00501 static abyss_bool
00502 fileContainsText(const char * const fileName) {
00503 /*----------------------------------------------------------------------------
00504    Return true iff we can read the contents of the file named 'fileName'
00505    and see that it appears to be composed of plain text characters.
00506 -----------------------------------------------------------------------------*/
00507     abyss_bool retval;
00508     abyss_bool fileOpened;
00509     TFile file;
00510 
00511     fileOpened = FileOpen(&file, fileName, O_BINARY | O_RDONLY);
00512     if (fileOpened) {
00513         char const ctlZ = 26;
00514         unsigned char buffer[80];
00515         int32_t readRc;
00516         unsigned int i;
00517 
00518         readRc = FileRead(&file, buffer, sizeof(buffer));
00519        
00520         if (readRc >= 0) {
00521             unsigned int bytesRead = readRc;
00522             abyss_bool nonTextFound;
00523 
00524             nonTextFound = FALSE;  /* initial value */
00525     
00526             for (i = 0; i < bytesRead; ++i) {
00527                 char const c = buffer[i];
00528                 if (c < ' ' && !isspace(c) && c != ctlZ)
00529                     nonTextFound = TRUE;
00530             }
00531             retval = !nonTextFound;
00532         } else
00533             retval = FALSE;
00534         FileClose(&file);
00535     } else
00536         retval = FALSE;
00537 
00538     return retval;
00539 }
00540 
00541 
00542  
00543 static const char *
00544 mimeTypeGuessFromFile(MIMEType *   const MIMETypeP,
00545                       const char * const fileName) {
00546 
00547     const char * retval;
00548     const char * ext;
00549 
00550     findExtension(fileName, &ext);
00551 
00552     retval = NULL;
00553 
00554     if (ext && MIMETypeP)
00555         retval = MIMETypeFromExt2(MIMETypeP, ext);
00556     
00557     if (!retval) {
00558         if (fileContainsText(fileName))
00559             retval = "text/plain";
00560         else
00561             retval = "application/octet-stream";  
00562     }
00563     return retval;
00564 }
00565 
00566 
00567 
00568 const char *
00569 MIMETypeGuessFromFile2(MIMEType *   const MIMETypeArg,
00570                        const char * const fileName) {
00571 
00572     return mimeTypeGuessFromFile(MIMETypeArg ? MIMETypeArg : globalMimeTypeP,
00573                                  fileName);
00574 }
00575 
00576 
00577 
00578 const char *
00579 MIMETypeGuessFromFile(const char * const fileName) {
00580 
00581     return mimeTypeGuessFromFile(globalMimeTypeP, fileName);
00582 }
00583 
00584                                   
00585 
00586 /*********************************************************************
00587 ** Base64
00588 *********************************************************************/
00589 
00590 void Base64Encode(char *s,char *d)
00591 {
00592     /* Conversion table. */
00593     static char tbl[64] = {
00594         'A','B','C','D','E','F','G','H',
00595         'I','J','K','L','M','N','O','P',
00596         'Q','R','S','T','U','V','W','X',
00597         'Y','Z','a','b','c','d','e','f',
00598         'g','h','i','j','k','l','m','n',
00599         'o','p','q','r','s','t','u','v',
00600         'w','x','y','z','0','1','2','3',
00601         '4','5','6','7','8','9','+','/'
00602     };
00603 
00604     uint32_t i,length=strlen(s);
00605     char *p=d;
00606     
00607     /* Transform the 3x8 bits to 4x6 bits, as required by base64. */
00608     for (i = 0; i < length; i += 3)
00609     {
00610         *p++ = tbl[s[0] >> 2];
00611         *p++ = tbl[((s[0] & 3) << 4) + (s[1] >> 4)];
00612         *p++ = tbl[((s[1] & 0xf) << 2) + (s[2] >> 6)];
00613         *p++ = tbl[s[2] & 0x3f];
00614         s += 3;
00615     }
00616     
00617     /* Pad the result if necessary... */
00618     if (i == length + 1)
00619         *(p - 1) = '=';
00620     else if (i == length + 2)
00621         *(p - 1) = *(p - 2) = '=';
00622     
00623     /* ...and zero-terminate it. */
00624     *p = '\0';
00625 }
00626 
00627 /******************************************************************************
00628 **
00629 ** http.c
00630 **
00631 ** Copyright (C) 2000 by Moez Mahfoudh <mmoez@bigfoot.com>.
00632 ** All rights reserved.
00633 **
00634 ** Redistribution and use in source and binary forms, with or without
00635 ** modification, are permitted provided that the following conditions
00636 ** are met:
00637 ** 1. Redistributions of source code must retain the above copyright
00638 **    notice, this list of conditions and the following disclaimer.
00639 ** 2. Redistributions in binary form must reproduce the above copyright
00640 **    notice, this list of conditions and the following disclaimer in the
00641 **    documentation and/or other materials provided with the distribution.
00642 ** 3. The name of the author may not be used to endorse or promote products
00643 **    derived from this software without specific prior written permission.
00644 ** 
00645 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
00646 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00647 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00648 ** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
00649 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
00650 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
00651 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
00652 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00653 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
00654 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
00655 ** SUCH DAMAGE.
00656 **
00657 ******************************************************************************/