initial load of upstream version 1.06.32
[xmlrpc-c] / lib / abyss / src / http.c
1 /* Copyright information is at the end of the file */
2
3 #include <ctype.h>
4 #include <assert.h>
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <string.h>
8 #include <errno.h>
9 #include <time.h>
10
11 #include "xmlrpc_config.h"
12 #include "mallocvar.h"
13 #include "xmlrpc-c/string_int.h"
14 #include "xmlrpc-c/abyss.h"
15
16 #include "server.h"
17 #include "session.h"
18 #include "conn.h"
19 #include "token.h"
20 #include "date.h"
21 #include "data.h"
22 #include "abyss_info.h"
23
24 #include "http.h"
25
26 /*********************************************************************
27 ** Request Parser
28 *********************************************************************/
29
30 /*********************************************************************
31 ** Request
32 *********************************************************************/
33
34 static void
35 initRequestInfo(TRequestInfo * const requestInfoP,
36                 httpVersion    const httpVersion,
37                 const char *   const requestLine,
38                 TMethod        const httpMethod,
39                 const char *   const host,
40                 unsigned int   const port,
41                 const char *   const path,
42                 const char *   const query) {
43 /*----------------------------------------------------------------------------
44   Set up the request info structure.  For information that is
45   controlled by headers, use the defaults -- I.e. the value that
46   applies if the request contains no applicable header.
47 -----------------------------------------------------------------------------*/
48     requestInfoP->requestline = requestLine;
49     requestInfoP->method      = httpMethod;
50     requestInfoP->host        = host;
51     requestInfoP->port        = port;
52     requestInfoP->uri         = path;
53     requestInfoP->query       = query;
54     requestInfoP->from        = NULL;
55     requestInfoP->useragent   = NULL;
56     requestInfoP->referer     = NULL;
57     requestInfoP->user        = NULL;
58
59     if (httpVersion.major > 1 ||
60         (httpVersion.major == 1 && httpVersion.minor >= 1))
61         requestInfoP->keepalive = TRUE;
62     else
63         requestInfoP->keepalive = FALSE;
64 }
65
66
67
68 static void
69 freeRequestInfo(TRequestInfo * const requestInfoP) {
70
71     if (requestInfoP->requestline)
72         xmlrpc_strfree(requestInfoP->requestline);
73
74     if (requestInfoP->user)
75         xmlrpc_strfree(requestInfoP->user);
76 }
77
78
79
80 void
81 RequestInit(TSession * const sessionP,
82             TConn *    const connectionP) {
83
84     time_t nowtime;
85
86     sessionP->validRequest = false;  /* Don't have valid request yet */
87
88     time(&nowtime);
89     sessionP->date = *gmtime(&nowtime);
90
91     sessionP->conn = connectionP;
92
93     sessionP->responseStarted = FALSE;
94
95     sessionP->chunkedwrite = FALSE;
96     sessionP->chunkedwritemode = FALSE;
97
98     sessionP->continueRequired = FALSE;
99
100     ListInit(&sessionP->cookies);
101     ListInit(&sessionP->ranges);
102     TableInit(&sessionP->request_headers);
103     TableInit(&sessionP->response_headers);
104
105     sessionP->status = 0;  /* No status from handler yet */
106
107     StringAlloc(&(sessionP->header));
108 }
109
110
111
112 void
113 RequestFree(TSession * const sessionP) {
114
115     if (sessionP->validRequest)
116         freeRequestInfo(&sessionP->request_info);
117
118     ListFree(&sessionP->cookies);
119     ListFree(&sessionP->ranges);
120     TableFree(&sessionP->request_headers);
121     TableFree(&sessionP->response_headers);
122     StringFree(&(sessionP->header));
123 }
124
125
126
127 static void
128 readRequestLine(TSession *   const sessionP,
129                 char **      const requestLineP,
130                 uint16_t *   const httpErrorCodeP) {
131
132     *httpErrorCodeP = 0;
133
134     /* Ignore CRLFs in the beginning of the request (RFC2068-P30) */
135     do {
136         abyss_bool success;
137         success = ConnReadHeader(sessionP->conn, requestLineP);
138         if (!success)
139             *httpErrorCodeP = 408;  /* Request Timeout */
140     } while (!*httpErrorCodeP && (*requestLineP)[0] == '\0');
141 }
142
143
144
145 static void
146 unescapeUri(char *       const uri,
147             abyss_bool * const errorP) {
148
149     char * x;
150     char * y;
151
152     x = y = uri;
153     
154     *errorP = FALSE;
155
156     while (*x && !*errorP) {
157         switch (*x) {
158         case '%': {
159             char c;
160             ++x;
161             c = tolower(*x++);
162             if ((c >= '0') && (c <= '9'))
163                 c -= '0';
164             else if ((c >= 'a') && (c <= 'f'))
165                 c -= 'a' - 10;
166             else
167                 *errorP = TRUE;
168
169             if (!*errorP) {
170                 char d;
171                 d = tolower(*x++);
172                 if ((d >= '0') && (d <= '9'))
173                     d -= '0';
174                 else if ((d >= 'a') && (d <= 'f'))
175                     d -= 'a' - 10;
176                 else
177                     *errorP = TRUE;
178
179                 if (!*errorP)
180                     *y++ = ((c << 4) | d);
181             }
182         } break;
183
184         default:
185             *y++ = *x++;
186             break;
187         }
188     }
189     *y = '\0';
190 }
191
192
193
194 static void
195 parseHostPort(char *           const hostport,
196               const char **    const hostP,
197               unsigned short * const portP,
198               uint16_t *       const httpErrorCodeP) {
199     
200     char * colonPos;
201
202     colonPos = strchr(hostport, ':');
203     if (colonPos) {
204         const char * p;
205         uint32_t port;
206
207         *colonPos = '\0';  /* Split hostport at the colon */
208
209         *hostP = hostport;
210
211         for (p = colonPos + 1, port = 0;
212              isdigit(*p) && port < 65535;
213              (port = port * 10 + (*p - '0')), ++p);
214             
215         *portP = port;
216
217         if (*p || port == 0)
218             *httpErrorCodeP = 400;  /* Bad Request */
219         else
220             *httpErrorCodeP = 0;
221     } else {
222         *hostP          = hostport;
223         *portP          = 80;
224         *httpErrorCodeP = 0;
225     }
226 }
227
228
229
230 static void
231 parseRequestUri(char *           const requestUri,
232                 const char **    const hostP,
233                 const char **    const pathP,
234                 const char **    const queryP,
235                 unsigned short * const portP,
236                 uint16_t *       const httpErrorCodeP) {
237 /*----------------------------------------------------------------------------
238   Parse the request URI (in the request line
239   "GET http://www.myserver.com/myfile?parm HTTP/1.1",
240   "http://www.myserver.com/myfile?parm" is the request URI).
241
242   This destroys *requestUri and returns pointers into *requestUri!
243
244   This is extremely ugly.  We need to redo it with dynamically allocated
245   storage.  We should return individual malloc'ed strings.
246 -----------------------------------------------------------------------------*/
247     abyss_bool error;
248
249     unescapeUri(requestUri, &error);
250     
251     if (error)
252         *httpErrorCodeP = 400;  /* Bad Request */
253     else {
254         char * requestUriNoQuery;
255            /* The request URI with any query (the stuff marked by a question
256               mark at the end of a request URI) chopped off.
257            */
258         {
259             /* Split requestUri at the question mark */
260             char * const qmark = strchr(requestUri, '?');
261             
262             if (qmark) {
263                 *qmark = '\0';
264                 *queryP = qmark + 1;
265             } else
266                 *queryP = NULL;
267         }
268         
269         requestUriNoQuery = requestUri;
270
271         if (requestUriNoQuery[0] == '/') {
272             *hostP = NULL;
273             *pathP = requestUriNoQuery;
274             *portP = 80;
275         } else {
276             if (!xmlrpc_strneq(requestUriNoQuery, "http://", 7))
277                 *httpErrorCodeP = 400;  /* Bad Request */
278             else {
279                 char * const hostportpath = &requestUriNoQuery[7];
280                 char * const slashPos = strchr(hostportpath, '/');
281                 char * hostport;
282                 
283                 if (slashPos) {
284                     char * p;
285                     *pathP = slashPos;
286                     
287                     /* Nul-terminate the host name.  To make space for
288                        it, slide the whole name back one character.
289                        This moves it into the space now occupied by
290                        the end of "http://", which we don't need.
291                     */
292                     for (p = hostportpath; *p != '/'; ++p)
293                         *(p-1) = *p;
294                     *(p-1) = '\0';
295                     
296                     hostport = hostportpath - 1;
297                     *httpErrorCodeP = 0;
298                 } else {
299                     *pathP = "*";
300                     hostport = hostportpath;
301                     *httpErrorCodeP = 0;
302                 }
303                 if (!*httpErrorCodeP)
304                     parseHostPort(hostport, hostP, portP, httpErrorCodeP);
305             }
306         }
307     }
308 }
309
310
311
312 static void
313 parseRequestLine(char *           const requestLine,
314                  TMethod *        const httpMethodP,
315                  httpVersion *    const httpVersionP,
316                  const char **    const hostP,
317                  unsigned short * const portP,
318                  const char **    const pathP,
319                  const char **    const queryP,
320                  abyss_bool *     const moreLinesP,
321                  uint16_t *       const httpErrorCodeP) {
322 /*----------------------------------------------------------------------------
323    Modifies *header1 and returns pointers to its storage!
324 -----------------------------------------------------------------------------*/
325     const char * httpMethodName;
326     char * p;
327
328     p = requestLine;
329
330     /* Jump over spaces */
331     NextToken((const char **)&p);
332
333     httpMethodName = GetToken(&p);
334     if (!httpMethodName)
335         *httpErrorCodeP = 400;  /* Bad Request */
336     else {
337         char * requestUri;
338
339         if (xmlrpc_streq(httpMethodName, "GET"))
340             *httpMethodP = m_get;
341         else if (xmlrpc_streq(httpMethodName, "PUT"))
342             *httpMethodP = m_put;
343         else if (xmlrpc_streq(httpMethodName, "OPTIONS"))
344             *httpMethodP = m_options;
345         else if (xmlrpc_streq(httpMethodName, "DELETE"))
346             *httpMethodP = m_delete;
347         else if (xmlrpc_streq(httpMethodName, "POST"))
348             *httpMethodP = m_post;
349         else if (xmlrpc_streq(httpMethodName, "TRACE"))
350             *httpMethodP = m_trace;
351         else if (xmlrpc_streq(httpMethodName, "HEAD"))
352             *httpMethodP = m_head;
353         else
354             *httpMethodP = m_unknown;
355         
356         /* URI and Query Decoding */
357         NextToken((const char **)&p);
358
359         
360         requestUri = GetToken(&p);
361         if (!requestUri)
362             *httpErrorCodeP = 400;  /* Bad Request */
363         else {
364             parseRequestUri(requestUri, hostP, pathP, queryP, portP,
365                             httpErrorCodeP);
366
367             if (!*httpErrorCodeP) {
368                 const char * httpVersion;
369
370                 NextToken((const char **)&p);
371         
372                 /* HTTP Version Decoding */
373                 
374                 httpVersion = GetToken(&p);
375                 if (httpVersion) {
376                     uint32_t vmin, vmaj;
377                     if (sscanf(httpVersion, "HTTP/%d.%d", &vmaj, &vmin) != 2)
378                         *httpErrorCodeP = 400;  /* Bad Request */
379                     else {
380                         httpVersionP->major = vmaj;
381                         httpVersionP->minor = vmin;
382                         *httpErrorCodeP = 0;  /* no error */
383                     }
384                     *moreLinesP = TRUE;
385                 } else {
386                     /* There is no HTTP version, so this is a single
387                        line request.
388                     */
389                     *httpErrorCodeP = 0;  /* no error */
390                     *moreLinesP = FALSE;
391                 }
392             }
393         }
394     }
395 }
396
397
398
399 static void
400 strtolower(char * const s) {
401
402     char * t;
403
404     t = &s[0];
405     while (*t) {
406         *t = tolower(*t);
407         ++t;
408     }
409 }
410
411
412
413 static void
414 getFieldNameToken(char **    const pP,
415                   char **    const fieldNameP,
416                   uint16_t * const httpErrorCodeP) {
417     
418     char * fieldName;
419
420     NextToken((const char **)pP);
421     
422     fieldName = GetToken(pP);
423     if (!fieldName)
424         *httpErrorCodeP = 400;  /* Bad Request */
425     else {
426         if (fieldName[strlen(fieldName)-1] != ':')
427             /* Not a valid field name */
428             *httpErrorCodeP = 400;  /* Bad Request */
429         else {
430             fieldName[strlen(fieldName)-1] = '\0';  /* remove trailing colon */
431
432             strtolower(fieldName);
433             
434             *httpErrorCodeP = 0;  /* no error */
435             *fieldNameP = fieldName;
436         }
437     }
438 }
439
440
441
442 static void
443 processHeader(const char * const fieldName,
444               char *       const fieldValue,
445               TSession *   const sessionP,
446               uint16_t *   const httpErrorCodeP) {
447 /*----------------------------------------------------------------------------
448    We may modify *fieldValue, and we put pointers to *fieldValue and
449    *fieldName into *sessionP.
450
451    We must fix this some day.  *sessionP should point to individual
452    malloc'ed strings.
453 -----------------------------------------------------------------------------*/
454     *httpErrorCodeP = 0;  /* initial assumption */
455
456     if (xmlrpc_streq(fieldName, "connection")) {
457         if (xmlrpc_strcaseeq(fieldValue, "keep-alive"))
458             sessionP->request_info.keepalive = TRUE;
459         else
460             sessionP->request_info.keepalive = FALSE;
461     } else if (xmlrpc_streq(fieldName, "host"))
462         parseHostPort(fieldValue, &sessionP->request_info.host,
463                       &sessionP->request_info.port, httpErrorCodeP);
464     else if (xmlrpc_streq(fieldName, "from"))
465         sessionP->request_info.from = fieldValue;
466     else if (xmlrpc_streq(fieldName, "user-agent"))
467         sessionP->request_info.useragent = fieldValue;
468     else if (xmlrpc_streq(fieldName, "referer"))
469         sessionP->request_info.referer = fieldValue;
470     else if (xmlrpc_streq(fieldName, "range")) {
471         if (xmlrpc_strneq(fieldValue, "bytes=", 6)) {
472             abyss_bool succeeded;
473             succeeded = ListAddFromString(&sessionP->ranges, &fieldValue[6]);
474             *httpErrorCodeP = succeeded ? 0 : 400;
475         }
476     } else if (xmlrpc_streq(fieldName, "cookies")) {
477         abyss_bool succeeded;
478         succeeded = ListAddFromString(&sessionP->cookies, fieldValue);
479         *httpErrorCodeP = succeeded ? 0 : 400;
480     } else if (xmlrpc_streq(fieldName, "expect")) {
481         if (xmlrpc_strcaseeq(fieldValue, "100-continue"))
482             sessionP->continueRequired = TRUE;
483     }
484 }
485
486
487
488 abyss_bool
489 RequestRead(TSession * const sessionP) {
490     uint16_t httpErrorCode;  /* zero for no error */
491     char * requestLine;
492
493     readRequestLine(sessionP, &requestLine, &httpErrorCode);
494     if (!httpErrorCode) {
495         TMethod httpMethod;
496         const char * host;
497         const char * path;
498         const char * query;
499         unsigned short port;
500         abyss_bool moreHeaders;
501
502         parseRequestLine(requestLine, &httpMethod, &sessionP->version,
503                          &host, &port, &path, &query,
504                          &moreHeaders, &httpErrorCode);
505
506         if (!httpErrorCode)
507             initRequestInfo(&sessionP->request_info, sessionP->version,
508                             strdup(requestLine),
509                             httpMethod, host, port, path, query);
510
511         while (moreHeaders && !httpErrorCode) {
512             char * p;
513             abyss_bool succeeded;
514             succeeded = ConnReadHeader(sessionP->conn, &p);
515             if (!succeeded)
516                 httpErrorCode = 408;  /* Request Timeout */
517             else {
518                 if (!*p)
519                     /* We have reached the empty line so all the request
520                        was read.
521                     */
522                     moreHeaders = FALSE;
523                 else {
524                     char * fieldName;
525                     getFieldNameToken(&p, &fieldName, &httpErrorCode);
526                     if (!httpErrorCode) {
527                         char * fieldValue;
528
529                         NextToken((const char **)&p);
530                         
531                         fieldValue = p;
532                         
533                         TableAdd(&sessionP->request_headers,
534                                  fieldName, fieldValue);
535                         
536                         processHeader(fieldName, fieldValue, sessionP,
537                                       &httpErrorCode);
538                     }
539                 }
540             }
541         }
542     }
543     if (httpErrorCode)
544         ResponseStatus(sessionP, httpErrorCode);
545     else
546         sessionP->validRequest = true;
547
548     return !httpErrorCode;
549 }
550
551
552
553 char *RequestHeaderValue(TSession *r,char *name)
554 {
555     return (TableFind(&r->request_headers,name));
556 }
557
558
559
560 abyss_bool
561 RequestValidURI(TSession * const sessionP) {
562
563     if (!sessionP->request_info.uri)
564         return FALSE;
565     
566     if (xmlrpc_streq(sessionP->request_info.uri, "*"))
567         return (sessionP->request_info.method != m_options);
568
569     if (strchr(sessionP->request_info.uri, '*'))
570         return FALSE;
571
572     return TRUE;
573 }
574
575
576
577 abyss_bool
578 RequestValidURIPath(TSession * const sessionP) {
579
580     uint32_t i;
581     const char * p;
582
583     p = sessionP->request_info.uri;
584
585     i = 0;
586
587     if (*p == '/') {
588         i = 1;
589         while (*p)
590             if (*(p++) == '/') {
591                 if (*p == '/')
592                     break;
593                 else if ((strncmp(p,"./",2) == 0) || (strcmp(p, ".") == 0))
594                     ++p;
595                 else if ((strncmp(p, "../", 2) == 0) ||
596                          (strcmp(p, "..") == 0)) {
597                     p += 2;
598                     --i;
599                     if (i == 0)
600                         break;
601                 }
602                 /* Prevent accessing hidden files (starting with .) */
603                 else if (*p == '.')
604                     return FALSE;
605                 else
606                     if (*p)
607                         ++i;
608             }
609     }
610     return (*p == 0 && i > 0);
611 }
612
613
614
615 abyss_bool
616 RequestAuth(TSession *r,char *credential,char *user,char *pass) {
617
618     char *p,*x;
619     char z[80],t[80];
620
621     p=RequestHeaderValue(r,"authorization");
622     if (p) {
623         NextToken((const char **)&p);
624         x=GetToken(&p);
625         if (x) {
626             if (strcasecmp(x,"basic")==0) {
627                 NextToken((const char **)&p);
628                 sprintf(z,"%s:%s",user,pass);
629                 Base64Encode(z,t);
630
631                 if (strcmp(p,t)==0) {
632                     r->request_info.user=strdup(user);
633                     return TRUE;
634                 };
635             };
636         }
637     };
638
639     sprintf(z,"Basic realm=\"%s\"",credential);
640     ResponseAddField(r,"WWW-Authenticate",z);
641     ResponseStatus(r,401);
642     return FALSE;
643 }
644
645
646
647 /*********************************************************************
648 ** Range
649 *********************************************************************/
650
651 abyss_bool RangeDecode(char *str,uint64_t filesize,uint64_t *start,uint64_t *end)
652 {
653     char *ss;
654
655     *start=0;
656     *end=filesize-1;
657
658     if (*str=='-')
659     {
660         *start=filesize-strtol(str+1,&ss,10);
661         return ((ss!=str) && (!*ss));
662     };
663
664     *start=strtol(str,&ss,10);
665
666     if ((ss==str) || (*ss!='-'))
667         return FALSE;
668
669     str=ss+1;
670
671     if (!*str)
672         return TRUE;
673
674     *end=strtol(str,&ss,10);
675
676     if ((ss==str) || (*ss) || (*end<*start))
677         return FALSE;
678
679     return TRUE;
680 }
681
682 /*********************************************************************
683 ** HTTP
684 *********************************************************************/
685
686 const char *
687 HTTPReasonByStatus(uint16_t const code) {
688
689     struct _HTTPReasons {
690         uint16_t status;
691         const char * reason;
692     };
693
694     static struct _HTTPReasons const reasons[] =  {
695         { 100,"Continue" }, 
696         { 101,"Switching Protocols" }, 
697         { 200,"OK" }, 
698         { 201,"Created" }, 
699         { 202,"Accepted" }, 
700         { 203,"Non-Authoritative Information" }, 
701         { 204,"No Content" }, 
702         { 205,"Reset Content" }, 
703         { 206,"Partial Content" }, 
704         { 300,"Multiple Choices" }, 
705         { 301,"Moved Permanently" }, 
706         { 302,"Moved Temporarily" }, 
707         { 303,"See Other" }, 
708         { 304,"Not Modified" }, 
709         { 305,"Use Proxy" }, 
710         { 400,"Bad Request" }, 
711         { 401,"Unauthorized" }, 
712         { 402,"Payment Required" }, 
713         { 403,"Forbidden" }, 
714         { 404,"Not Found" }, 
715         { 405,"Method Not Allowed" }, 
716         { 406,"Not Acceptable" }, 
717         { 407,"Proxy Authentication Required" }, 
718         { 408,"Request Timeout" }, 
719         { 409,"Conflict" }, 
720         { 410,"Gone" }, 
721         { 411,"Length Required" }, 
722         { 412,"Precondition Failed" }, 
723         { 413,"Request Entity Too Large" }, 
724         { 414,"Request-URI Too Long" }, 
725         { 415,"Unsupported Media Type" }, 
726         { 500,"Internal Server Error" }, 
727         { 501,"Not Implemented" }, 
728         { 502,"Bad Gateway" }, 
729         { 503,"Service Unavailable" }, 
730         { 504,"Gateway Timeout" }, 
731         { 505,"HTTP Version Not Supported" },
732         { 000, NULL }
733     };
734     const struct _HTTPReasons * reasonP;
735
736     reasonP = &reasons[0];
737
738     while (reasonP->status <= code)
739         if (reasonP->status == code)
740             return reasonP->reason;
741         else
742             ++reasonP;
743
744     return "No Reason";
745 }
746
747
748
749 int32_t
750 HTTPRead(TSession *   const s ATTR_UNUSED,
751          const char * const buffer ATTR_UNUSED,
752          uint32_t     const len ATTR_UNUSED) {
753
754     return 0;
755 }
756
757
758
759 abyss_bool
760 HTTPWriteBodyChunk(TSession *   const sessionP,
761                    const char * const buffer,
762                    uint32_t     const len) {
763
764     abyss_bool succeeded;
765
766     if (sessionP->chunkedwrite && sessionP->chunkedwritemode) {
767         char chunkHeader[16];
768
769         sprintf(chunkHeader, "%x\r\n", len);
770
771         succeeded =
772             ConnWrite(sessionP->conn, chunkHeader, strlen(chunkHeader));
773         if (succeeded) {
774             succeeded = ConnWrite(sessionP->conn, buffer, len);
775             if (succeeded)
776                 succeeded = ConnWrite(sessionP->conn, "\r\n", 2);
777         }
778     } else
779         succeeded = ConnWrite(sessionP->conn, buffer, len);
780
781     return succeeded;
782 }
783
784
785
786 abyss_bool
787 HTTPWriteEndChunk(TSession * const sessionP) {
788
789     abyss_bool retval;
790
791     if (sessionP->chunkedwritemode && sessionP->chunkedwrite) {
792         /* May be one day trailer dumping will be added */
793         sessionP->chunkedwritemode = FALSE;
794         retval = ConnWrite(sessionP->conn, "0\r\n\r\n", 5);
795     } else
796         retval = TRUE;
797
798     return retval;
799 }
800
801
802
803 abyss_bool
804 HTTPKeepalive(TSession * const sessionP) {
805 /*----------------------------------------------------------------------------
806    Return value: the connection should be kept alive after the session
807    *sessionP is over.
808 -----------------------------------------------------------------------------*/
809     return (sessionP->request_info.keepalive &&
810             !sessionP->serverDeniesKeepalive &&
811             sessionP->status < 400);
812 }
813
814
815
816 abyss_bool
817 HTTPWriteContinue(TSession * const sessionP) {
818
819     char const continueStatus[] = "HTTP/1.1 100 continue\r\n\r\n";
820         /* This is a status line plus an end-of-headers empty line */
821
822     return ConnWrite(sessionP->conn, continueStatus, strlen(continueStatus));
823 }
824
825
826
827 /******************************************************************************
828 **
829 ** http.c
830 **
831 ** Copyright (C) 2000 by Moez Mahfoudh <mmoez@bigfoot.com>.
832 ** All rights reserved.
833 **
834 ** Redistribution and use in source and binary forms, with or without
835 ** modification, are permitted provided that the following conditions
836 ** are met:
837 ** 1. Redistributions of source code must retain the above copyright
838 **    notice, this list of conditions and the following disclaimer.
839 ** 2. Redistributions in binary form must reproduce the above copyright
840 **    notice, this list of conditions and the following disclaimer in the
841 **    documentation and/or other materials provided with the distribution.
842 ** 3. The name of the author may not be used to endorse or promote products
843 **    derived from this software without specific prior written permission.
844 ** 
845 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
846 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
847 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
848 ** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
849 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
850 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
851 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
852 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
853 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
854 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
855 ** SUCH DAMAGE.
856 **
857 ******************************************************************************/