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

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

Document new parameter: max-connections

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