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

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

Change dependency info; intarweb and spiffy now use uri-common

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