source: rtems/c/src/libnetworking/rtems_webserver/webs.c @ 45a71c9d

Last change on this file since 45a71c9d was 45a71c9d, checked in by Joel Sherrill <joel.sherrill@…>, on 02/08/06 at 16:15:09

2006-02-08 Thomas Rauscher <trauscher@…>

PR 890/networking

  • rtems_webserver/webs.c: The webservers enters an infinite loop when a POST request with less data than indicated in the Content-Length header is received. It also consumes additional heap memory and a file descriptor for each invalid POST.
  • Property mode set to 100644
File size: 62.6 KB
Line 
1/*
2 * webs.c -- GoAhead Embedded HTTP webs server
3 *
4 * Copyright (c) GoAhead Software Inc., 1995-2000. All Rights Reserved.
5 *
6 * See the file "license.txt" for usage and redistribution license requirements
7 *
8 * $Id$
9 */
10
11/******************************** Description *********************************/
12
13/*
14 *      This module implements an embedded HTTP/1.1 web server. It supports
15 *      loadable URL handlers that define the nature of URL processing performed.
16 */
17
18/********************************* Includes ***********************************/
19
20#include        "wsIntrn.h"
21#ifdef DIGEST_ACCESS_SUPPORT
22#include        "websda.h"
23#endif
24
25/******************************** Global Data *********************************/
26
27websStatsType   websStats;                              /* Web access stats */
28webs_t                  *webs;                                  /* Open connection list head */
29sym_fd_t                websMime;                               /* Set of mime types */
30int                             websMax;                                /* List size */
31int                             websPort;                               /* Listen port for server */
32char_t                  websHost[64];                   /* Host name for the server */
33char_t                  websIpaddr[64];                 /* IP address for the server */
34char_t                  *websHostUrl = NULL;    /* URL to access server */
35char_t                  *websIpaddrUrl = NULL;  /* URL to access server */
36
37/*********************************** Locals ***********************************/
38/*
39 *      Standard HTTP error codes
40 */
41
42websErrorType websErrors[] = {
43        { 200, T("Data follows") },
44        { 204, T("No Content") },
45        { 301, T("Redirect") },
46        { 302, T("Redirect") },
47        { 304, T("User local copy") },
48        { 400, T("Page not found") },
49        { 401, T("Unauthorized") },
50        { 403, T("Forbidden") },
51        { 404, T("Site or Page Not Found") },
52        { 405, T("Access Denied") },
53        { 500, T("Web Error") },
54        { 501, T("Not Implemented") },
55        { 503, T("Site Temporarily Unavailable. Try again.") },
56        { 0, NULL }
57};
58
59#if WEBS_LOG_SUPPORT
60static char_t   websLogname[64] = T("log.txt"); /* Log filename */
61static int              websLogFd;                                              /* Log file handle */
62#endif
63
64static int              websListenSock;                                 /* Listen socket */
65static char_t   websRealm[64] = T("GoAhead");   /* Realm name */
66
67static int              websOpenCount = 0;              /* count of apps using this module */
68
69/**************************** Forward Declarations ****************************/
70
71
72static char_t   *websErrorMsg(int code);
73static int              websGetInput(webs_t wp, char_t **ptext, int *nbytes);
74static int              websParseFirst(webs_t wp, char_t *text);
75static void     websParseRequest(webs_t wp);
76static void             websSocketEvent(int sid, int mask, int data);
77static int              websGetTimeSinceMark(webs_t wp);
78
79#if WEBS_LOG_SUPPORT
80static void     websLog(webs_t wp, int code);
81#endif
82#if WEBS_IF_MODIFIED_SUPPORT
83static time_t   dateParse(time_t tip, char_t *cmd);
84#endif
85
86/*********************************** Code *************************************/
87/*
88 *      Open the GoAhead WebServer
89 */
90
91int websOpenServer(int port, int retries)
92{
93        websMimeType    *mt;
94
95        if (++websOpenCount != 1) {
96                return websPort;
97        }
98
99        a_assert(port > 0);
100        a_assert(retries >= 0);
101
102#if WEBS_PAGE_ROM
103        websRomOpen();
104#endif
105
106        webs = NULL;
107        websMax = 0;
108/*
109 *      Create a mime type lookup table for quickly determining the content type
110 */
111        websMime = symOpen(WEBS_SYM_INIT * 4);
112        a_assert(websMime >= 0);
113        for (mt = websMimeList; mt->type; mt++) {
114                symEnter(websMime, mt->ext, valueString(mt->type, 0), 0);
115        }
116
117/*
118 *      Open the URL handler module. The caller should create the required
119 *      URL handlers after calling this function.
120 */
121        if (websUrlHandlerOpen() < 0) {
122                return -1;
123        }
124        websFormOpen();
125
126#if WEBS_LOG_SUPPORT
127/*
128 *      Optional request log support
129 */
130        websLogFd = gopen(websLogname, O_CREAT | O_TRUNC | O_APPEND | O_WRONLY,
131                0666);
132        a_assert(websLogFd >= 0);
133#endif
134       
135        return websOpenListen(port, retries);
136}
137
138/******************************************************************************/
139/*
140 *      Close the GoAhead WebServer
141 */
142
143void websCloseServer()
144{
145        webs_t  wp;
146        int             wid;
147
148        if (--websOpenCount > 0) {
149                return;
150        }
151
152/*
153 *      Close the listen handle first then all open connections.
154 */
155        websCloseListen();
156
157/*
158 *      Close each open browser connection and free all resources
159 */
160        for (wid = websMax; webs && wid >= 0; wid--) {
161                if ((wp = webs[wid]) == NULL) {
162                        continue;
163                }
164                socketCloseConnection(wp->sid);
165                websFree(wp);
166        }
167
168#if WEBS_LOG_SUPPORT
169        if (websLogFd >= 0) {
170                close(websLogFd);
171                websLogFd = -1;
172        }
173#endif
174
175#if WEBS_PAGE_ROM
176        websRomClose();
177#endif
178        symClose(websMime);
179        websFormClose();
180        websUrlHandlerClose();
181}
182
183/******************************************************************************/
184/*
185 *      Open the GoAhead WebServer listen port
186 */
187
188int websOpenListen(int port, int retries)
189{
190        int             i, orig;
191
192        a_assert(port > 0);
193        a_assert(retries >= 0);
194
195        orig = port;
196/*
197 *      Open the webs webs listen port. If we fail, try the next port.
198 */
199        for (i = 0; i <= retries; i++) {
200                websListenSock = socketOpenConnection(NULL, port, websAccept, 0);
201                if (websListenSock >= 0) {
202                        break;
203                }
204                port++;
205        }
206        if (i > retries) {
207                error(E_L, E_USER, T("Couldn't open a socket on ports %d - %d"),
208                        orig, port - 1);
209                return -1;
210        }
211
212/*
213 *      Determine the full URL address to access the home page for this web server
214 */
215        websPort = port;
216        bfreeSafe(B_L, websHostUrl);
217        bfreeSafe(B_L, websIpaddrUrl);
218        websIpaddrUrl = websHostUrl = NULL;
219
220        if (port == 80) {
221                websHostUrl = bstrdup(B_L, websHost);
222                websIpaddrUrl = bstrdup(B_L, websIpaddr);
223        } else {
224                fmtAlloc(&websHostUrl, WEBS_MAX_URL + 80, T("%s:%d"), websHost, port);
225                fmtAlloc(&websIpaddrUrl, WEBS_MAX_URL + 80, T("%s:%d"),
226                        websIpaddr, port);
227        }
228        trace(0, T("webs: Listening for HTTP requests at address %s\n"),
229                websIpaddrUrl);
230
231        return port;
232}
233
234/******************************************************************************/
235/*
236 *      Close webs listen port
237 */
238
239void websCloseListen()
240{
241        if (websListenSock >= 0) {
242                socketCloseConnection(websListenSock);
243                websListenSock = -1;
244        }
245        bfreeSafe(B_L, websHostUrl);
246        bfreeSafe(B_L, websIpaddrUrl);
247        websIpaddrUrl = websHostUrl = NULL;
248}
249
250/******************************************************************************/
251/*
252 *      Accept a connection
253 */
254
255int websAccept(int sid, char *ipaddr, int port, int listenSid)
256{
257        webs_t  wp;
258        int             wid;
259
260        a_assert(ipaddr && *ipaddr);
261        a_assert(sid >= 0);
262        a_assert(port >= 0);
263
264/*
265 *      Allocate a new handle for this accepted connection. This will allocate
266 *      a webs_t structure in the webs[] list
267 */
268        if ((wid = websAlloc(sid)) < 0) {
269                return -1;
270        }
271        wp = webs[wid];
272        a_assert(wp);
273        wp->listenSid = listenSid;
274
275        ascToUni(wp->ipaddr, ipaddr, sizeof(wp->ipaddr));
276
277/*
278 *      Check if this is a request from a browser on this system. This is useful
279 *      to know for permitting administrative operations only for local access
280 */
281        if (gstrcmp(wp->ipaddr, T("127.0.0.1")) == 0 ||
282                        gstrcmp(wp->ipaddr, websIpaddr) == 0 ||
283                        gstrcmp(wp->ipaddr, websHost) == 0) {
284                wp->flags |= WEBS_LOCAL_REQUEST;
285        }
286
287/*
288 *      Arrange for websSocketEvent to be called when read data is available
289 */
290        socketCreateHandler(sid, SOCKET_READABLE, websSocketEvent, (int) wp);
291
292/*
293 *      Arrange for a timeout to kill hung requests
294 */
295        wp->timeout = emfSchedCallback(WEBS_TIMEOUT, websTimeout, (void *) wp);
296        trace(8, T("webs: accept request\n"));
297        return 0;
298}
299
300/******************************************************************************/
301/*
302 *      The webs socket handler.  Called in response to I/O. We just pass control
303 *      to the relevant read or write handler. A pointer to the webs structure
304 *      is passed as an (int) in iwp.
305 */
306
307static void websSocketEvent(int sid, int mask, int iwp)
308{
309        webs_t  wp;
310
311        wp = (webs_t) iwp;
312        a_assert(wp);
313
314        if (! websValid(wp)) {
315                return;
316        }
317
318        if (mask & SOCKET_READABLE) {
319                websReadEvent(wp);
320        }
321        if (mask & SOCKET_WRITABLE) {
322                if (wp->writeSocket) {
323                        (*wp->writeSocket)(wp);
324                }
325        }
326}
327
328/******************************************************************************/
329/*
330 *      The webs read handler. This is the primary read event loop. It uses a
331 *      state machine to track progress while parsing the HTTP request.
332 *      Note: we never block as the socket is always in non-blocking mode.
333 */
334
335void websReadEvent(webs_t wp)
336{
337        char_t  *text;
338        int             rc, nbytes, len, done, fd;
339
340        a_assert(wp);
341        a_assert(websValid(wp));
342
343        websMarkTime(wp);
344
345/*
346 *      Read as many lines as possible. socketGets is called to read the header
347 *      and socketRead is called to read posted data.
348 */
349        text = NULL;
350        fd = -1;
351        for (done = 0; !done; ) {
352                if (text) {
353                        bfree(B_L, text);
354                        text = NULL;
355                }
356
357/*
358 *              Get more input into "text". Returns 0, if more data is needed
359 *              to continue, -1 if finished with the request, or 1 if all
360 *              required data is available for current state.
361 */
362                while ((rc = websGetInput(wp, &text, &nbytes)) == 0) {
363                        ;
364                }
365
366/*
367 *              websGetInput returns -1 if it finishes with the request
368 */
369                if (rc < 0) {
370                        break;
371                }
372
373/*
374 *              This is the state machine for the web server.
375 */
376                switch(wp->state) {
377                case WEBS_BEGIN:
378/*
379 *                      Parse the first line of the Http header
380 */
381                        if (websParseFirst(wp, text) < 0) {
382                                done++;
383                                break;
384                        }
385                        wp->state = WEBS_HEADER;
386                        break;
387               
388                case WEBS_HEADER:
389/*
390 *                      Store more of the HTTP header. As we are doing line reads, we
391 *                      need to separate the lines with '\n'
392 */
393                        if (ringqLen(&wp->header) > 0) {
394                                ringqPutStr(&wp->header, T("\n"));
395                        }
396                        ringqPutStr(&wp->header, text);
397                        break;
398
399                case WEBS_POST_CLEN:
400/*
401 *                      POST request with content specified by a content length.
402 *                      If this is a CGI request, write the data to the cgi stdin.
403 *                      socketGets was used to get the data and it strips \n's so
404 *                      add them back in here.
405 */
406#ifndef __NO_CGI_BIN
407                        if (wp->flags & WEBS_CGI_REQUEST) {
408                                if (fd == -1) {
409                                        fd = gopen(wp->cgiStdin, O_CREAT | O_WRONLY | O_BINARY,
410                                                0666);
411                                }
412                                gwrite(fd, text, gstrlen(text));
413                                gwrite(fd, T("\n"), sizeof(char_t));
414                                nbytes += 1;
415                        } else
416#endif
417                        if (wp->query) {
418                                if (wp->query[0] && !(wp->flags & WEBS_POST_DATA)) {
419/*
420 *                                      Special case where the POST request also had query data
421 *                                      specified in the URL, ie. url?query_data. In this case
422 *                                      the URL query data is separated by a '&' from the posted
423 *                                      query data.
424 */
425                                        len = gstrlen(wp->query);
426                                        wp->query = brealloc(B_L, wp->query, (len + gstrlen(text) +
427                                                2) * sizeof(char_t));
428                                        wp->query[len++] = '&';
429                                        gstrcpy(&wp->query[len], text);
430
431                                } else {
432/*
433 *                                      The existing query data came from the POST request so just
434 *                                      append it.
435 */
436                                        len = gstrlen(wp->query);
437                                        wp->query = brealloc(B_L, wp->query, (len +     gstrlen(text) +
438                                                1) * sizeof(char_t));
439                                        if (wp->query) {
440                                                gstrcpy(&wp->query[len], text);
441                                        }
442                                }
443
444                        } else {
445                                wp->query = bstrdup(B_L, text);
446                        }
447/*
448 *                      Calculate how much more post data is to be read.
449 */
450                        wp->flags |= WEBS_POST_DATA;
451                        wp->clen -= nbytes;
452                        if (wp->clen > 0) {
453                                if (nbytes > 0) {
454                                        break;
455                                }
456                                done++;
457                                break;
458                        }
459/*
460 *                      No more data so process the request
461 */
462                        websUrlHandlerRequest(wp);
463                        done++;
464                        break;
465
466                case WEBS_POST:
467/*
468 *                      POST without content-length specification
469 *                      If this is a CGI request, write the data to the cgi stdin.
470 *                      socketGets was used to get the data and it strips \n's so
471 *                      add them back in here.
472 */
473
474#ifndef __NO_CGI_BIN
475                        if (wp->flags & WEBS_CGI_REQUEST) {
476                                if (fd == -1) {
477                                        fd = gopen(wp->cgiStdin, O_CREAT | O_WRONLY | O_BINARY,
478                                                0666);
479                                }
480                                gwrite(fd, text, gstrlen(text));
481                                gwrite(fd, T("\n"), sizeof(char_t));
482                        } else
483#endif
484                        if (wp->query && *wp->query && !(wp->flags & WEBS_POST_DATA)) {
485                                len = gstrlen(wp->query);
486                                wp->query = brealloc(B_L, wp->query, (len + gstrlen(text) +
487                                        2) * sizeof(char_t));
488                                if (wp->query) {
489                                        wp->query[len++] = '&';
490                                        gstrcpy(&wp->query[len], text);
491                                }
492
493                        } else {
494                                wp->query = bstrdup(B_L, text);
495                        }
496                        wp->flags |= WEBS_POST_DATA;
497                        done++;
498                        break;
499
500                default:
501                        websError(wp, 404, T("Bad state"));
502                        done++;
503                        break;
504                }
505        }
506
507        if (fd != -1) {
508                fd = gclose (fd);
509        }
510
511        if (text) {
512                bfree(B_L, text);
513        }
514}
515
516/******************************************************************************/
517/*
518 *      Get input from the browser. Return TRUE (!0) if the request has been
519 *      handled. Return -1 on errors or if the request has been processed,
520 *      1 if input read, and 0 to instruct the caller to call again for more input.
521 *
522 *      Note: socketRead will Return the number of bytes read if successful. This
523 *      may be less than the requested "bufsize" and may be zero. It returns -1 for
524 *      errors. It returns 0 for EOF. Otherwise it returns the number of bytes
525 *      read. Since this may be zero, callers should use socketEof() to
526 *      distinguish between this and EOF.
527 */
528
529static int websGetInput(webs_t wp, char_t **ptext, int *pnbytes)
530{
531        char_t  *text;
532        char    buf[WEBS_SOCKET_BUFSIZ+1];
533        int             nbytes, len, clen;
534
535        a_assert(websValid(wp));
536        a_assert(ptext);
537        a_assert(pnbytes);
538
539        *ptext = text = NULL;
540        *pnbytes = 0;
541
542/*
543 *      If this request is a POST with a content length, we know the number
544 *      of bytes to read so we use socketRead().
545 */
546        if (wp->state == WEBS_POST_CLEN) {
547                len = (wp->clen > WEBS_SOCKET_BUFSIZ) ? WEBS_SOCKET_BUFSIZ : wp->clen;
548        } else {
549                len = 0;
550        }
551
552        if (len > 0) {
553
554#ifdef WEBS_SSL_SUPPORT
555                if (wp->flags & WEBS_SECURE) {
556                        nbytes = websSSLRead(wp->wsp, buf, len);
557                } else {
558                        nbytes = socketRead(wp->sid, buf, len);
559                }
560#else
561                nbytes = socketRead(wp->sid, buf, len);
562#endif
563                if (nbytes < 0) {                                               /* Error */
564                        websDone(wp, 0);
565                        return -1;
566
567                }  else if (nbytes == 0) {                              /* EOF or No data available */
568                        /* Bugfix for POST DoS attack with invalid content length */
569                        if (socketEof(wp->sid)) {
570                                websDone(wp, 0);
571                        }
572                        /* End of bugfix */
573                        return -1;
574
575                } else {                                                                /* Valid data */
576/*
577 *                      Convert to UNICODE if necessary.  First be sure the string
578 *                      is NULL terminated.
579 */
580                        buf[nbytes] = '\0';
581                        if ((text = ballocAscToUni(buf, nbytes)) == NULL) {
582                                websError(wp, 503, T("Insufficient memory"));
583                                return -1;
584                        }
585                }
586
587        } else {
588#ifdef WEBS_SSL_SUPPORT
589                if (wp->flags & WEBS_SECURE) {
590                        nbytes = websSSLGets(wp->wsp, &text);
591                } else {
592                        nbytes = socketGets(wp->sid, &text);
593                }
594#else
595                nbytes = socketGets(wp->sid, &text);
596#endif
597
598                if (nbytes < 0) {
599                        int eof;
600/*
601 *                      Error, EOF or incomplete
602 */
603#ifdef WEBS_SSL_SUPPORT
604                        if (wp->flags & WEBS_SECURE) {
605/*
606 *                              If state is WEBS_BEGIN and the request is secure, a -1 will
607 *                              usually indicate SSL negotiation
608 */
609                                if (wp->state == WEBS_BEGIN) {
610                                        eof = 1;
611                                } else {
612                                        eof = websSSLEof(wp->wsp);
613                                }
614                        } else {
615                                eof = socketEof(wp->sid);
616                        }
617#else
618                        eof = socketEof(wp->sid);
619#endif
620
621                        if (eof) {
622/*
623 *                              If this is a post request without content length, process
624 *                              the request as we now have all the data. Otherwise just
625 *                              close the connection.
626 */
627                                if (wp->state == WEBS_POST) {
628                                        websUrlHandlerRequest(wp);
629                                } else {
630                                        websDone(wp, 0);
631                                }
632                        }
633/*
634 *                      If state is WEBS_HEADER and the ringq is empty, then this is a
635 *                      simple request with no additional header fields to process and
636 *                      no empty line terminator.
637 */
638                        if (wp->state == WEBS_HEADER && ringqLen(&wp->header) <= 0) {
639                                websParseRequest(wp);
640                                websUrlHandlerRequest(wp);
641                        }
642                        return -1;
643
644                } else if (nbytes == 0) {
645                        if (wp->state == WEBS_HEADER) {
646/*
647 *                              Valid empty line, now finished with header
648 */
649                                websParseRequest(wp);
650                                if (wp->flags & WEBS_POST_REQUEST) {
651                                        if (wp->flags & WEBS_CLEN) {
652                                                wp->state = WEBS_POST_CLEN;
653                                                clen = wp->clen;
654                                        } else {
655                                                wp->state = WEBS_POST;
656                                                clen = 1;
657                                        }
658                                        if (clen > 0) {
659/*
660 *                                              Return 0 to get more data.
661 */
662                                                return 0;
663                                        }
664                                        return 1;
665                                }
666/*
667 *                              We've read the header so go and handle the request
668 */
669                                websUrlHandlerRequest(wp);
670                        }
671                        return -1;
672                }
673        }
674        a_assert(text);
675        a_assert(nbytes > 0);
676        *ptext = text;
677        *pnbytes = nbytes;
678        return 1;
679}
680
681/******************************************************************************/
682/*
683 *      Parse the first line of a HTTP request
684 */
685
686static int websParseFirst(webs_t wp, char_t *text)
687{
688        char_t  *op, *proto, *protoVer, *url, *host, *query, *path, *port, *ext;
689        char_t  *buf;
690        int             testPort;
691
692        a_assert(websValid(wp));
693        a_assert(text && *text);
694
695/*
696 *      Determine the request type: GET, HEAD or POST
697 */
698        op = gstrtok(text, T(" \t"));
699        if (op == NULL || *op == '\0') {
700                websError(wp, 400, T("Bad HTTP request"));
701                return -1;
702        }
703        if (gstrcmp(op, T("GET")) != 0) {
704                if (gstrcmp(op, T("POST")) == 0) {
705                        wp->flags |= WEBS_POST_REQUEST;
706                } else if (gstrcmp(op, T("HEAD")) == 0) {
707                        wp->flags |= WEBS_HEAD_REQUEST;
708                } else {
709                        websError(wp, 400, T("Bad request type"));
710                        return -1;
711                }
712        }
713
714/*
715 *      Store result in the form (CGI) variable store
716 */
717        websSetVar(wp, T("REQUEST_METHOD"), op);
718
719        url = gstrtok(NULL, T(" \t\n"));
720        if (url == NULL || *url == '\0') {
721                websError(wp, 400, T("Bad HTTP request"));
722                return -1;
723        }
724        protoVer = gstrtok(NULL, T(" \t\n"));
725
726/*
727 *      Parse the URL and store all the various URL components. websUrlParse
728 *      returns an allocated buffer in buf which we must free. We support both
729 *      proxied and non-proxied requests. Proxied requests will have http://host/
730 *      at the start of the URL. Non-proxied will just be local path names.
731 */
732        host = path = port = proto = query = ext = NULL;
733        if (websUrlParse(url, &buf, &host, &path, &port, &query, &proto,
734                        NULL, &ext) < 0) {
735                websError(wp, 400, T("Bad URL format"));
736                return -1;
737        }
738
739        wp->url = bstrdup(B_L, url);
740
741#ifndef __NO_CGI_BIN
742        if (gstrstr(url, CGI_BIN) != NULL) {
743                wp->flags |= WEBS_CGI_REQUEST;
744                if (wp->flags & WEBS_POST_REQUEST) {
745                        wp->cgiStdin = websGetCgiCommName();
746                }
747        }
748#endif
749
750        wp->query = bstrdup(B_L, query);
751        wp->host = bstrdup(B_L, host);
752        wp->path = bstrdup(B_L, path);
753        wp->protocol = bstrdup(B_L, proto);
754        wp->protoVersion = bstrdup(B_L, protoVer);
755       
756        if ((testPort = socketGetPort(wp->listenSid)) >= 0) {
757                wp->port = testPort;
758        } else {
759                wp->port = gatoi(port);
760        }
761
762        if (gstrcmp(ext, T(".asp")) == 0) {
763                wp->flags |= WEBS_ASP;
764        }
765        bfree(B_L, buf);
766
767        websUrlType(url, wp->type, TSZ(wp->type));
768
769#if WEBS_PROXY_SUPPORT
770/*
771 *      Determine if this is a request for local webs data. If it is not a proxied
772 *      request from the browser, we won't see the "http://" or the system name, so
773 *      we assume it must be talking to us directly for local webs data.
774 *      Note: not fully implemented yet.
775 */
776        if (gstrstr(wp->url, T("http://")) == NULL ||
777                ((gstrcmp(wp->host, T("localhost")) == 0 ||
778                        gstrcmp(wp->host, websHost) == 0) && (wp->port == websPort))) {
779                wp->flags |= WEBS_LOCAL_PAGE;
780                if (gstrcmp(wp->path, T("/")) == 0) {
781                        wp->flags |= WEBS_HOME_PAGE;
782                }
783        }
784#endif
785
786        ringqFlush(&wp->header);
787        return 0;
788}
789
790/******************************************************************************/
791/*
792 *      Parse a full request
793 */
794
795#define isgoodchar(s) (gisalnum((s)) || ((s) == '/') || ((s) == '_') || \
796                                                ((s) == '.')  || ((s) == '-') )
797
798static void websParseRequest(webs_t wp)
799{
800        char_t  *authType, *upperKey, *cp, *browser, *lp, *key, *value;
801
802        a_assert(websValid(wp));
803
804/*
805 *      Define default CGI values
806 */
807        websSetVar(wp, T("HTTP_AUTHORIZATION"), T(""));
808
809/*
810 *      Parse the header and create the Http header keyword variables
811 *      We rewrite the header as we go for non-local requests.  NOTE: this
812 *      modifies the header string directly and tokenizes each line with '\0'.
813 */
814        browser = NULL;
815        for (lp = (char_t*) wp->header.servp; lp && *lp; ) {
816                cp = lp;
817                if ((lp = gstrchr(lp, '\n')) != NULL) {
818                        lp++;
819                }
820
821                if ((key = gstrtok(cp, T(": \t\n"))) == NULL) {
822                        continue;
823                }
824
825                if ((value = gstrtok(NULL, T("\n"))) == NULL) {
826                        value = T("");
827                }
828
829                while (gisspace(*value)) {
830                        value++;
831                }
832                strlower(key);
833
834/*
835 *              Create a variable (CGI) for each line in the header
836 */
837                fmtAlloc(&upperKey, (gstrlen(key) + 6), T("HTTP_%s"), key);
838                for (cp = upperKey; *cp; cp++) {
839                        if (*cp == '-')
840                                *cp = '_';
841                }
842                strupper(upperKey);
843                websSetVar(wp, upperKey, value);
844                bfree(B_L, upperKey);
845
846/*
847 *              Track the requesting agent (browser) type
848 */
849                if (gstrcmp(key, T("user-agent")) == 0) {
850                        wp->userAgent = bstrdup(B_L, value);
851
852/*
853 *              Parse the user authorization. ie. password
854 */
855                } else if (gstricmp(key, T("authorization")) == 0) {
856/*
857 *                      Determine the type of Authorization Request
858 */
859                        authType = bstrdup (B_L, value);
860                        a_assert (authType);
861/*                     
862 *                      Truncate authType at the next non-alpha character
863 */
864                        cp = authType;
865                        while (gisalpha(*cp)) {
866                                cp++;
867                        }
868                        *cp = '\0';
869
870                        wp->authType = bstrdup(B_L, authType);
871                        bfree(B_L, authType);
872
873                        if (gstricmp(wp->authType, T("basic")) == 0) {
874                                char_t  userAuth[FNAMESIZE];
875/*
876 *                              The incoming value is username:password (Basic authentication)
877 */
878                                if ((cp = gstrchr(value, ' ')) != NULL) {
879                                        *cp = '\0';
880                                        wp->authType = bstrdup(B_L, value);
881                                        websDecode64(userAuth, ++cp, sizeof(userAuth));
882                                } else {
883                                        websDecode64(userAuth, value, sizeof(userAuth));
884                                }
885/*
886 *                              Split userAuth into userid and password
887 */
888                                if ((cp = gstrchr(userAuth, ':')) != NULL) {
889                                        *cp++ = '\0';
890                                }
891                                if (cp) {
892                                        wp->userName = bstrdup(B_L, userAuth);
893                                        wp->password = bstrdup(B_L, cp);
894                                } else {
895                                        wp->userName = bstrdup(B_L, T(""));
896                                        wp->password = bstrdup(B_L, T(""));
897                                }
898/*
899 *                              Set the flags to indicate digest authentication
900 */
901                                wp->flags |= WEBS_AUTH_BASIC;
902                        } else {
903#ifdef DIGEST_ACCESS_SUPPORT
904/*
905 *                              The incoming value is slightly more complicated (Digest)
906 */
907                                char_t *np;             /* pointer to end of tag name */
908                                char_t tp;              /* temporary character holding space */
909                                char_t *vp;             /* pointer to value */
910                                char_t *npv;    /* pointer to end of value, "next" pointer */
911                                char_t tpv;             /* temporary character holding space */
912/*
913 *                              Set the flags to indicate digest authentication
914 */
915                                wp->flags |= WEBS_AUTH_DIGEST;
916/*
917 *                              Move cp to Next word beyond "Digest",
918 *                              vp to first char after '='.
919 */
920                                cp = value;
921                                while (isgoodchar(*cp)) {
922                                        cp++;
923                                }
924                                while (!isgoodchar(*cp)) {
925                                        cp++;
926                                }
927
928/*
929 *                              Find beginning of value
930 */
931                                vp = gstrchr(cp, '=');
932                                while (vp) {
933/*
934 *                                      Zero-terminate tag name
935 */
936                                        np = cp;
937                                        while (isgoodchar(*np)) {
938                                                np++;
939                                        }
940                                        tp = *np;
941                                        *np = 0;
942/*
943 *                                      Advance value pointer to first legit character
944 */
945                                        vp++;
946                                        while (!isgoodchar(*vp)) {
947                                                vp++;
948                                        }
949/*
950 *                                      Zero-terminate value
951 */
952                                        npv = vp;
953                                        while (isgoodchar(*npv)) {
954                                                npv++;
955                                        }
956                                        tpv = *npv;
957                                        *npv = 0;
958/*
959 *                                      Extract the fields
960 */
961                                        if (gstricmp(cp, T("username")) == 0) {
962                                                wp->userName = bstrdup(B_L, vp);
963                                        } else if (gstricmp(cp, T("response")) == 0) {
964                                                wp->digest = bstrdup(B_L, vp);
965                                        } else if (gstricmp(cp, T("opaque")) == 0) {
966                                                wp->opaque = bstrdup(B_L, vp);
967                                        } else if (gstricmp(cp, T("uri")) == 0) {
968                                                wp->uri = bstrdup(B_L, vp);
969                                        } else if (gstricmp(cp, T("realm")) == 0) {
970                                                wp->realm = bstrdup(B_L, vp);
971                                        } else if (gstricmp(cp, T("nonce")) == 0) {
972                                                wp->nonce = bstrdup(B_L, vp);
973                                        } else if (gstricmp(cp, T("nc")) == 0) {
974                                                wp->nc = bstrdup(B_L, vp);
975                                        } else if (gstricmp(cp, T("cnonce")) == 0) {
976                                                wp->cnonce = bstrdup(B_L, vp);
977                                        } else if (gstricmp(cp, T("qop")) == 0) {
978                                                wp->qop = bstrdup(B_L, vp);
979                                        }
980/*
981 *                                      Restore tag name and value zero-terminations
982 */
983                                        *np = tp;
984                                        *npv = tpv;
985/*
986 *                                      Advance tag name and value pointers
987 */
988                                        cp = npv;
989                                        while (*cp && isgoodchar(*cp)) {
990                                                cp++;
991                                        }
992                                        while (*cp && !isgoodchar(*cp)) {
993                                                cp++;
994                                        }
995
996                                        if (*cp) {
997                                                vp = gstrchr(cp, '=');
998                                        } else {
999                                                vp = NULL;
1000                                        }
1001                                }
1002#endif /* DIGEST_ACCESS_SUPPORT */
1003                        } /* if (gstrcmp(wp->authType)) */
1004/*
1005 *              Parse the content length
1006 */
1007                } else if (gstrcmp(key, T("content-length")) == 0) {
1008                        wp->flags |= WEBS_CLEN;
1009                        wp->clen = gatoi(value);
1010                        websSetVar(wp, T("CONTENT_LENGTH"), value);
1011
1012/*
1013 *              Parse the content type
1014 */
1015                } else if (gstrcmp(key, T("content-type")) == 0) {
1016                        websSetVar(wp, T("CONTENT_TYPE"), value);
1017
1018#if WEBS_KEEP_ALIVE_SUPPORT
1019                } else if (gstrcmp(key, T("connection")) == 0) {
1020                        strlower(value);
1021                        if (gstrcmp(value, T("keep-alive")) == 0) {
1022                                wp->flags |= WEBS_KEEP_ALIVE;
1023                        }
1024#endif
1025
1026#if WEBS_PROXY_SUPPORT
1027/*
1028 *              This may be useful if you wish to keep a local cache of web pages
1029 *              for proxied requests.
1030 */
1031                } else if (gstrcmp(key, T("pragma")) == 0) {
1032                        char_t  tmp[256];
1033                        gstrncpy(tmp, value, TSZ(tmp));
1034                        strlower(tmp);
1035                        if (gstrstr(tmp, T("no-cache"))) {
1036                                wp->flags |= WEBS_DONT_USE_CACHE;
1037                        }
1038#endif /* WEBS_PROXY_SUPPORT */
1039
1040/*
1041 *              Store the cookie
1042 */
1043                } else if (gstrcmp(key, T("cookie")) == 0) {
1044                        wp->flags |= WEBS_COOKIE;
1045                        wp->cookie = bstrdup(B_L, value);
1046
1047#if WEBS_IF_MODIFIED_SUPPORT
1048/*
1049 *              See if the local page has been modified since the browser last
1050 *              requested this document. If not, just return a 302
1051 */
1052                } else if (gstrcmp(key, T("if-modified-since")) == 0) {
1053                        char_t *cmd;
1054                        time_t tip = 0;
1055
1056                        if ((cp = gstrchr(value, ';')) != NULL) {
1057                                *cp = '\0';
1058                        }
1059
1060                        fmtAlloc(&cmd, 64, T("%s"), value);
1061
1062                        if ((wp->since = dateParse(tip, cmd)) != 0) {
1063                                wp->flags |= WEBS_IF_MODIFIED;
1064                        }
1065
1066                        bfreeSafe(B_L, cmd);
1067#endif /* WEBS_IF_MODIFIED_SUPPORT */
1068                }
1069        }
1070}
1071
1072/******************************************************************************/
1073/*
1074 *      Set the variable (CGI) environment for this request. Create variables
1075 *      for all standard CGI variables. Also decode the query string and create
1076 *      a variable for each name=value pair.
1077 */
1078
1079void websSetEnv(webs_t wp)
1080{
1081        char_t  portBuf[8];
1082        char_t  *keyword, *value, *valCheck, *valNew;
1083
1084        a_assert(websValid(wp));
1085
1086        websSetVar(wp, T("QUERY_STRING"), wp->query);
1087        websSetVar(wp, T("GATEWAY_INTERFACE"), T("CGI/1.1"));
1088        websSetVar(wp, T("SERVER_HOST"), websHost);
1089        websSetVar(wp, T("SERVER_NAME"), websHost);
1090        websSetVar(wp, T("SERVER_URL"), websHostUrl);
1091        websSetVar(wp, T("REMOTE_HOST"), wp->ipaddr);
1092        websSetVar(wp, T("REMOTE_ADDR"), wp->ipaddr);
1093        websSetVar(wp, T("PATH_INFO"), wp->path);
1094        stritoa(websPort, portBuf, sizeof(portBuf));
1095        websSetVar(wp, T("SERVER_PORT"), portBuf);
1096        websSetVar(wp, T("SERVER_ADDR"), websIpaddr);
1097        fmtAlloc(&value, FNAMESIZE, T("%s/%s"), WEBS_NAME, WEBS_VERSION);
1098        websSetVar(wp, T("SERVER_SOFTWARE"), value);
1099        bfreeSafe(B_L, value);
1100        websSetVar(wp, T("SERVER_PROTOCOL"), wp->protoVersion);
1101
1102/*
1103 *      Decode and create an environment query variable for each query keyword.
1104 *      We split into pairs at each '&', then split pairs at the '='.
1105 *      Note: we rely on wp->decodedQuery preserving the decoded values in the
1106 *      symbol table.
1107 */
1108        wp->decodedQuery = bstrdup(B_L, wp->query);
1109        keyword = gstrtok(wp->decodedQuery, T("&"));
1110        while (keyword != NULL) {
1111                if ((value = gstrchr(keyword, '=')) != NULL) {
1112                        *value++ = '\0';
1113                        websDecodeUrl(keyword, keyword, gstrlen(keyword));
1114                        websDecodeUrl(value, value, gstrlen(value));
1115                } else {
1116                        value = T("");
1117                }
1118
1119                if (*keyword) {
1120/*
1121 *                      If keyword has already been set, append the new value to what has
1122 *                      been stored.
1123 */
1124                        if ((valCheck = websGetVar(wp, keyword, NULL)) != 0) {
1125                                fmtAlloc(&valNew, 256, T("%s %s"), valCheck, value);
1126                                websSetVar(wp, keyword, valNew);
1127                                bfreeSafe(B_L, valNew);
1128                        } else {
1129                                websSetVar(wp, keyword, value);
1130                        }
1131                }
1132                keyword = gstrtok(NULL, T("&"));
1133        }
1134
1135#if EMF
1136/*
1137 *      Add GoAhead Embedded Management Framework defines
1138 */
1139        websSetEmfEnvironment(wp);
1140#endif
1141}
1142
1143/******************************************************************************/
1144/*
1145 *      Define a webs (CGI) variable for this connection. Also create in relevant
1146 *      scripting engines. Note: the incoming value may be volatile.
1147 */
1148
1149void websSetVar(webs_t wp, char_t *var, char_t *value)
1150{
1151        value_t          v;
1152
1153        a_assert(websValid(wp));
1154
1155/*
1156 *      value_instring will allocate the string if required.
1157 */
1158        if (value) {
1159                v = valueString(value, VALUE_ALLOCATE);
1160        } else {
1161                v = valueString(T(""), VALUE_ALLOCATE);
1162        }
1163        symEnter(wp->cgiVars, var, v, 0);
1164}
1165
1166/******************************************************************************/
1167/*
1168 *      Return TRUE if a webs variable exists for this connection.
1169 */
1170
1171int websTestVar(webs_t wp, char_t *var)
1172{
1173        sym_t           *sp;
1174
1175        a_assert(websValid(wp));
1176
1177        if (var == NULL || *var == '\0') {
1178                return 0;
1179        }
1180
1181        if ((sp = symLookup(wp->cgiVars, var)) == NULL) {
1182                return 0;
1183        }
1184        return 1;
1185}
1186
1187/******************************************************************************/
1188/*
1189 *      Get a webs variable but return a default value if string not found.
1190 *      Note, defaultGetValue can be NULL to permit testing existence.
1191 */
1192
1193char_t *websGetVar(webs_t wp, char_t *var, char_t *defaultGetValue)
1194{
1195        sym_t   *sp;
1196
1197        a_assert(websValid(wp));
1198        a_assert(var && *var);
1199 
1200        if ((sp = symLookup(wp->cgiVars, var)) != NULL) {
1201                a_assert(sp->content.type == string);
1202                if (sp->content.value.string) {
1203                        return sp->content.value.string;
1204                } else {
1205                        return T("");
1206                }
1207        }
1208        return defaultGetValue;
1209}
1210
1211/******************************************************************************/
1212/*
1213 *      Return TRUE if a webs variable is set to a given value
1214 */
1215
1216int websCompareVar(webs_t wp, char_t *var, char_t *value)
1217{
1218        a_assert(websValid(wp));
1219        a_assert(var && *var);
1220 
1221        if (gstrcmp(value, websGetVar(wp, var, T(" __UNDEF__ "))) == 0) {
1222                return 1;
1223        }
1224        return 0;
1225}
1226
1227/******************************************************************************/
1228/*
1229 *      Cancel the request timeout. Note may be called multiple times.
1230 */
1231
1232void websTimeoutCancel(webs_t wp)
1233{
1234        a_assert(websValid(wp));
1235
1236        if (wp->timeout >= 0) {
1237                emfUnschedCallback(wp->timeout);
1238                wp->timeout = -1;
1239        }
1240}
1241
1242/******************************************************************************/
1243/*
1244 *      Output a HTTP response back to the browser. If redirect is set to a
1245 *      URL, the browser will be sent to this location.
1246 */
1247
1248void websResponse(webs_t wp, int code, char_t *message, char_t *redirect)
1249{
1250        char_t          *date;
1251
1252        a_assert(websValid(wp));
1253
1254/*
1255 *      IE3.0 needs no Keep Alive for some return codes.
1256 */
1257        wp->flags &= ~WEBS_KEEP_ALIVE;
1258
1259/*
1260 *      Only output the header if a header has not already been output.
1261 */
1262        if ( !(wp->flags & WEBS_HEADER_DONE)) {
1263                wp->flags |= WEBS_HEADER_DONE;
1264                websWrite(wp, T("HTTP/1.1 %d %s\r\n"), code, websErrorMsg(code));
1265/*             
1266 *              By license terms the following line of code must not be modified.
1267 */
1268                websWrite(wp, T("Server: %s\r\n"), WEBS_NAME);
1269
1270/*             
1271 *              Timestamp/Date is usually the next to go
1272 */
1273                if ((date = websGetDateString(NULL)) != NULL) {
1274                        websWrite(wp, T("Date: %s\r\n"), date);
1275                        bfree(B_L, date);
1276                }
1277/*
1278 *              If authentication is required, send the auth header info
1279 */
1280                if (code == 401) {
1281                        if (!(wp->flags & WEBS_AUTH_DIGEST)) {
1282                                websWrite(wp, T("WWW-Authenticate: Basic realm=\"%s\"\r\n"),
1283                                        websGetRealm());
1284#ifdef DIGEST_ACCESS_SUPPORT
1285                        } else {
1286                                char_t *nonce, *opaque;
1287
1288                                nonce = websCalcNonce(wp),
1289                                opaque = websCalcOpaque(wp),
1290                                websWrite(wp,
1291                                        T("WWW-Authenticate: Digest realm=\"%s\", domain=\"%s\",")
1292                                        T("qop=\"%s\", nonce=\"%s\", opaque=\"%s\",")
1293                                        T("algorithm=\"%s\", stale=\"%s\"\r\n"),
1294                                        websGetRealm(),
1295                                        websGetHostUrl(),
1296                                        T("auth"),
1297                                        nonce,
1298                                        opaque, T("MD5"), T("FALSE"));
1299                                bfree(B_L, nonce);
1300                                bfree(B_L, opaque);
1301#endif
1302                        }
1303                }
1304
1305                if (wp->flags & WEBS_KEEP_ALIVE) {
1306                        websWrite(wp, T("Connection: keep-alive\r\n"));
1307                }
1308
1309                websWrite(wp, T("Pragma: no-cache\r\nCache-Control: no-cache\r\n"));
1310                websWrite(wp, T("Content-Type: text/html\r\n"));
1311/*
1312 *              We don't do a string length here as the message may be multi-line.
1313 *              Ie. <CR><LF> will count as only one and we will have a content-length
1314 *              that is too short.
1315 *
1316 *              websWrite(wp, T("Content-Length: %s\r\n"), message);
1317 */
1318                if (redirect) {
1319                        websWrite(wp, T("Location: %s\r\n"), redirect);
1320                }
1321                websWrite(wp, T("\r\n"));
1322        }
1323
1324/*
1325 *      If the browser didn't do a HEAD only request, send the message as well.
1326 */
1327        if ((wp->flags & WEBS_HEAD_REQUEST) == 0 && message && *message) {
1328                websWrite(wp, T("%s\r\n"), message);
1329        }
1330        websDone(wp, code);
1331}
1332
1333/******************************************************************************/
1334/*
1335 *      Redirect the user to another webs page
1336 */
1337
1338void websRedirect(webs_t wp, char_t *url)
1339{
1340        char_t  *msgbuf, *urlbuf, *redirectFmt;
1341
1342        a_assert(websValid(wp));
1343        a_assert(url);
1344
1345        websStats.redirects++;
1346        msgbuf = urlbuf = NULL;
1347
1348/*
1349 *      Some browsers require a http://host qualified URL for redirection
1350 */
1351        if (gstrstr(url, T("http://")) == NULL) {
1352                if (*url == '/') {
1353                        url++;
1354                }
1355
1356                redirectFmt = T("http://%s/%s");
1357
1358#ifdef WEBS_SSL_SUPPORT
1359                if (wp->flags & WEBS_SECURE) {
1360                        redirectFmt = T("https://%s/%s");
1361                }
1362#endif
1363
1364                fmtAlloc(&urlbuf, WEBS_MAX_URL + 80, redirectFmt,
1365                        websGetVar(wp, T("HTTP_HOST"),  websHostUrl), url);
1366                url = urlbuf;
1367        }
1368
1369/*
1370 *      Add human readable message for completeness. Should not be required.
1371 */
1372        fmtAlloc(&msgbuf, WEBS_MAX_URL + 80,
1373                T("<html><head></head><body>\r\n\
1374                This document has moved to a new <a href=\"%s\">location</a>.\r\n\
1375                Please update your documents to reflect the new location.\r\n\
1376                </body></html>\r\n"), url);
1377
1378        websResponse(wp, 302, msgbuf, url);
1379
1380        bfreeSafe(B_L, msgbuf);
1381        bfreeSafe(B_L, urlbuf);
1382}
1383
1384/******************************************************************************/
1385/*     
1386 *      Output an error message and cleanup
1387 */
1388
1389void websError(webs_t wp, int code, char_t *fmt, ...)
1390{
1391        va_list         args;
1392        char_t          *msg, *userMsg, *buf;
1393
1394        a_assert(websValid(wp));
1395        a_assert(fmt);
1396
1397        websStats.errors++;
1398
1399        va_start(args, fmt);
1400        userMsg = NULL;
1401        fmtValloc(&userMsg, WEBS_BUFSIZE, fmt, args);
1402        va_end(args);
1403
1404        msg = T("<html><head><title>Document Error: %s</title></head>\r\n\
1405                <body><h2>Access Error: %s</h2>\r\n\
1406                when trying to obtain <b>%s</b><br><p>%s</p></body></html>\r\n");
1407/*
1408 *      Ensure we have plenty of room
1409 */
1410        buf = NULL;
1411        fmtAlloc(&buf, WEBS_BUFSIZE, msg, websErrorMsg(code),
1412                websErrorMsg(code), wp->url, userMsg);
1413
1414        websResponse(wp, code, buf, NULL);
1415        bfreeSafe(B_L, buf);
1416        bfreeSafe(B_L, userMsg);
1417}
1418
1419/******************************************************************************/
1420/*
1421 *      Return the error message for a given code
1422 */
1423
1424static char_t *websErrorMsg(int code)
1425{
1426        websErrorType   *ep;
1427
1428        for (ep = websErrors; ep->code; ep++) {
1429                if (code == ep->code) {
1430                        return ep->msg;
1431                }
1432        }
1433        a_assert(0);
1434        return T("");
1435}
1436
1437/******************************************************************************/
1438/*
1439 *      Do formatted output to the browser. This is the public ASP and form
1440 *      write procedure.
1441 */
1442
1443int websWrite(webs_t wp, char_t *fmt, ...)
1444{
1445        va_list          vargs;
1446        char_t          *buf;
1447        int                      rc;
1448       
1449        a_assert(websValid(wp));
1450
1451        va_start(vargs, fmt);
1452
1453        buf = NULL;
1454        rc = 0;
1455        if (fmtValloc(&buf, WEBS_BUFSIZE, fmt, vargs) >= WEBS_BUFSIZE) {
1456                trace(0, T("webs: websWrite lost data, buffer overflow\n"));
1457        }
1458        va_end(vargs);
1459        a_assert(buf);
1460        if (buf) {
1461                rc = websWriteBlock(wp, buf, gstrlen(buf));
1462                bfree(B_L, buf);
1463        }
1464        return rc;
1465}
1466
1467/******************************************************************************/
1468/*
1469 *      Write a block of data of length "nChars" to the user's browser. Public
1470 *      write block procedure.  If unicode is turned on this function expects
1471 *      buf to be a unicode string and it converts it to ASCII before writing.
1472 *      See websWriteDataNonBlock to always write binary or ASCII data with no
1473 *      unicode conversion.  This returns the number of char_t's processed.
1474 *      It spins until nChars are flushed to the socket.  For non-blocking
1475 *      behavior, use websWriteDataNonBlock.
1476 */
1477
1478int websWriteBlock(webs_t wp, char_t *buf, int nChars)
1479{
1480        int             len, done;
1481        char    *asciiBuf, *pBuf;
1482
1483        a_assert(wp);
1484        a_assert(websValid(wp));
1485        a_assert(buf);
1486        a_assert(nChars >= 0);
1487
1488        done = len = 0;
1489
1490/*
1491 *      ballocUniToAsc will convert Unicode to strings to Ascii.  If Unicode is
1492 *      not turned on then ballocUniToAsc will not do the conversion.
1493 */
1494        pBuf = asciiBuf = ballocUniToAsc(buf, nChars);
1495
1496        while (nChars > 0) { 
1497#ifdef WEBS_SSL_SUPPORT
1498                if (wp->flags & WEBS_SECURE) {
1499                        if ((len = websSSLWrite(wp->wsp, pBuf, nChars)) < 0) {
1500                                bfree(B_L, asciiBuf);
1501                                return -1;
1502                        }
1503                        websSSLFlush(wp->wsp);
1504                } else {
1505                        if ((len = socketWrite(wp->sid, pBuf, nChars)) < 0) {
1506                                bfree(B_L, asciiBuf);
1507                                return -1;
1508                        }
1509                        socketFlush(wp->sid);
1510                }
1511#else /* ! WEBS_SSL_SUPPORT */
1512                if ((len = socketWrite(wp->sid, pBuf, nChars)) < 0) {
1513                        bfree(B_L, asciiBuf);
1514                        return -1;
1515                }
1516                socketFlush(wp->sid);
1517#endif /* WEBS_SSL_SUPPORT */
1518                nChars -= len;
1519                pBuf += len;
1520                done += len;
1521        }
1522
1523        bfree(B_L, asciiBuf);
1524        return done;
1525}
1526
1527/******************************************************************************/
1528/*
1529 *      Write a block of data of length "nChars" to the user's browser. Same as
1530 *      websWriteBlock except that it expects straight ASCII or binary and does no
1531 *      unicode conversion before writing the data.  If the socket cannot hold all
1532 *      the data, it will return the number of bytes flushed to the socket before
1533 *      it would have blocked.  This returns the number of chars processed or -1
1534 *      if socketWrite fails.
1535 */
1536
1537int websWriteDataNonBlock(webs_t wp, char *buf, int nChars)
1538{
1539        int r;
1540
1541        a_assert(wp);
1542        a_assert(websValid(wp));
1543        a_assert(buf);
1544        a_assert(nChars >= 0);
1545
1546#ifdef WEBS_SSL_SUPPORT
1547        if (wp->flags & WEBS_SECURE) {
1548                r = websSSLWrite(wp->wsp, buf, nChars);
1549                websSSLFlush(wp->wsp);
1550        } else {
1551                r = socketWrite(wp->sid, buf, nChars);
1552                socketFlush(wp->sid);
1553        }
1554#else
1555        r = socketWrite(wp->sid, buf, nChars);
1556        socketFlush(wp->sid);
1557#endif
1558
1559        return r;
1560}
1561
1562/******************************************************************************/
1563/*
1564 *      Decode a URL (or part thereof). Allows insitu decoding.
1565 */
1566
1567void websDecodeUrl(char_t *decoded, char_t *token, int len)
1568{
1569        char_t  *ip,  *op;
1570        int             num, i, c;
1571       
1572        a_assert(decoded);
1573        a_assert(token);
1574
1575        op = decoded;
1576        for (ip = token; *ip && len > 0; ip++, op++) {
1577                if (*ip == '+') {
1578                        *op = ' ';
1579                } else if (*ip == '%' && gisxdigit(ip[1]) && gisxdigit(ip[2])) {
1580
1581/*
1582 *                      Convert %nn to a single character
1583 */
1584                        ip++;
1585                        for (i = 0, num = 0; i < 2; i++, ip++) {
1586                                c = tolower(*ip);
1587                                if (c >= 'a' && c <= 'f') {
1588                                        num = (num * 16) + 10 + c - 'a';
1589                                } else {
1590                                        num = (num * 16) + c - '0';
1591                                }
1592                        }
1593                        *op = (char_t) num;
1594                        ip--;
1595
1596                } else {
1597                        *op = *ip;
1598                }
1599                len--;
1600        }
1601        *op = '\0';
1602}
1603
1604/******************************************************************************/
1605#if WEBS_LOG_SUPPORT
1606/*
1607 *      Output a log message
1608 */
1609
1610static void websLog(webs_t wp, int code)
1611{
1612        char_t  *buf;
1613        char    *abuf;
1614        int             len;
1615
1616        a_assert(websValid(wp));
1617
1618        buf = NULL;
1619        fmtAlloc(&buf, WEBS_MAX_URL + 80, T("%d %s %d %d\n"), time(0),
1620                wp->url, code, wp->written);
1621        len = gstrlen(buf);
1622        abuf = ballocUniToAsc(buf, len+1);
1623        write(websLogFd, abuf, len);
1624        bfreeSafe(B_L, buf);
1625        bfreeSafe(B_L, abuf);
1626}
1627
1628#endif /* WEBS_LOG_SUPPORT */
1629
1630/******************************************************************************/
1631/*
1632 *      Request timeout. The timeout triggers if we have not read any data from
1633 *      the users browser in the last WEBS_TIMEOUT period. If we have heard from
1634 *      the browser, simply re-issue the timeout.
1635 */
1636
1637void websTimeout(void *arg, int id)
1638{
1639        webs_t          wp;
1640        int                     delay, tm;
1641
1642        wp = (webs_t) arg;
1643        a_assert(websValid(wp));
1644
1645        tm = websGetTimeSinceMark(wp) * 1000;
1646        if (tm >= WEBS_TIMEOUT) {
1647                websStats.timeouts++;
1648                emfUnschedCallback(id);
1649
1650/*
1651 *              Clear the timeout id
1652 */
1653                wp->timeout = -1;
1654                websDone(wp, 404);
1655
1656        } else {
1657                delay = WEBS_TIMEOUT - tm;
1658                a_assert(delay > 0);
1659                emfReschedCallback(id, delay);
1660        }
1661}
1662
1663/******************************************************************************/
1664/*
1665 *      Called when the request is done.
1666 */
1667
1668void websDone(webs_t wp, int code)
1669{
1670        a_assert(websValid(wp));
1671
1672/*
1673 *      Disable socket handler in case keep alive set.
1674 */
1675        socketDeleteHandler(wp->sid);
1676
1677        if (code != 200) {
1678                wp->flags &= ~WEBS_KEEP_ALIVE;
1679        }
1680
1681#if WEBS_PROXY_SUPPORT
1682        if (! (wp->flags & WEBS_LOCAL_PAGE)) {
1683                websStats.activeNetRequests--;
1684        }
1685#endif
1686
1687#if WEBS_LOG_SUPPORT
1688        if (! (wp->flags & WEBS_REQUEST_DONE)) {
1689                websLog(wp, code);
1690        }
1691#endif
1692
1693/*
1694 *      Close any opened document by a handler
1695 */
1696        websPageClose(wp);
1697
1698/*
1699 *      Exit if secure.
1700 */
1701#ifdef WEBS_SSL_SUPPORT
1702        if (wp->flags & WEBS_SECURE) {
1703                websTimeoutCancel(wp);
1704                websSSLFlush(wp->wsp);
1705                socketCloseConnection(wp->sid);
1706                websFree(wp);
1707                return;
1708        }
1709#endif
1710
1711/*
1712 *      If using Keep Alive (HTTP/1.1) we keep the socket open for a period
1713 *      while waiting for another request on the socket.
1714 */
1715        if (wp->flags & WEBS_KEEP_ALIVE) {
1716                if (socketFlush(wp->sid) == 0) {
1717                        wp->state = WEBS_BEGIN;
1718                        wp->flags |= WEBS_REQUEST_DONE;
1719                        if (wp->header.buf) {
1720                                ringqFlush(&wp->header);
1721                        }
1722                        socketCreateHandler(wp->sid, SOCKET_READABLE, websSocketEvent,
1723                                (int) wp);
1724                        websTimeoutCancel(wp);
1725                        wp->timeout = emfSchedCallback(WEBS_TIMEOUT, websTimeout,
1726                                (void *) wp);
1727                        return;
1728                }
1729        } else {
1730                websTimeoutCancel(wp);
1731                socketSetBlock(wp->sid, 1);
1732                socketFlush(wp->sid);
1733                socketCloseConnection(wp->sid);
1734        }
1735        websFree(wp);
1736}
1737
1738/******************************************************************************/
1739/*
1740 *      Allocate a new webs structure
1741 */
1742
1743int websAlloc(int sid)
1744{
1745        webs_t          wp;
1746        int                     wid;
1747
1748/*
1749 *      Allocate a new handle for this connection
1750 */
1751        if ((wid = hAllocEntry((void***) &webs, &websMax,
1752                        sizeof(struct websRec))) < 0) {
1753                return -1;
1754        }
1755        wp = webs[wid];
1756
1757        wp->wid = wid;
1758        wp->sid = sid;
1759        wp->state = WEBS_BEGIN;
1760        wp->docfd = -1;
1761        wp->timeout = -1;
1762        wp->dir = NULL;
1763        wp->authType = NULL;
1764        wp->protocol = NULL;
1765        wp->protoVersion = NULL;
1766        wp->password = NULL;
1767        wp->userName = NULL;
1768#ifdef DIGEST_ACCESS_SUPPORT
1769        wp->realm = NULL;
1770        wp->nonce = NULL;
1771        wp->digest = NULL;
1772        wp->uri = NULL;
1773        wp->opaque = NULL;
1774        wp->nc = NULL;
1775        wp->cnonce = NULL;
1776        wp->qop = NULL;
1777#endif
1778#ifdef WEBS_SSL_SUPPORT
1779        wp->wsp = NULL;
1780#endif
1781
1782        ringqOpen(&wp->header, WEBS_HEADER_BUFINC, WEBS_MAX_HEADER);
1783
1784/*
1785 *      Create storage for the CGI variables. We supply the symbol tables for
1786 *      both the CGI variables and for the global functions. The function table
1787 *      is common to all webs instances (ie. all browsers)
1788 */
1789        wp->cgiVars = symOpen(WEBS_SYM_INIT);
1790
1791        return wid;
1792}
1793
1794/******************************************************************************/
1795/*
1796 *      Free a webs structure
1797 */
1798
1799void websFree(webs_t wp)
1800{
1801        a_assert(websValid(wp));
1802
1803        if (wp->path)
1804                bfree(B_L, wp->path);
1805        if (wp->url)
1806                bfree(B_L, wp->url);
1807        if (wp->host)
1808                bfree(B_L, wp->host);
1809        if (wp->lpath)
1810                bfree(B_L, wp->lpath);
1811        if (wp->query)
1812                bfree(B_L, wp->query);
1813        if (wp->decodedQuery)
1814                bfree(B_L, wp->decodedQuery);
1815        if (wp->authType)
1816                bfree(B_L, wp->authType);
1817        if (wp->password)
1818                bfree(B_L, wp->password);
1819        if (wp->userName)
1820                bfree(B_L, wp->userName);
1821        if (wp->cookie)
1822                bfree(B_L, wp->cookie);
1823        if (wp->userAgent)
1824                bfree(B_L, wp->userAgent);
1825        if (wp->dir)
1826                bfree(B_L, wp->dir);
1827        if (wp->protocol)
1828                bfree(B_L, wp->protocol);
1829        if (wp->protoVersion)
1830                bfree(B_L, wp->protoVersion);
1831        if (wp->cgiStdin)
1832                bfree(B_L, wp->cgiStdin);
1833
1834
1835#ifdef DIGEST_ACCESS_SUPPORT
1836        if (wp->realm)
1837                bfree(B_L, wp->realm);
1838        if (wp->uri)
1839                bfree(B_L, wp->uri);
1840        if (wp->digest)
1841                bfree(B_L, wp->digest);
1842        if (wp->opaque)
1843                bfree(B_L, wp->opaque);
1844        if (wp->nonce)
1845                bfree(B_L, wp->nonce);
1846        if (wp->nc)
1847                bfree(B_L, wp->nc);
1848        if (wp->cnonce)
1849                bfree(B_L, wp->cnonce);
1850        if (wp->qop)
1851                bfree(B_L, wp->qop);
1852#endif
1853#ifdef WEBS_SSL_SUPPORT
1854        websSSLFree(wp->wsp);
1855#endif
1856        symClose(wp->cgiVars);
1857
1858        if (wp->header.buf) {
1859                ringqClose(&wp->header);
1860        }
1861
1862        websMax = hFree((void***) &webs, wp->wid);
1863        bfree(B_L, wp);
1864        a_assert(websMax >= 0);
1865}
1866
1867/******************************************************************************/
1868/*
1869 *      Return the server address
1870 */
1871
1872char_t *websGetHost()
1873{
1874        return websHost;
1875}
1876
1877/******************************************************************************/
1878/*
1879 *      Return the the url to access the server. (ip address)
1880 */
1881
1882char_t *websGetIpaddrUrl()
1883{
1884        return websIpaddrUrl;
1885}
1886
1887/******************************************************************************/
1888/*
1889 *      Return the server address
1890 */
1891
1892char_t *websGetHostUrl()
1893{
1894        return websHostUrl;
1895}
1896
1897/******************************************************************************/
1898/*
1899 *      Return the listen port
1900 */
1901
1902int websGetPort()
1903{
1904        return websPort;
1905}
1906
1907/******************************************************************************/
1908/*
1909 *      Get the number of bytes to write
1910 */
1911
1912int websGetRequestBytes(webs_t wp)
1913{
1914        a_assert(websValid(wp));
1915
1916        return wp->numbytes;
1917}
1918
1919/******************************************************************************/
1920/*
1921 *      Get the directory for this request
1922 */
1923
1924char_t *websGetRequestDir(webs_t wp)
1925{
1926        a_assert(websValid(wp));
1927
1928        if (wp->dir == NULL) {
1929                return T("");
1930        }
1931
1932        return wp->dir;
1933}
1934
1935/******************************************************************************/
1936/*
1937 *      Get the flags for this request
1938 */
1939
1940int websGetRequestFlags(webs_t wp)
1941{
1942        a_assert(websValid(wp));
1943
1944        return wp->flags;
1945}
1946
1947/******************************************************************************/
1948/*
1949 *      Return the IP address
1950 */
1951
1952char_t *websGetRequestIpaddr(webs_t wp)
1953{
1954        a_assert(websValid(wp));
1955
1956        return wp->ipaddr;
1957}
1958
1959/******************************************************************************/
1960/*
1961 *      Set the local path for the request
1962 */
1963
1964char_t *websGetRequestLpath(webs_t wp)
1965{
1966        a_assert(websValid(wp));
1967
1968#if WEBS_PAGE_ROM
1969        return wp->path;
1970#else
1971        return wp->lpath;
1972#endif
1973}
1974
1975/******************************************************************************/
1976/*
1977 *      Get the path for this request
1978 */
1979
1980char_t *websGetRequestPath(webs_t wp)
1981{
1982        a_assert(websValid(wp));
1983
1984        if (wp->path == NULL) {
1985                return T("");
1986        }
1987
1988        return wp->path;
1989}
1990
1991/******************************************************************************/
1992/*
1993 *      Return the password
1994 */
1995
1996char_t *websGetRequestPassword(webs_t wp)
1997{
1998        a_assert(websValid(wp));
1999
2000        return wp->password;
2001}
2002
2003/******************************************************************************/
2004/*
2005 *      Return the request type
2006 */
2007
2008char_t *websGetRequestType(webs_t wp)
2009{
2010        a_assert(websValid(wp));
2011
2012        return wp->type;
2013}
2014
2015/******************************************************************************/
2016/*
2017 *      Return the username
2018 */
2019
2020char_t *websGetRequestUserName(webs_t wp)
2021{
2022        a_assert(websValid(wp));
2023
2024        return wp->userName;
2025}
2026
2027/******************************************************************************/
2028/*
2029 *      Get the number of bytes written
2030 */
2031
2032int websGetRequestWritten(webs_t wp)
2033{
2034        a_assert(websValid(wp));
2035
2036        return wp->written;
2037}
2038
2039/******************************************************************************/
2040/*
2041 *      Set the hostname
2042 */
2043
2044void websSetHost(char_t *host)
2045{
2046        gstrncpy(websHost, host, TSZ(websHost));
2047}
2048
2049/******************************************************************************/
2050/*
2051 *      Set the host URL
2052 */
2053
2054void websSetHostUrl(char_t *url)
2055{
2056        a_assert(url && *url);
2057
2058        bfreeSafe(B_L, websHostUrl);
2059        websHostUrl = gstrdup(B_L, url);
2060}
2061
2062/******************************************************************************/
2063/*
2064 *      Set the IP address
2065 */
2066
2067void websSetIpaddr(char_t *ipaddr)
2068{
2069        a_assert(ipaddr && *ipaddr);
2070
2071        gstrncpy(websIpaddr, ipaddr, TSZ(websIpaddr));
2072}
2073
2074/******************************************************************************/
2075/*
2076 *      Set the number of bytes to write
2077 */
2078
2079void websSetRequestBytes(webs_t wp, int bytes)
2080{
2081        a_assert(websValid(wp));
2082        a_assert(bytes >= 0);
2083
2084        wp->numbytes = bytes;
2085}
2086
2087/******************************************************************************/
2088/*
2089 *      Set the flags for this request
2090 */
2091
2092void websSetRequestFlags(webs_t wp, int flags)
2093{
2094        a_assert(websValid(wp));
2095
2096        wp->flags = flags;
2097}
2098
2099/******************************************************************************/
2100/*
2101 *      Set the local path for the request
2102 */
2103
2104void websSetRequestLpath(webs_t wp, char_t *lpath)
2105{
2106        a_assert(websValid(wp));
2107        a_assert(lpath && *lpath);
2108
2109        if (wp->lpath) {
2110                bfree(B_L, wp->lpath);
2111        }
2112        wp->lpath = bstrdup(B_L, lpath);
2113        websSetVar(wp, T("PATH_TRANSLATED"), wp->lpath);
2114}
2115
2116/******************************************************************************/
2117/*
2118 *      Update the URL path and the directory containing the web page
2119 */
2120
2121void websSetRequestPath(webs_t wp, char_t *dir, char_t *path)
2122{
2123        char_t  *tmp;
2124
2125        a_assert(websValid(wp));
2126
2127        if (dir) {
2128                tmp = wp->dir;
2129                wp->dir = bstrdup(B_L, dir);
2130                if (tmp) {
2131                        bfree(B_L, tmp);
2132                }
2133        }
2134        if (path) {
2135                tmp = wp->path;
2136                wp->path = bstrdup(B_L, path);
2137                websSetVar(wp, T("PATH_INFO"), wp->path);
2138                if (tmp) {
2139                        bfree(B_L, tmp);
2140                }
2141        }
2142}
2143
2144/******************************************************************************/
2145/*
2146 *      Set the Write handler for this socket
2147 */
2148
2149void websSetRequestSocketHandler(webs_t wp, int mask, void (*fn)(webs_t wp))
2150{
2151        a_assert(websValid(wp));
2152
2153        wp->writeSocket = fn;
2154        socketCreateHandler(wp->sid, SOCKET_WRITABLE, websSocketEvent, (int) wp);
2155}
2156
2157/******************************************************************************/
2158/*
2159 *      Set the number of bytes written
2160 */
2161
2162void websSetRequestWritten(webs_t wp, int written)
2163{
2164        a_assert(websValid(wp));
2165
2166        wp->written = written;
2167}
2168
2169/******************************************************************************/
2170/*
2171 *      Reurn true if the webs handle is valid
2172 */
2173
2174int websValid(webs_t wp)
2175{
2176        int             wid;
2177
2178        for (wid = 0; wid < websMax; wid++) {
2179                if (wp == webs[wid]) {
2180                        return 1;
2181                }
2182        }
2183        return 0;
2184}
2185
2186/******************************************************************************/
2187/*
2188 *      Build an ASCII time string.  If sbuf is NULL we use the current time,
2189 *      else we use the last modified time of sbuf;
2190 */
2191
2192char_t *websGetDateString(websStatType *sbuf)
2193{
2194        char_t* cp, *r;
2195        time_t  now;
2196
2197        if (sbuf == NULL) {
2198                time(&now);
2199        } else {
2200                now = sbuf->mtime;
2201        }
2202        if ((cp = gctime(&now)) != NULL) {
2203                cp[gstrlen(cp) - 1] = '\0';
2204                r = bstrdup(B_L, cp);
2205                return r;
2206        }
2207        return NULL;
2208}
2209
2210/******************************************************************************/
2211/*
2212 *      Mark time. Set a timestamp so that, later, we can return the number of
2213 *      seconds since we made the mark. Note that the mark my not be a
2214 *      "real" time, but rather a relative marker.
2215 */
2216
2217void websMarkTime(webs_t wp)
2218{
2219        wp->timestamp = time(0);
2220}
2221
2222/******************************************************************************/
2223/*
2224 *      Get the number of seconds since the last mark.
2225 */
2226
2227static int websGetTimeSinceMark(webs_t wp)
2228{
2229        return time(0) - wp->timestamp;
2230}
2231
2232/******************************************************************************/
2233/*
2234 *      Store the new realm name
2235 */
2236
2237void websSetRealm(char_t *realmName)
2238{
2239        a_assert(realmName);
2240
2241        gstrncpy(websRealm, realmName, TSZ(websRealm));
2242}
2243
2244/******************************************************************************/
2245/*
2246 *      Return the realm name (used for authorization)
2247 */
2248
2249char_t *websGetRealm()
2250{
2251        return websRealm;
2252}
2253
2254
2255#if WEBS_IF_MODIFIED_SUPPORT
2256/******************************************************************************/
2257/*     
2258 *      These functions are intended to closely mirror the syntax for HTTP-date
2259 *      from RFC 2616 (HTTP/1.1 spec).  This code was submitted by Pete Bergstrom.
2260 */
2261
2262/*     
2263 *      RFC1123Date     = wkday "," SP date1 SP time SP "GMT"
2264 *      RFC850Date      = weekday "," SP date2 SP time SP "GMT"
2265 *      ASCTimeDate     = wkday SP date3 SP time SP 4DIGIT
2266 *
2267 *      Each of these functions tries to parse the value and update the index to
2268 *      the point it leaves off parsing.
2269 */
2270
2271typedef enum { JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC } MonthEnumeration;
2272typedef enum { SUN, MON, TUE, WED, THU, FRI, SAT } WeekdayEnumeration;
2273
2274/******************************************************************************/
2275/*     
2276 *      Parse an N-digit value
2277 */
2278
2279static int parseNDIGIT(char_t *buf, int digits, int *index)
2280{
2281        int tmpIndex, returnValue;
2282
2283        returnValue = 0;
2284
2285        for (tmpIndex = *index; tmpIndex < *index+digits; tmpIndex++) {
2286                if (gisdigit(buf[tmpIndex])) {
2287                        returnValue = returnValue * 10 + (buf[tmpIndex] - T('0'));
2288                }
2289        }
2290        *index = tmpIndex;
2291       
2292        return returnValue;
2293}
2294
2295/******************************************************************************/
2296/*
2297 *      Return an index into the month array
2298 */
2299
2300static int parseMonth(char_t *buf, int *index)
2301{
2302/*     
2303 *      "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" |
2304 *      "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec"
2305 */
2306        int tmpIndex, returnValue;
2307
2308        returnValue = -1;
2309        tmpIndex = *index;
2310
2311        switch (buf[tmpIndex]) {
2312                case 'A':
2313                        switch (buf[tmpIndex+1]) {
2314                                case 'p':
2315                                        returnValue = APR;
2316                                        break;
2317                                case 'u':
2318                                        returnValue = AUG;
2319                                        break;
2320                        }
2321                        break;
2322                case 'D':
2323                        returnValue = DEC;
2324                        break;
2325                case 'F':
2326                        returnValue = FEB;
2327                        break;
2328                case 'J':
2329                        switch (buf[tmpIndex+1]) {
2330                                case 'a':
2331                                        returnValue = JAN;
2332                                        break;
2333                                case 'u':
2334                                        switch (buf[tmpIndex+2]) {
2335                                                case 'l':
2336                                                        returnValue = JUL;
2337                                                        break;
2338                                                case 'n':
2339                                                        returnValue = JUN;
2340                                                        break;
2341                                        }
2342                                        break;
2343                        }
2344                        break;
2345                case 'M':
2346                        switch (buf[tmpIndex+1]) {
2347                                case 'a':
2348                                        switch (buf[tmpIndex+2]) {
2349                                                case 'r':
2350                                                        returnValue = MAR;
2351                                                        break;
2352                                                case 'y':
2353                                                        returnValue = MAY;
2354                                                        break;
2355                                        }
2356                                        break;
2357                        }
2358                        break;
2359                case 'N':
2360                        returnValue = NOV;
2361                        break;
2362                case 'O':
2363                        returnValue = OCT;
2364                        break;
2365                case 'S':
2366                        returnValue = SEP;
2367                        break;
2368        }
2369
2370        if (returnValue >= 0) {
2371                *index += 3;
2372        }
2373
2374        return returnValue;
2375}
2376
2377/******************************************************************************/
2378/*
2379 *      Parse a year value (either 2 or 4 digits)
2380 */
2381
2382static int parseYear(char_t *buf, int *index)
2383{
2384        int tmpIndex, returnValue;
2385
2386        tmpIndex = *index;
2387        returnValue = parseNDIGIT(buf, 4, &tmpIndex);
2388
2389        if (returnValue >= 0) {
2390                *index = tmpIndex;
2391        } else {
2392                returnValue = parseNDIGIT(buf, 2, &tmpIndex);
2393                if (returnValue >= 0) {
2394/*
2395 *                      Assume that any year earlier than the start of the
2396 *                      epoch for time_t (1970) specifies 20xx
2397 */
2398                        if (returnValue < 70) {
2399                                returnValue += 2000;
2400                        } else {
2401                                returnValue += 1900;
2402                        }
2403
2404                        *index = tmpIndex;
2405                }
2406        }
2407
2408        return returnValue;
2409}
2410
2411/******************************************************************************/
2412/*
2413 *      The formulas used to build these functions are from "Calendrical Calculations",
2414 *      by Nachum Dershowitz, Edward M. Reingold, Cambridge University Press, 1997.
2415 */
2416
2417#include <math.h>
2418
2419const int GregorianEpoch = 1;
2420
2421/******************************************************************************/
2422/*
2423 *  Determine if year is a leap year
2424 */
2425
2426int GregorianLeapYearP(long year)
2427{
2428        int             result;
2429        long    tmp;
2430       
2431        tmp = year % 400;
2432
2433        if ((year % 4 == 0) &&
2434                (tmp != 100) &&
2435                (tmp != 200) &&
2436                (tmp != 300)) {
2437                result = TRUE;
2438        } else {
2439                result = FALSE;
2440        }
2441
2442        return result;
2443}
2444
2445/******************************************************************************/
2446/*
2447 *  Return the fixed date from the gregorian date
2448 */
2449
2450long FixedFromGregorian(long month, long day, long year)
2451{
2452        long fixedDate;
2453
2454        fixedDate = (long)(GregorianEpoch - 1 + 365 * (year - 1) +
2455                floor((year - 1) / 4.0) -
2456                floor((double)(year - 1) / 100.0) +
2457                floor((double)(year - 1) / 400.0) +
2458                floor((367.0 * ((double)month) - 362.0) / 12.0));
2459
2460        if (month <= 2) {
2461                fixedDate += 0;
2462        } else if (TRUE == GregorianLeapYearP(year)) {
2463                fixedDate += -1;
2464        } else {
2465                fixedDate += -2;
2466        }
2467
2468        fixedDate += day;
2469
2470        return fixedDate;
2471}
2472
2473/******************************************************************************/
2474/*
2475 *  Return the gregorian year from a fixed date
2476 */
2477
2478long GregorianYearFromFixed(long fixedDate)
2479{
2480        long result, d0, n400, d1, n100, d2, n4, d3, n1, d4, year;
2481
2482        d0 =    fixedDate - GregorianEpoch;
2483        n400 =  (long)(floor((double)d0 / (double)146097));
2484        d1 =    d0 % 146097;
2485        n100 =  (long)(floor((double)d1 / (double)36524));
2486        d2 =    d1 % 36524;
2487        n4 =    (long)(floor((double)d2 / (double)1461));
2488        d3 =    d2 % 1461;
2489        n1 =    (long)(floor((double)d3 / (double)365));
2490        d4 =    (d3 % 365) + 1;
2491        year =  400 * n400 + 100 * n100 + 4 * n4 + n1;
2492
2493        if ((n100 == 4) || (n1 == 4)) {
2494                result = year;
2495        } else {
2496                result = year + 1;
2497        }
2498
2499        return result;
2500}
2501
2502/******************************************************************************/
2503/*
2504 *      Returns the Gregorian date from a fixed date
2505 *      (not needed for this use, but included for completeness
2506 */
2507
2508#if 0
2509GregorianFromFixed(long fixedDate, long *month, long *day, long *year)
2510{
2511        long priorDays, correction;
2512
2513        *year =                 GregorianYearFromFixed(fixedDate);
2514        priorDays =             fixedDate - FixedFromGregorian(1, 1, *year);
2515
2516        if (fixedDate < FixedFromGregorian(3,1,*year)) {
2517                correction = 0;
2518        } else if (true == GregorianLeapYearP(*year)) {
2519                correction = 1;
2520        } else {
2521                correction = 2;
2522        }
2523
2524        *month = (long)(floor((12.0 * (double)(priorDays + correction) + 373.0) / 367.0));
2525        *day = fixedDate - FixedFromGregorian(*month, 1, *year);
2526}
2527#endif
2528
2529/******************************************************************************/
2530/*
2531 *      Returns the difference between two Gregorian dates
2532 */
2533
2534long GregorianDateDifference(   long month1, long day1, long year1,
2535                                                                long month2, long day2, long year2)
2536{
2537        return FixedFromGregorian(month2, day2, year2) -
2538                FixedFromGregorian(month1, day1, year1);
2539}
2540
2541
2542/******************************************************************************/
2543/*
2544 *      Return the number of seconds into the current day
2545 */
2546
2547#define SECONDS_PER_DAY 24*60*60
2548
2549static int parseTime(char_t *buf, int *index)
2550{
2551/*     
2552 *      Format of buf is - 2DIGIT ":" 2DIGIT ":" 2DIGIT
2553 */
2554        int returnValue, tmpIndex, hourValue, minuteValue, secondValue;
2555
2556        hourValue = minuteValue = secondValue = -1;
2557        returnValue = -1;
2558        tmpIndex = *index;
2559
2560        hourValue = parseNDIGIT(buf, 2, &tmpIndex);
2561
2562        if (hourValue >= 0) {
2563                tmpIndex++;
2564                minuteValue = parseNDIGIT(buf, 2, &tmpIndex);
2565                if (minuteValue >= 0) {
2566                        tmpIndex++;
2567                        secondValue = parseNDIGIT(buf, 2, &tmpIndex);
2568                }
2569        }
2570
2571        if ((hourValue >= 0) &&
2572                (minuteValue >= 0) &&
2573                (secondValue >= 0)) {
2574                returnValue = (((hourValue * 60) + minuteValue) * 60) + secondValue;
2575                *index = tmpIndex;
2576        }
2577
2578        return returnValue;
2579}
2580
2581/******************************************************************************/
2582/*
2583 *      Return the equivalent of time() given a gregorian date
2584 */
2585
2586static time_t dateToTimet(int year, int month, int day)
2587{
2588        long dayDifference;
2589
2590        dayDifference = FixedFromGregorian(month, day, year) -
2591                FixedFromGregorian(1, 1, 1970);
2592
2593        return dayDifference * SECONDS_PER_DAY;
2594}
2595
2596/******************************************************************************/
2597/*
2598 *      Return the number of seconds between Jan 1, 1970 and the parsed date
2599 *      (corresponds to documentation for time() function)
2600 */
2601
2602static time_t parseDate1or2(char_t *buf, int *index)
2603{
2604/*     
2605 *      Format of buf is either
2606 *      2DIGIT SP month SP 4DIGIT
2607 *      or
2608 *      2DIGIT "-" month "-" 2DIGIT
2609 */
2610        int             dayValue, monthValue, yearValue, tmpIndex;
2611        time_t  returnValue;
2612
2613        returnValue = (time_t) -1;
2614        tmpIndex = *index;
2615
2616        dayValue = monthValue = yearValue = -1;
2617
2618        if (buf[tmpIndex] == T(',')) {
2619/*
2620 *              Skip over the ", "
2621 */
2622                tmpIndex += 2;
2623
2624                dayValue = parseNDIGIT(buf, 2, &tmpIndex);
2625                if (dayValue >= 0) {
2626/*
2627 *                      Skip over the space or hyphen
2628 */
2629                        tmpIndex++;
2630                        monthValue = parseMonth(buf, &tmpIndex);
2631                        if (monthValue >= 0) {
2632/*
2633 *                              Skip over the space or hyphen
2634 */
2635                                tmpIndex++;
2636                                yearValue = parseYear(buf, &tmpIndex);
2637                        }
2638                }
2639
2640                if ((dayValue >= 0) &&
2641                        (monthValue >= 0) &&
2642                        (yearValue >= 0)) {
2643                        if (yearValue < 1970) {
2644/*                             
2645 *                              Allow for Microsoft IE's year 1601 dates
2646 */
2647                                returnValue = 0;
2648                        } else {
2649                                returnValue = dateToTimet(yearValue, monthValue, dayValue);
2650                        }
2651                        *index = tmpIndex;
2652                }
2653        }
2654       
2655        return returnValue;
2656}
2657
2658/******************************************************************************/
2659/*
2660 *      Return the number of seconds between Jan 1, 1970 and the parsed date
2661 */
2662
2663static time_t parseDate3Time(char_t *buf, int *index)
2664{
2665/*
2666 *      Format of buf is month SP ( 2DIGIT | ( SP 1DIGIT ))
2667 */
2668        int             dayValue, monthValue, yearValue, timeValue, tmpIndex;
2669        time_t  returnValue;
2670
2671        returnValue = (time_t) -1;
2672        tmpIndex = *index;
2673
2674        dayValue = monthValue = yearValue = timeValue = -1;
2675
2676        monthValue = parseMonth(buf, &tmpIndex);
2677        if (monthValue >= 0) {
2678/*             
2679 *              Skip over the space
2680 */
2681                tmpIndex++;
2682                if (buf[tmpIndex] == T(' ')) {
2683/*
2684 *                      Skip over this space too
2685 */
2686                        tmpIndex++;
2687                        dayValue = parseNDIGIT(buf, 1, &tmpIndex);
2688                } else {
2689                        dayValue = parseNDIGIT(buf, 2, &tmpIndex);
2690                }
2691/*             
2692 *              Now get the time and time SP 4DIGIT
2693 */
2694                timeValue = parseTime(buf, &tmpIndex);
2695                if (timeValue >= 0) {
2696/*                     
2697 *                      Now grab the 4DIGIT year value
2698 */
2699                        yearValue = parseYear(buf, &tmpIndex);
2700                }
2701        }
2702
2703        if ((dayValue >= 0) &&
2704                (monthValue >= 0) &&
2705                (yearValue >= 0)) {
2706                returnValue = dateToTimet(yearValue, monthValue, dayValue);
2707                returnValue += timeValue;
2708                *index = tmpIndex;
2709        }
2710       
2711        return returnValue;
2712}
2713
2714
2715/******************************************************************************/
2716/*
2717 *      Although this looks like a trivial function, I found I was replicating the implementation
2718 *      seven times in the parseWeekday function. In the interests of minimizing code size
2719 *      and redundancy, it is broken out into a separate function. The cost of an extra
2720 *      function call I can live with given that it should only be called once per HTTP request.
2721 */
2722
2723static int bufferIndexIncrementGivenNTest(char_t *buf, int testIndex, char_t testChar,
2724                                                                                  int foundIncrement, int notfoundIncrement)
2725{
2726        if (buf[testIndex] == testChar) {
2727                return foundIncrement;
2728        }
2729
2730        return notfoundIncrement;
2731}
2732
2733/******************************************************************************/
2734/*
2735 *      Return an index into a logical weekday array
2736 */
2737
2738static int parseWeekday(char_t *buf, int *index)
2739{
2740/*     
2741 *      Format of buf is either
2742 *      "Mon" | "Tue" | "Wed" | "Thu" | "Fri" | "Sat" | "Sun"
2743 *      or
2744 *      "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday"
2745 */
2746        int tmpIndex, returnValue;
2747
2748        returnValue = -1;
2749        tmpIndex = *index;
2750
2751        switch (buf[tmpIndex]) {
2752                case 'F':
2753                        returnValue = FRI;
2754                        *index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'd', sizeof("Friday"), 3);
2755                        break;
2756                case 'M':
2757                        returnValue = MON;
2758                        *index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'd', sizeof("Monday"), 3);
2759                        break;
2760                case 'S':
2761                        switch (buf[tmpIndex+1]) {
2762                                case 'a':
2763                                        returnValue = SAT;
2764                                        *index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'u', sizeof("Saturday"), 3);
2765                                        break;
2766                                case 'u':
2767                                        returnValue = SUN;
2768                                        *index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'd', sizeof("Sunday"), 3);
2769                                        break;
2770                        }
2771                        break;
2772                case 'T':
2773                        switch (buf[tmpIndex+1]) {
2774                                case 'h':
2775                                        returnValue = THU;
2776                                        *index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'r', sizeof("Thursday"), 3);
2777                                        break;
2778                                case 'u':
2779                                        returnValue = TUE;
2780                                        *index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 's', sizeof("Tuesday"), 3);
2781                                        break;
2782                        }
2783                        break;
2784                case 'W':
2785                        returnValue = WED;
2786                        *index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'n', sizeof("Wednesday"), 3);
2787                        break;
2788        }
2789        return returnValue;
2790}
2791
2792/******************************************************************************/
2793/*
2794 *              Parse the date and time string.
2795 */
2796
2797static time_t dateParse(time_t tip, char_t *cmd)
2798{
2799        int index, tmpIndex, weekday, timeValue;
2800        time_t parsedValue, dateValue;
2801
2802        parsedValue = (time_t) 0;
2803        index = timeValue = 0;
2804        weekday = parseWeekday(cmd, &index);
2805
2806        if (weekday >= 0) {
2807                tmpIndex = index;
2808                dateValue = parseDate1or2(cmd, &tmpIndex);
2809                if (dateValue >= 0) {
2810                        index = tmpIndex + 1;
2811/*
2812 *                      One of these two forms is being used
2813 *                      wkday "," SP date1 SP time SP "GMT"
2814 *                      weekday "," SP date2 SP time SP "GMT"
2815 */
2816                        timeValue = parseTime(cmd, &index);
2817                        if (timeValue >= 0) {
2818/*                             
2819 *                              Now match up that "GMT" string for completeness
2820 *                              Compute the final value if there were no problems in the parse
2821 */
2822                                if ((weekday >= 0) &&
2823                                        (dateValue >= 0) &&
2824                                        (timeValue >= 0)) {
2825                                        parsedValue = dateValue + timeValue;
2826                                }
2827                        }
2828                } else {
2829/*
2830 *                      Try the other form - wkday SP date3 SP time SP 4DIGIT
2831 */
2832                        tmpIndex = index;
2833                        parsedValue = parseDate3Time(cmd, &tmpIndex);
2834                }
2835        }
2836
2837        return parsedValue;
2838}
2839
2840#endif /* WEBS_IF_MODIFIED_SUPPORT */
2841
2842
2843/******************************************************************************/
Note: See TracBrowser for help on using the repository browser.