initial load of upstream version 1.06.32
[xmlrpc-c] / lib / abyss / src / response.c
1 /*=============================================================================
2                              response
3 ===============================================================================
4   This module contains callbacks from and services for a request handler.
5
6   Copyright information is at the end of the file
7 =============================================================================*/
8
9 #include <ctype.h>
10 #include <assert.h>
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <errno.h>
15 #include <time.h>
16
17 #include "xmlrpc_config.h"
18 #include "mallocvar.h"
19 #include "xmlrpc-c/string_int.h"
20 #include "xmlrpc-c/abyss.h"
21
22 #include "server.h"
23 #include "session.h"
24 #include "conn.h"
25 #include "token.h"
26 #include "date.h"
27 #include "data.h"
28 #include "abyss_info.h"
29 #include "http.h"
30
31
32
33 void
34 ResponseError(TSession * const sessionP) {
35
36     const char * const reason = HTTPReasonByStatus(sessionP->status);
37     const char * errorDocument;
38
39     ResponseAddField(sessionP, "Content-type", "text/html");
40
41     ResponseWriteStart(sessionP);
42     
43     xmlrpc_asprintf(&errorDocument,
44                     "<HTML><HEAD><TITLE>Error %d</TITLE></HEAD>"
45                     "<BODY><H1>Error %d</H1><P>%s</P>" SERVER_HTML_INFO 
46                     "</BODY></HTML>",
47                     sessionP->status, sessionP->status, reason);
48     
49     ConnWrite(sessionP->conn, errorDocument, strlen(errorDocument)); 
50
51     xmlrpc_strfree(errorDocument);
52 }
53
54
55
56 abyss_bool
57 ResponseChunked(TSession * const sessionP) {
58     /* This is only a hope, things will be real only after a call of
59        ResponseWriteStart()
60     */
61     assert(!sessionP->responseStarted);
62
63     sessionP->chunkedwrite =
64         (sessionP->version.major > 1) ||
65         (sessionP->version.major == 1 && (sessionP->version.minor >= 1));
66
67     sessionP->chunkedwritemode = TRUE;
68
69     return TRUE;
70 }
71
72
73
74 void
75 ResponseStatus(TSession * const sessionP,
76                uint16_t   const code) {
77
78     sessionP->status = code;
79 }
80
81
82
83 uint16_t
84 ResponseStatusFromErrno(int const errnoArg) {
85
86     uint16_t code;
87
88     switch (errnoArg) {
89     case EACCES:
90         code=403;
91         break;
92     case ENOENT:
93         code=404;
94         break;
95     default:
96         code=500;
97     }
98     return code;
99 }
100
101
102
103 void
104 ResponseStatusErrno(TSession * const sessionP) {
105
106     ResponseStatus(sessionP, ResponseStatusFromErrno(errno));
107 }
108
109
110
111 abyss_bool
112 ResponseAddField(TSession *   const sessionP,
113                  const char * const name,
114                  const char * const value) {
115
116     return TableAdd(&sessionP->response_headers, name, value);
117 }
118
119
120
121 static void
122 addDateHeader(TSession * const sessionP) {
123
124     char dateValue[64];
125     abyss_bool validDate;
126
127     validDate = DateToString(&sessionP->date, dateValue);
128
129     if (sessionP->status >= 200 && validDate)
130         ResponseAddField(sessionP, "Date", dateValue);
131 }
132
133
134
135 void
136 ResponseWriteStart(TSession * const sessionP) {
137
138     struct _TServer * const srvP = ConnServer(sessionP->conn)->srvP;
139
140     unsigned int i;
141
142     assert(!sessionP->responseStarted);
143
144     if (sessionP->status == 0) {
145         // Handler hasn't set status.  That's an error
146         sessionP->status = 500;
147     }
148
149     sessionP->responseStarted = TRUE;
150
151     {
152         const char * const reason = HTTPReasonByStatus(sessionP->status);
153         const char * line;
154         xmlrpc_asprintf(&line,"HTTP/1.1 %u %s\r\n", sessionP->status, reason);
155         ConnWrite(sessionP->conn, line, strlen(line));
156         xmlrpc_strfree(line);
157     }
158
159     if (HTTPKeepalive(sessionP)) {
160         const char * keepaliveValue;
161         
162         ResponseAddField(sessionP, "Connection", "Keep-Alive");
163
164         xmlrpc_asprintf(&keepaliveValue, "timeout=%u, max=%u",
165                         srvP->keepalivetimeout, srvP->keepalivemaxconn);
166
167         ResponseAddField(sessionP, "Keep-Alive", keepaliveValue);
168
169         xmlrpc_strfree(keepaliveValue);
170     } else
171         ResponseAddField(sessionP, "Connection", "close");
172     
173     if (sessionP->chunkedwrite && sessionP->chunkedwritemode)
174         ResponseAddField(sessionP, "Transfer-Encoding", "chunked");
175
176     addDateHeader(sessionP);
177
178     /* Generation of the server field */
179     if (srvP->advertise)
180         ResponseAddField(sessionP, "Server", SERVER_HVERSION);
181
182     /* send all the fields */
183     for (i = 0; i < sessionP->response_headers.size; ++i) {
184         TTableItem * const ti = &sessionP->response_headers.item[i];
185         const char * line;
186         xmlrpc_asprintf(&line, "%s: %s\r\n", ti->name, ti->value);
187         ConnWrite(sessionP->conn, line, strlen(line));
188         xmlrpc_strfree(line);
189     }
190
191     ConnWrite(sessionP->conn, "\r\n", 2);  
192 }
193
194
195
196 abyss_bool
197 ResponseWriteBody(TSession *   const sessionP,
198                   const char * const data,
199                   uint32_t     const len) {
200
201     return HTTPWriteBodyChunk(sessionP, data, len);
202 }
203
204
205
206 abyss_bool
207 ResponseWriteEnd(TSession * const sessionP) {
208
209     return HTTPWriteEndChunk(sessionP);
210 }
211
212
213
214 abyss_bool
215 ResponseContentType(TSession *   const serverP,
216                     const char * const type) {
217
218     return ResponseAddField(serverP, "Content-type", type);
219 }
220
221
222
223 abyss_bool
224 ResponseContentLength(TSession * const sessionP,
225                       uint64_t   const len) {
226     char contentLengthValue[32];
227     
228     sprintf(contentLengthValue, "%llu", len);
229
230     return ResponseAddField(sessionP, "Content-length", contentLengthValue);
231 }
232
233
234 /*********************************************************************
235 ** MIMEType
236 *********************************************************************/
237
238 struct MIMEType {
239     TList typeList;
240     TList extList;
241     TPool pool;
242 };
243
244
245 static MIMEType * globalMimeTypeP = NULL;
246
247
248
249 MIMEType *
250 MIMETypeCreate(void) {
251  
252     MIMEType * MIMETypeP;
253
254     MALLOCVAR(MIMETypeP);
255
256     if (MIMETypeP) {
257         ListInit(&MIMETypeP->typeList);
258         ListInit(&MIMETypeP->extList);
259         PoolCreate(&MIMETypeP->pool, 1024);
260     }
261     return MIMETypeP;
262 }
263
264
265
266 void
267 MIMETypeDestroy(MIMEType * const MIMETypeP) {
268
269     PoolFree(&MIMETypeP->pool);
270 }
271
272
273
274 void
275 MIMETypeInit(void) {
276
277     if (globalMimeTypeP != NULL)
278         abort();
279
280     globalMimeTypeP = MIMETypeCreate();
281 }
282
283
284
285 void
286 MIMETypeTerm(void) {
287
288     if (globalMimeTypeP == NULL)
289         abort();
290
291     MIMETypeDestroy(globalMimeTypeP);
292
293     globalMimeTypeP = NULL;
294 }
295
296
297
298 static void
299 mimeTypeAdd(MIMEType *   const MIMETypeP,
300             const char * const type,
301             const char * const ext,
302             abyss_bool * const successP) {
303     
304     uint16_t index;
305     void * mimeTypesItem;
306     abyss_bool typeIsInList;
307
308     assert(MIMETypeP != NULL);
309
310     typeIsInList = ListFindString(&MIMETypeP->typeList, type, &index);
311     if (typeIsInList)
312         mimeTypesItem = MIMETypeP->typeList.item[index];
313     else
314         mimeTypesItem = (void*)PoolStrdup(&MIMETypeP->pool, type);
315
316     if (mimeTypesItem) {
317         abyss_bool extIsInList;
318         extIsInList = ListFindString(&MIMETypeP->extList, ext, &index);
319         if (extIsInList) {
320             MIMETypeP->typeList.item[index] = mimeTypesItem;
321             *successP = TRUE;
322         } else {
323             void * extItem = (void*)PoolStrdup(&MIMETypeP->pool, ext);
324             if (extItem) {
325                 abyss_bool addedToMimeTypes;
326
327                 addedToMimeTypes =
328                     ListAdd(&MIMETypeP->typeList, mimeTypesItem);
329                 if (addedToMimeTypes) {
330                     abyss_bool addedToExt;
331                     
332                     addedToExt = ListAdd(&MIMETypeP->extList, extItem);
333                     *successP = addedToExt;
334                     if (!*successP)
335                         ListRemove(&MIMETypeP->typeList);
336                 } else
337                     *successP = FALSE;
338                 if (!*successP)
339                     PoolReturn(&MIMETypeP->pool, extItem);
340             } else
341                 *successP = FALSE;
342         }
343     } else
344         *successP = FALSE;
345 }
346
347
348
349
350 abyss_bool
351 MIMETypeAdd2(MIMEType *   const MIMETypeArg,
352              const char * const type,
353              const char * const ext) {
354
355     MIMEType * MIMETypeP = MIMETypeArg ? MIMETypeArg : globalMimeTypeP;
356
357     abyss_bool success;
358
359     if (MIMETypeP == NULL)
360         success = FALSE;
361     else 
362         mimeTypeAdd(MIMETypeP, type, ext, &success);
363
364     return success;
365 }
366
367
368
369 abyss_bool
370 MIMETypeAdd(const char * const type,
371             const char * const ext) {
372
373     return MIMETypeAdd2(globalMimeTypeP, type, ext);
374 }
375
376
377
378 static const char *
379 mimeTypeFromExt(MIMEType *   const MIMETypeP,
380                 const char * const ext) {
381
382     const char * retval;
383     uint16_t extindex;
384     abyss_bool extIsInList;
385
386     assert(MIMETypeP != NULL);
387
388     extIsInList = ListFindString(&MIMETypeP->extList, ext, &extindex);
389     if (!extIsInList)
390         retval = NULL;
391     else
392         retval = MIMETypeP->typeList.item[extindex];
393     
394     return retval;
395 }
396
397
398
399 const char *
400 MIMETypeFromExt2(MIMEType *   const MIMETypeArg,
401                  const char * const ext) {
402
403     const char * retval;
404
405     MIMEType * MIMETypeP = MIMETypeArg ? MIMETypeArg : globalMimeTypeP;
406
407     if (MIMETypeP == NULL)
408         retval = NULL;
409     else
410         retval = mimeTypeFromExt(MIMETypeP, ext);
411
412     return retval;
413 }
414
415
416
417 const char *
418 MIMETypeFromExt(const char * const ext) {
419
420     return MIMETypeFromExt2(globalMimeTypeP, ext);
421 }
422
423
424
425 static void
426 findExtension(const char *  const fileName,
427               const char ** const extP) {
428
429     unsigned int extPos = 0;  /* stifle unset variable warning */
430         /* Running estimation of where in fileName[] the extension starts */
431     abyss_bool extFound;
432     unsigned int i;
433
434     /* We're looking for the last dot after the last slash */
435     for (i = 0, extFound = FALSE; fileName[i]; ++i) {
436         char const c = fileName[i];
437         
438         if (c == '.') {
439             extFound = TRUE;
440             extPos = i + 1;
441         }
442         if (c == '/')
443             extFound = FALSE;
444     }
445
446     if (extFound)
447         *extP = &fileName[extPos];
448     else
449         *extP = NULL;
450 }
451
452
453
454 static const char *
455 mimeTypeFromFileName(MIMEType *   const MIMETypeP,
456                      const char * const fileName) {
457
458     const char * retval;
459     const char * ext;
460
461     assert(MIMETypeP != NULL);
462     
463     findExtension(fileName, &ext);
464
465     if (ext)
466         retval = MIMETypeFromExt2(MIMETypeP, ext);
467     else
468         retval = "application/octet-stream";
469
470     return retval;
471 }
472
473
474
475 const char *
476 MIMETypeFromFileName2(MIMEType *   const MIMETypeArg,
477                       const char * const fileName) {
478
479     const char * retval;
480     
481     MIMEType * MIMETypeP = MIMETypeArg ? MIMETypeArg : globalMimeTypeP;
482
483     if (MIMETypeP == NULL)
484         retval = NULL;
485     else
486         retval = mimeTypeFromFileName(MIMETypeP, fileName);
487
488     return retval;
489 }
490
491
492
493 const char *
494 MIMETypeFromFileName(const char * const fileName) {
495
496     return MIMETypeFromFileName2(globalMimeTypeP, fileName);
497 }
498
499
500
501 static abyss_bool
502 fileContainsText(const char * const fileName) {
503 /*----------------------------------------------------------------------------
504    Return true iff we can read the contents of the file named 'fileName'
505    and see that it appears to be composed of plain text characters.
506 -----------------------------------------------------------------------------*/
507     abyss_bool retval;
508     abyss_bool fileOpened;
509     TFile file;
510
511     fileOpened = FileOpen(&file, fileName, O_BINARY | O_RDONLY);
512     if (fileOpened) {
513         char const ctlZ = 26;
514         unsigned char buffer[80];
515         int32_t readRc;
516         unsigned int i;
517
518         readRc = FileRead(&file, buffer, sizeof(buffer));
519        
520         if (readRc >= 0) {
521             unsigned int bytesRead = readRc;
522             abyss_bool nonTextFound;
523
524             nonTextFound = FALSE;  /* initial value */
525     
526             for (i = 0; i < bytesRead; ++i) {
527                 char const c = buffer[i];
528                 if (c < ' ' && !isspace(c) && c != ctlZ)
529                     nonTextFound = TRUE;
530             }
531             retval = !nonTextFound;
532         } else
533             retval = FALSE;
534         FileClose(&file);
535     } else
536         retval = FALSE;
537
538     return retval;
539 }
540
541
542  
543 static const char *
544 mimeTypeGuessFromFile(MIMEType *   const MIMETypeP,
545                       const char * const fileName) {
546
547     const char * retval;
548     const char * ext;
549
550     findExtension(fileName, &ext);
551
552     retval = NULL;
553
554     if (ext && MIMETypeP)
555         retval = MIMETypeFromExt2(MIMETypeP, ext);
556     
557     if (!retval) {
558         if (fileContainsText(fileName))
559             retval = "text/plain";
560         else
561             retval = "application/octet-stream";  
562     }
563     return retval;
564 }
565
566
567
568 const char *
569 MIMETypeGuessFromFile2(MIMEType *   const MIMETypeArg,
570                        const char * const fileName) {
571
572     return mimeTypeGuessFromFile(MIMETypeArg ? MIMETypeArg : globalMimeTypeP,
573                                  fileName);
574 }
575
576
577
578 const char *
579 MIMETypeGuessFromFile(const char * const fileName) {
580
581     return mimeTypeGuessFromFile(globalMimeTypeP, fileName);
582 }
583
584                                   
585
586 /*********************************************************************
587 ** Base64
588 *********************************************************************/
589
590 void Base64Encode(char *s,char *d)
591 {
592     /* Conversion table. */
593     static char tbl[64] = {
594         'A','B','C','D','E','F','G','H',
595         'I','J','K','L','M','N','O','P',
596         'Q','R','S','T','U','V','W','X',
597         'Y','Z','a','b','c','d','e','f',
598         'g','h','i','j','k','l','m','n',
599         'o','p','q','r','s','t','u','v',
600         'w','x','y','z','0','1','2','3',
601         '4','5','6','7','8','9','+','/'
602     };
603
604     uint32_t i,length=strlen(s);
605     char *p=d;
606     
607     /* Transform the 3x8 bits to 4x6 bits, as required by base64. */
608     for (i = 0; i < length; i += 3)
609     {
610         *p++ = tbl[s[0] >> 2];
611         *p++ = tbl[((s[0] & 3) << 4) + (s[1] >> 4)];
612         *p++ = tbl[((s[1] & 0xf) << 2) + (s[2] >> 6)];
613         *p++ = tbl[s[2] & 0x3f];
614         s += 3;
615     }
616     
617     /* Pad the result if necessary... */
618     if (i == length + 1)
619         *(p - 1) = '=';
620     else if (i == length + 2)
621         *(p - 1) = *(p - 2) = '=';
622     
623     /* ...and zero-terminate it. */
624     *p = '\0';
625 }
626
627 /******************************************************************************
628 **
629 ** http.c
630 **
631 ** Copyright (C) 2000 by Moez Mahfoudh <mmoez@bigfoot.com>.
632 ** All rights reserved.
633 **
634 ** Redistribution and use in source and binary forms, with or without
635 ** modification, are permitted provided that the following conditions
636 ** are met:
637 ** 1. Redistributions of source code must retain the above copyright
638 **    notice, this list of conditions and the following disclaimer.
639 ** 2. Redistributions in binary form must reproduce the above copyright
640 **    notice, this list of conditions and the following disclaimer in the
641 **    documentation and/or other materials provided with the distribution.
642 ** 3. The name of the author may not be used to endorse or promote products
643 **    derived from this software without specific prior written permission.
644 ** 
645 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
646 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
647 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
648 ** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
649 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
650 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
651 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
652 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
653 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
654 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
655 ** SUCH DAMAGE.
656 **
657 ******************************************************************************/