source: project/wiki/eggref/4/spiffy @ 12704

Last change on this file since 12704 was 12704, checked in by sjamaan, 12 years ago

Try to document cgi-handler a bit better

File size: 24.7 KB
Line 
1[[tags: egg]]
2
3== Spiffy
4
5[[toc:]]
6
7=== Description
8
9A small web-server written in [[http://www.call-with-current-continuation.org|Chicken]].
10
11=== Author
12
13[[Felix Winkelmann]].  Currently maintained by [[Peter Bex]].
14
15=== Requirements
16
17Requires the [[intarweb]], [[matchable]] and [[sendfile]] extensions.
18
19=== Download
20
21[[http://www.call-with-current-continuation.org/eggs/spiffy.egg|spiffy.egg]]
22
23=== Documentation
24
25Spiffy is a web-server library for the Chicken Scheme system. It's
26quite easy to set up and use (whether as a library or a standalone
27server application) and it can be customized in numerous ways.
28
29=== Starting the server
30
31<procedure>(start-server [port: port-number])</procedure>
32
33Starts the server, to listen on the given port. Other configuration
34can be tweaked through SRFI-39 parameters. These are listed below.
35Once the server is started, server behaviour can be controlled through
36these parameters as well. By default, Spiffy will only serve static
37files. On directories, it will give a "403 forbidden", unless there
38is an index-file. If there is, that file's contents will be shown.
39
40All arguments directly supplied to {{start-server}} override the
41configuration parameter values.
42
43{{Port-number}} defaults to the value of {{server-port}} (see below).
44
45=== Configuration parameters
46
47The following parameters can be used to control spiffy's behaviour.
48Besides these parameters, you can also influence spiffy's behaviour by
49tweaking the [[intarweb]] parameters.
50
51<parameter>(server-software [product])</parameter>
52
53The server software product description. This should be a valid
54product value as used in the server and user-agent headers by intarweb;
55this is a list of lists. The inner lists contain the product name,
56the product version and a comment, all either a string or {{#f}}.
57Default: {{(("Spiffy" "a.b" "Running on Chicken x.y"))}}, with {{a.b}}
58being the Spiffy major/minor version and {{x.y}} being Chicken's.
59
60<parameter>(root-path [path])</parameter>
61
62The path to the document root, for the current vhost.
63Defaults to {{"./web"}}.
64
65<parameter>(server-port [port-number])</parameter>
66
67The port number on which to listen. Defaults to 8080.
68
69<parameter>(spiffy-user [name-or-uid])</parameter>
70
71The name or UID of a user to switch to just after binding the
72port. This only works if you start Spiffy as root, so it can bind port
7380 and then drop privileges. If {{#f}}, no switch will occur.
74Defaults to {{#f}}.
75
76<parameter>(spiffy-group [name-or-gid])</parameter>
77
78The name or GID of a group to switch to just after binding the
79port. This only works if you start Spiffy as root, so it can bind port
8080 and then drop privileges. If {{#f}}, it will be set to the primary
81group of {{spiffy-user}} if the user was selected. Otherwise, no
82change will occur.  Defaults to {{#f}}.
83
84<parameter>(index-files [file-list])</parameter>
85
86A list of filenames which are to be used as index files to serve when
87the requested URL identifies a directory.  Defaults to
88{{'("index.html" "index.xhtml")}}
89
90<parameter>(mime-type-map [extension->mimetype-list])</parameter>
91
92An alist of extensions (strings) to mime-types (symbols), to use
93for the content-type header when serving up a static file. Defaults to
94  (("xml" . text/xml)
95   ("html" . text/html)
96   ("xhtml" . text/xhtml+xml)
97   ("js"  . text/javascript)
98   ("pdf" . application/pdf)
99   ("css" . text/css)
100   ("png" . image/png)
101   ("ico" . image/x-icon)
102   ("gif" . image/gif)
103   ("jpeg" . image/jpeg)
104   ("jpg" . image/jpeg)
105   ("svg" . image/svg+xml)
106   ("bmp" . image/bmp)
107   ("txt" . text/plain))
108
109<parameter>(default-mime-type [mime-type])</parameter>
110
111The mime-type (a symbol) to use if none was found in the
112{{mime-type-map}}. Defaults to {{'application/octet-stream}}
113
114<parameter>(default-host [hostname])</parameter>
115
116The host name to use when no virtual host could be determined from the
117request.  See the section on virtual hosts below.
118
119<parameter>(vhost-map [host-regex->vhost-handler])</parameter>
120
121A mapping of virtual hosts (regex) to handlers (procedures of one
122argument; a continuation thunk). See the section on virtual hosts
123below. Defaults to {{`((".*" . ,(lambda (continue) (continue))))}}
124
125<parameter>(file-extension-handlers [extension->handler-list])</parameter>
126
127An alist mapping file extensions (strings) to handler procedures
128(lambdas of one argument; the file name relative to the webroot).
129Defaults to {{'()}}. If no handler was found, defaults to just sending
130a static file.
131
132<parameter>(spiffy-access-log [log-file-or-port])</parameter>
133
134Filename (string) or port to append access log output to.  Default:
135{{#f}} (disabled)
136
137<parameter>(spiffy-error-log [log-file-or-port])</parameter>
138
139Filename (string) or port to which error messages from evaluated code
140should be output. Default: {{(current-error-port)}}
141
142<parameter>(spiffy-debug-log [log-file-or-port])</parameter>
143
144Filename (string) or port to write debugging messages to.  Default:
145{{#f}} (disabled)
146
147<parameter>(access-file [string])</parameter>
148
149The name of an access file, or {{#f}} if not applicable.  This file is
150read when the directory is entered by the directory traversal system,
151and allows you to write dynamic handlers that can assign new values
152for parameters only for resources below that directory, very much like
153adding parameters in code before calling a procedure.  See the section
154"Access files" for more information.
155
156=== Handlers
157
158Besides "static" configuration, Spiffy also has several handlers for
159when something is to be served.
160
161<parameter>(handle-directory [proc])</parameter>
162
163The handler for directory entries. If the requested URL points to a
164directory which has no index file, this handler is invoked. It is a
165procedure of one argument, the path (a string) relative to the
166webroot. Defaults to a procedure which returns a "403 forbidden".
167
168<parameter>(handle-file [proc])</parameter>
169
170The handler for files. If the requested URL points to a file, this
171handler is invoked to serve the file. It is a procedure of one
172argument, the path (a string) relative to the webroot. Defaults to a
173procedure which sets the content-type and determines a handler based
174on the {{file-extension-handlers}}, or {{send-static-file}} if none
175was found.
176
177<parameter>(handle-not-found [proc])</parameter>
178
179The handler for nonexisting files. If the requested URL does not point
180to an existing file or directory, this procedure is called. It is a
181procedure of one argument, the path (a string) that was
182requested. This path should be interpreted as being relative to the
183webroot (even though it points to no existing file). Defaults to a
184procedure which returns a "404 Not found".
185
186<parameter>(handle-exception [proc])</parameter>
187
188The handler for when an exception occurs. This defaults to a procedure
189that logs the error to the error log. While debugging or developing, it
190may be more convenient to use a procedure that sends the error back to
191the client:
192
193<enscript highlight=scheme>
194(handle-exception
195  (lambda (exn chain)
196    (send-status 500 "Internal server error" (build-error-message exn chain))))
197</enscript>
198
199<parameter>(handle-access-logging [proc])</parameter>
200
201The handler for access logging. This is a procedure of zero arguments
202which should write a line to the access log. Defaults to a procedure which
203writes a line to {{access-log}} which looks like this:
204
205   127.0.0.1 [Sun Nov 16 15:16:01 2008] "GET http://localhost:8080/foo HTTP/1.1" Links (2.2; NetBSD 5.99.01 macppc; x)
206
207=== Runtime information
208
209During the handling of a request, Spiffy adds more information to the
210environment by parameterizing the following parameters whenever the
211information becomes available:
212
213<parameter>(current-request [request])</parameter>
214
215An intarweb request-object that defines the current request. Available
216from the moment the request comes in and is parsed. Contains, among
217other things, the query parameters and the request-headers, in fully
218parsed form (as intarweb returns them).
219
220<parameter>(current-response [response])</parameter>
221
222An intarweb response-object that defines the current
223response. Available from the same time current-request is available.
224This keeps getting updated along the way, while the response data is
225being refined (like when headers are being added).
226
227<parameter>(current-file [path])</parameter>
228
229The path to the requested file (a string). Available from the moment
230Spiffy determined the requested URL points to a file (just before the
231{{handle-file}} procedure is called). This file is relative to the
232{{root-path}}.
233
234<parameter>(current-pathinfo [path])</parameter>
235
236The trailing path ''fragments'' (a list of strings) that were passed
237in the URL after the requested filename. Available from the moment
238Spiffy determined the requested URL points to a file (just before the
239{{handle-file}} procedure is called).
240
241<parameter>(remote-address [address])</parameter>
242
243The IP address (a string) of the user-agent performing the current
244request.
245
246<parameter>(local-address [address])</parameter>
247
248The IP address (a string) on which the current request came in.
249
250=== Virtual hosts
251
252Spiffy has support for virtual hosting, using the HTTP/1.1 Host
253header.  This allows you to use one Spiffy instance running on one IP
254address/port number to serve multiple webpages, as determined by the
255hostname that was requested.
256
257The virtual host is defined by a procedure, which can set arbitrary
258parameters on-the-fly. It is passed a continuation thunk, which it
259should explicitly call if it wants the processing to continue.  The
260most used parameter in virtual host setups is the {{root-path}}
261parameter, so that another docroot can be selected based on the
262requested hostname, showing different websites for different hosts:
263
264<example>
265<expr>
266(vhost-map `(("foo\\.bar\\.com" .
267               ,(lambda (continue)
268                  (parameterize ((file-extension-handlers
269                                   `(("ssp" . ,ssp-handler) ("ws" . ,web-scheme-handler)))
270                                 (root-path "/var/www/domains/foo.bar.com"))
271                     (continue))))
272             (,(glob->regexp "*.domain.com") .
273                ,(lambda (continue)
274                   (parameterize ((file-extension-handlers
275                                    `(("php" . ,(cgi-handler* "/usr/pkg/bin/php"))))
276                                  (root-path "/var/www/domains/domain.com"))
277                     (continue))))))
278</expr>
279</example>
280
281In this example, if a client accesses
282{{foo.bar.com/mumble/blah.html}}, the file
283{{/var/www/domains/foo.bar.com/mumble/blah.html}} will be served.  Any
284files ending in {{.ssp}} or {{.ws}} will be served by the
285corresponding file type handler.  If there's any PHP file, its source
286will simply be displayed.  In case of
287{{my.domain.com/something/bar.html}}, the file
288{{/var/www/domains/domain.com/something/bar.html}} will be served.  If
289there's a {{.ssp}} or {{.ws}} file there, it will not be interpreted.
290Its source will be displayed instead.  A {{.php}} file, on the other
291hand, will be passed via CGI to the program {{/usr/pkg/bin/php}}.
292
293Domain names are mapped to a lambda that sets up any parameters it
294wants to override from the defaults.  The host names are matched using
295{{string-match}}.  If the host name is not yet a regexp, it will be
296converted to a ''case-insensitive'' regexp.
297
298=== Access files
299
300Fine-grained access-control can be implemented by using so-called
301access files.  When a request for a specific file is made and a file
302with the name given in the {{access-file}} parameter exists in any
303directory between the {{root-dir}} of that vhost and the directory in
304which the file resides, then the access file is loaded as an
305s-expression containing a function and is evaluated with a single
306argument, the function that should be called to continue processing
307the request.
308
309This works just like vhosting.  The function that gets called can call
310{{parameterize}} to set additional constraints on the code that
311handles deeper directories.
312
313For example, if we evaluate {{(access-file ".access")}} before
314starting the server, and we put the following code in a file named
315{{.access}} into the root-directory, then all accesses from localhost
316to any file in the root-directory or any subdirectory will be denied:
317
318<enscript highlight=scheme>
319 (lambda (continue)
320   (if (string=? (remote-address (current-request)) "127.0.0.1")
321       (continue)
322       (send-status 403 "Forbidden" "Sorry, you're not allowed here")))
323</enscript>
324
325If we only want to deny access to files that start with an X, put this
326in the {{.access}} file:
327
328<enscript highlight=scheme>
329 (lambda (continue)
330   (let ((old-handler (handle-file)))
331     (parameterize ((handle-file
332                      (lambda (path)
333                        (if (not (string-prefix? "X" (pathname-file path)))
334                            (send-status 403 "Forbidden" "No X-files allowed!")
335                            (old-handler path)))))
336       (continue))))
337</enscript>
338
339Of course, access files can be used for much more than just access
340checks.  One can put anything in them that could be put in vhost
341configuration or in top-level configuration.
342
343They are very useful for making deployable web applications, so you
344can just drop a directory on your server which has its own
345configuration embedded in an access file in the root directory of the
346application, without having to edit the server's main configuration
347files.
348
349=== Procedures and macros
350
351The following procedures and macros can be used in dynamic web
352programs, or dynamic server configuration:
353
354<procedure>(with-headers new-headers thunk)</procedure>
355
356Call {{thunk}} with the header list {{new-headers}}. This
357parameterizes the current response to contain the new headers.  The
358existing headers are extended with {{new-headers}} through intarweb's
359{{headers}} procedure.
360
361<procedure>(write-logged-response)</procedure>
362
363This procedure simply writes {{current-response}} after calling
364{{handle-access-logging}}. Responses should always go through this
365procedure instead of directly using {{write-response}} from intarweb.
366
367<procedure>(log-to log format . rest)</procedure>
368
369Write a printf-style format string to the specified log (one of
370{{access-log}}, {{error-log}} or {{debug-log}}). {{format}} is a
371{{printf}}-style format string, and rest arguments should match
372the arguments one would pass to printf. A newline is appended to
373the end of the log message automatically.
374
375<procedure>(send-status code reason [message])</procedure>
376
377Easy way to send a page and a status code to the client.  The optional
378message is a string containing HTML to add in the body of the
379response. Example:
380
381<example>
382<expr>
383(send-status 404 "Not found"
384 "Sorry, page not found! Please try <a href='/search.ws'>our search page</a>")
385</expr>
386</example>
387
388<procedure>(send-static-file filename)</procedure>
389
390Send a file to the client. This sets the {{content-length}} header and
391tries to send the file as quickly as possible to the client. The
392filename is interpreted relative to {{root-path}}.
393
394<procedure>(restart-request request)</procedure>
395
396Restart the entire request-handling starting at the point where the
397request was just parsed. The argument is the new request to use.
398Be careful, this makes it very easy to introduce unwanted endless loops!
399
400<procedure>(htmlize string) => string</procedure>
401
402Encode "special" html symbols like tag and attribute characters so
403they will not be interpreted by the browser.
404
405<procedure>(build-error-message exn chain [raw-output])</procedure>
406
407Build an error message for the exception {{exn}}, with call chain
408{{chain}}. Defaults to HTML output, unless {{raw-output}} is given and
409nonfalse.
410
411=== Modules
412
413This section will describe what the various modules that come with
414Spiffy are and how they work.
415
416==== ssp-handler
417
418SSP, or Scheme Server Pages, are a way to embed Scheme in HTML
419pages. Files with an extension of {{.ssp}} are handled specifically,
420by replacing occurrences of certain reserved tags with Scheme code.
421There are two possible forms, either the long version, where all
422output is redirected to the HTTP response port, or the short, where
423the result of the embedded expression is displayed in the response.
424The tags default to {{<?scheme}} and {{<?}}, see Configuration for how
425to change them.
426
427<enscript highlight=scheme>
428   <html><body>
429   <ol><?scheme (for-each (lambda (i) (printf "<li>~S~%" i)) (iota 5))?></ol>
430   <br />
431   <b><?(call-with-values (lambda () (user-information (current-user-id))) (lambda (name . _) name))?><b>
432   </body></html>
433</enscript>
434
435would generate for example something like this:
436
437     1. 0
438     2. 1
439     3. 2
440     4. 3
441     5. 4
442
443  (felix x 500 100 /home/felix /bin/bash)
444
445When a {{.ssp}} file is loaded the first time, or when it has been
446modified, then a translation takes place that generates a loadable
447Scheme source file (with the extension {{.sspx}}, in the same
448directory as the original file) from the original data, so in the
449above example something like this would be generated:
450
451<enscript highlight=scheme>
452  (let ()
453    (display "<html><body>\n<ol>")
454    (for-each (lambda (i) (printf "<li>~S~%" i)) (iota 5))
455    (display "</ol>\n<br />\n<b>")
456    (display (call-with-values (lambda () (user-information (current-user-id))) (lambda (name . _) name)))
457    (display "<b>\n</body></html>\n") )
458</enscript>
459
460Note that the body is evaluated in a {{(let () ...)}} form.
461
462Note: each request runs in a separate thread, so code in {{.ssp}}
463pages should take care when using global variables.
464
465===== Configuration
466
467The SSP handler can be configured with the following options:
468
469<parameter>(ssp-short-open-tag [tag-regexp])</parameter>
470
471The opening tag for short fragments. Default: {{<?}}
472
473<parameter>(ssp-long-open-tag [tag-regexp])</parameter>
474
475The opening tag for long fragments. Default: {{<?scheme}}
476
477<parameter>(ssp-close-tag [tag-regexp])</parameter>
478
479The closing tag for Scheme fragments in {{.ssp}} files. Default: {{?>}}
480
481<parameter>(ssp-eval-environment [environment])</parameter>
482
483The environment passed to {{eval}} when evaluating Scheme code inside
484{{.ssp}}-pages.  Default: {{interaction-environment}}
485
486===== Runtime information
487
488For the duration of evaluating a SSP page, the following parameters
489will have a value assigned to them:
490
491<parameter>(current-workdir [path])</parameter>
492
493During execution, the current working directory of the SSP handler.
494Any of the "include" procedures (ssp-include, ssp-stringize) will
495interpret their file arguments to be relative to this directory.
496
497<parameter>(ssp-exit-handler [handler])</parameter>
498
499During execution of an ssp page, {{ssp-exit-handler}} is bound to a
500procedure that will finish the current page, ignoring any further
501content or code.
502
503===== Procedures
504
505The ssp-handler module adds the following procedures to the environment:
506
507<procedure>(ssp-handler filename)</procedure>
508
509The handler itself, which should be used in the {{file-extension-handlers}}
510parameter list.
511
512<procedure>(ssp-include filename)</procedure>
513
514Translates the file {{filename}} into Scheme by replacing {{<?scheme ... ?>}}
515and {{<? ... ?>}} sequences (if needed) and writes the translated contents
516to the current output-port.
517
518<procedure>(ssp-stringize FILENAME)</procedure>
519
520Similar to {{ssp-include}}, but instead of writing the translated
521text, the text is returned as a string.
522
523==== web-scheme-handler
524
525Another way of executing Scheme code to produce content are {{.ws}}
526files: these should contain a Scheme expression that is expected to
527evaluate to a string which will be directly written as the response to
528the current request. This facility is intended for Scheme code that
529uses the [[web-scheme]] extension.
530
531You can use the {{web-scheme-handler}} for any Scheme file which
532returns HTML as a string or which has a side-effect of outputting the
533HTML. If it's the latter, make sure the final statement in your file
534does not return a string or it will be appended to the output (just
535like in the {{csi}} REPL).
536
537'''Tip''' This handler type is perfect not only for web-scheme but
538also for when you're using {{SRV:send-reply}} with SXML or for example a
539wiki-to-string translator.
540
541Note: each request runs in a separate thread, so code in {{.ws}} pages
542should take care when using global variables.
543
544Note: web-scheme-handler is a separate extension and must be imported
545as such.
546
547<enscript highlight=scheme>
548(require-extension web-scheme-handler)
549</enscript>
550
551===== Configuration
552
553The Web-scheme handler can be configured with the following options:
554
555<parameter>(web-scheme-eval-environment [environment])</parameter>
556
557The environment passed to {{eval}} when evaluating Scheme code inside
558{{.ws}}-pages.  Default: {{interaction-environment}}
559
560===== Procedures
561
562The Web-scheme handler adds only one procedure to the environment:
563
564<procedure>(web-scheme-handler filename)</procedure>
565
566The handler itself, which should be used in the {{file-extension-handlers}}
567parameter list.
568
569
570==== cgi-handler
571
572Spiffy supports [[http://www.ietf.org/rfc/rfc3875|CGI/1.1 as specified by RFC 3875]].
573
574All request headers will be passed as environment variables to the CGI
575program, prefixed with {{"HTTP_"}}, and converted to uppercase, with
576hyphens ("-") replaced by an underscore ("_"). The CGI program will
577receive the request body in unparsed form from stdin and should write
578a complete HTTP response to stdout.  Any headers that are missing but
579required for HTTP will be added by Spiffy.  For more info on how a CGI
580script is called, consult the spec.
581
582The {{AUTH_TYPE}} and {{REMOTE_USER}} environment variables are
583currently not set during invocation of CGI subprocesses.
584The {{REMOTE_IDENT}} environment variable is not and never will be supported.
585
586===== Configuration
587
588CGI handler can be configured with the following parameters:
589
590<procedure>(cgi-default-environment [env-alist])</procedure>
591
592The environment variables that should be in the default environnment
593of every CGI program.  Variables like {{SCRIPT_NAME}} will be added
594dynamically to the end of this alist.
595
596Default:
597
598<enscript highlight=scheme>
599(("GATEWAY_INTERFACE" . "CGI/1.1"))
600</enscript>
601
602===== Procedures
603
604CGI-handler adds two procedures to the environment:
605
606<procedure>(cgi-handler filename [interpreter])</procedure>
607
608The cgi handler simply calls CGI scripts. It is assumed the
609requested file is executable if no interpreter is given.
610(If used as a regular handler, it will only receive the
611filename).
612
613<procedure>(cgi-handler* [interpreter])</procedure>
614
615The {{cgi-handler*}} procedure is usually more useful.  It allows you
616to define an interpreter to use for files and returns a new handler.
617See the example above for {{file-extension-handlers}}.
618
619
620==== simple-directory-handler
621
622In order to get directory listings, you can use
623{{simple-directory-handler}}.  Just assign the
624{{simple-directory-handler}} to {{handle-directory}} and you're set.
625
626===== Configuration
627
628The simple directory handler has a few configuration options:
629
630<procedure>(simple-directory-dotfiles? [dotfiles?])</procedure>
631
632Determines if dotfiles should show up in the directory listings.
633Default: {{#f}}
634
635<procedure>(simple-directory-display-file [displayer])</procedure>
636
637A lambda that accepts three arguments: the remote filename, the local
638filename and a boolean that says if the file is a directory.  This
639lambda should output a table row with the desired information.
640Defaults to a lambda that prints the name, size and date when the file
641was last modified.
642
643===== Procedures
644
645The simple-directory handler adds only one procedure to the environment:
646
647<procedure>(simple-directory-handler pathname)</procedure>
648
649The handler itself, which should be used in the {{handle-directory}}
650parameter.
651
652
653=== Changelog
654
655* 4.0 Rewrite from scratch, using Intarweb
656* pre-4.0 See the changelog for the old [[spiffy]]
657
658=== License
659
660  Copyright (c) 2005-2008, Felix L. Winkelmann and Peter Bex
661  All rights reserved.
662 
663  Redistribution and use in source and binary forms, with or without
664  modification, are permitted provided that the following conditions are
665  met:
666 
667  Redistributions of source code must retain the above copyright
668  notice, this list of conditions and the following disclaimer.
669 
670  Redistributions in binary form must reproduce the above copyright
671  notice, this list of conditions and the following disclaimer in the
672  documentation and/or other materials provided with the distribution.
673 
674  Neither the name of the author nor the names of its contributors may
675  be used to endorse or promote products derived from this software
676  without specific prior written permission.
677 
678  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
679  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
680  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
681  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
682  COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
683  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
684  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
685  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
686  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
687  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
688  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
689  OF THE POSSIBILITY OF SUCH DAMAGE.
Note: See TracBrowser for help on using the repository browser.