source: rtems/c/src/libnetworking/rtems_webserver/webs.c @ d0d73ec

4.104.114.84.95
Last change on this file since d0d73ec was a6b4c0df, checked in by Joel Sherrill <joel.sherrill@…>, on 09/01/00 at 10:57:21

2000-08-30 Joel Sherrill <joel@…>

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