source: project/wiki/eggref/4/http-client @ 33749

Last change on this file since 33749 was 33749, checked in by sjamaan, 3 years ago

Document new http-client parameter and procedure.

File size: 25.8 KB
Line 
1[[tags: egg]]
2
3== http-client
4
5[[toc:]]
6
7=== Description
8
9Http-client is a highlevel HTTP client library.
10
11=== Author
12
13[[/users/peter-bex|Peter Bex]]
14
15=== Requirements
16
17Requires the [[intarweb]], [[sendfile]] and [[md5]] extensions.
18
19The [[openssl]] extension is optional as of 0.7; if it's not installed
20you'll get an error when trying to access a HTTPS URI.
21
22=== Documentation
23
24==== Main request procedures
25
26<procedure>(call-with-response request writer reader)</procedure>
27
28This is the core http-client procedure, but it is also pretty
29low-level.  It is only necessary to use this when you want the most
30control over the request/response cycle.  Otherwise, you should use
31{{with-input-from-request}}, {{call-with-input-request}} or
32{{call-with-input-request*}}.
33
34{{request}} is the request object that contains information about the
35request to perform.  {{reader}} is a procedure that receives the
36response object and should read the ''entire'' request body (any
37leftover data will cause errors on subsequent requests with keepalive
38connections), {{writer}} is a procedure that receives the request
39object and should write the request body.
40
41The {{writer}} should be prepared to be called several times; if the
42response is a redirect or some other status that indicates the server
43wants the client to perform a new request, the writer should be ready
44to write a request body for this new request. In case digest
45authentication with message integrity checking is used, {{writer}} is
46always invoked at least twice, once to determine the message digest of
47the response and once to actually write the response.
48
49Returns three values: The result of the call to {{reader}} (or {{#f}}
50if there is no message body in the response), the request-uri of
51the last request and the response object. The request-uri is useful
52because this is to be used as the base uri of the document. This can
53differ from the initial request in the presence of redirects.
54
55If there is no response body to read (as determined by intarweb's
56{{response-has-message-body-for-request?}}), the {{reader}} procedure
57is not invoked at all.
58
59If successive requests cause more than {{max-redirect-depth}} redirect
60responses to occur, a condition of type
61{{(exn http redirect-depth-exceeded)}} is raised.
62
63If the request's URI or the URI of a used proxy is of an unsupported
64type, a condition of type {{(exn http unsupported-uri-scheme)}} is
65raised (this can of course also occur when the initial URI is correct,
66but the server redirects to an URI with an unsupported scheme).
67
68When the request requires authentication of an unsupported type, a
69condition of type {{(exn http unknown-authtype)}} is raised.
70
71<procedure>(call-with-input-request uri-or-request writer reader)</procedure>
72
73This procedure is a convenience wrapper around {{call-with-response}}.
74
75It is much less strict - {{uri-or-request}} can be an [[intarweb]]
76request object, but also an uri-common object or even a string with
77the URI in it, in which case a request object will be automatically
78constructed around the URI, using the {{GET}} method when {{writer}}
79is {{#f}} or the {{POST}} method when {{writer}} is not {{#f}}.
80
81{{writer}} can be either {{#f}} (in which case nothing is written and
82the {{GET}} method chosen), a string containing the raw data to send,
83an alist, or a procedure that accepts a port and writes the
84response data to it.  If you supply a procedure, do not forget to set
85the {{content-length}} header!  In the other cases, whenever possible,
86the length is calculated and the header automatically set for you.
87
88If you supplied an alist, the {{content-type}} header is automatically
89set to {{application/x-www-form-urlencoded}} unless there's an alist
90entry whose value is a list starting with the keyword {{file:}}, in
91which case {{multipart/form-data}} is used.  See the examples for
92{{with-input-from-request}} below.  If the data cannot be form-encoded,
93a condition of type {{(exn http formdata-error)}} is raised.
94
95{{reader}} is either {{#f}} or a procedure which accepts a port and
96reads out the data.  If there is data left in the port when the reader
97returns (or {{#f}} was supplied), this will be automatically discarded
98to avoid problems.
99
100Returns three values: The result of the call to {{reader}} (or {{#f}}
101if there is no message body in the response), the request-uri of the
102last request and the response object.  If the response code is not in
103the 200 class, it will raise a condition of type
104{{(exn http client-error)}}, {{(exn http server-error)}} or
105{{(exn http unexpected-server-response)}}, depending on the response
106code.  This includes {{404 not found}} (which is a {{client-error}}).
107
108If there is no response body to read (as determined by intarweb's
109{{response-has-message-body-for-request?}}), the {{reader}} procedure
110is not invoked at all.
111
112When posting multipart form data, the value of a file entry is a list
113of keyword-value pairs.  The following keywords are recognised:
114
115; {{file:}} : This indicates the file to read from.  Can be either a string or a port. This ''must'' be specified, everything else is optional.
116; {{filename:}} : This indicates the filename to pass on to the server.  If not specified or {{#f}}, the {{file:}}'s string (or port-name in case of a port) will be used.
117; {{headers:}} : Additional headers to send for this entry (an [[intarweb]] headers-object).
118
119<procedure>(call-with-input-request* uri-or-request writer reader)</procedure>
120
121As {{call-with-input-request}}, except {{reader}} is passed two
122arguments: the input port and the complete intarweb response object
123(useful for when you want to inspect headers or other aspects of the
124response).
125
126Please note that the port is '''not''' the same as the
127{{response-port}} from the response object: the port is delimited so
128that you can read until {{EOF}}.  The {{response-port}} is the
129original underlying, unbounded port.  If you do want to read from it,
130you must make sure to read no more than what's in the
131{{Content-Length}} header, if present.  If the header is not present,
132it will either be a chunked port (which is implicitly delimited by
133intarweb) or the port will be closed by the remote end after it is
134consumed, so you can read until EOF in that case.
135
136<procedure>(with-input-from-request uri-or-request writer-thunk reader-thunk)</procedure>
137
138Same as {{call-with-input-request}}, except when you pass a procedure
139as {{reader-thunk}} or {{writer-thunk}} it has to be a thunk (lambda
140of no arguments) instead of a procedure of one argument.  These thunks
141will be executed with the current input (or output) port to the
142request or response port, respectively.
143
144You can still pass {{#f}} for both or an alist or string for
145{{writer-thunk}}.
146
147===== Examples
148
149<enscript highlight="scheme">
150(use http-client)
151
152;; Start with a simple GET request:
153(with-input-from-request "http://wiki.call-cc.org/" #f read-string)
154 => ;; [the chicken wiki page HTML contents]
155
156;; Perform a POST of the key "test" with value "value" to an echo service:
157(with-input-from-request "http://localhost/echo-service"
158                         '((test . "value")) read-string)
159 => "You posted: test=value"
160
161;; Performing a PUT request (a less commonly used method) requires
162;; constructing your request object manually:
163
164(use intarweb uri-common)  ; Required for "make-request" and "uri-reference"
165
166(with-input-from-request
167  (make-request method: 'PUT
168                uri: (uri-reference "http://example.com/blabla"))
169  (lambda () (print "Page contents"))
170  read-string)
171
172;; Performing a JSON PUT request furthermore requires you to
173;; pass custom headers:
174(let* ((uri (uri-reference "http://www.example.com/some/document"))
175       (req (make-request method: 'PUT
176                          uri: uri
177                          headers: (headers '((content-type application/json))))))
178  (with-input-from-request req "Contents of the document" read-string))
179
180;; Finally, an example where we need to send an "attachment" (file)
181;; We post a file to the echo-service from the first example.
182;; This results in a multi-part POST request, for which we set
183;; custom headers on the file (but not the main request)
184(with-input-from-request "http://localhost/echo-service"
185                         '((test . "value")
186                           (test-file file: "/tmp/myfile" filename: "hello.txt"
187                                      headers: ((content-type text/plain))))
188                         read-string)
189 => "You posted: test=value and a file named \"hello.txt\""
190</enscript>
191
192
193==== Request handling parameters
194
195<parameter>(max-retry-attempts [number])</parameter>
196
197When a request fails because of an I/O or network problem (or simply
198because the remote end closed a persistent connection while we were
199doing something else), the library will try to establish a new
200connection and perform the request again.  This parameter controls how
201many times this is allowed to be done.  If {{#f}}, it will never give up.
202
203Defaults to 1.
204
205<parameter>(retry-request? [predicate])</parameter>
206
207This procedure is invoked when a retry should take place, to determine
208if it should take place at all.  It should be a procedure accepting a
209request object and returning {{#f}} or a true value.  If the value is
210true, the new request will be sent.  Otherwise, the error that caused
211the retry attempt will be re-raised.
212
213Defaults to {{idempotent?}}, from [[intarweb]].  This is because
214non-idempotent requests cannot be safely retried when it is unknown
215whether the previous request reached the server or not.
216
217<parameter>(max-redirect-depth [number])</parameter>
218
219The maximum number of allowed redirects, or {{#f}} if there is no
220limit.  Currently there's no automatic redirect loop detection
221algorithm implemented.  If zero, no redirects will be followed at all.
222
223Defaults to 5.
224
225When the redirect limit is reached, {{call-with-response}} raises a
226condition of type {{(exn http redirect-depth-exceeded)}}.
227
228<parameter>(client-software [software-spec])</parameter>
229
230This is the names, versions and comments of the software packages that
231the client is using, for use in the {{user-agent}} header which is
232automatically added to each request.
233
234Defaults to {{(("Chicken Scheme HTTP-client" VERSION #f))}}, where
235{{VERSION}} is the version of this egg.
236
237
238==== Connection management
239
240This egg tries to re-use connections that are marked as keep-alive, to
241avoid unnecessary overhead in establishing new connections when making
242multiple requests to the same server.  This is handled through a pool
243of idle connections from which the request procedures take the oldest
244active connection.
245
246<parameter>(max-idle-connections [count])</parameter>
247
248This controls the maximum allowed idle connections at any given time.
249When a connection would be returned to the pool, the connection will
250be discarded instead, if the maximum is exceeded.
251
252This value should always be well below the maximum number of available
253file descriptors for your operating system.
254
255Defaults to {{32}}.
256
257
258<procedure>(close-connection! uri)</procedure>
259
260Close the connection to the server associated with the URI.
261
262<procedure>(close-idle-connections!)</procedure>
263
264Close all remaining idle connections.  Note that connections that are
265currently in use will still be returned to the connection pool after
266their requests finish!
267
268<procedure>(close-all-connections!)</procedure>
269
270Deprecated alias for {{close-idle-connections!}}.
271
272==== Setting up custom server connections
273
274<procedure>(default-server-connector uri proxy)</procedure>
275
276The default value of the {{server-connector}} parameter.  This
277procedure creates a connection to the remote end for the given {{uri}}
278(an [[uri-common]] object) and returns two values: an input port and
279an output port.
280
281If {{proxy}} is not {{#f}} but an [[uri-common]] object, it will
282connect to that, instead.
283
284This connector supports plain {{http}} connections, and {{https}} if
285the {{openssl}} egg can be loaded (which it attempts to do on the
286fly).
287
288<parameter>(server-connector [connector])</parameter>
289
290This parameter holds a procedure which is invoked to establish a
291connection for an URI.
292
293The procedure should accept two uri-common objects as arguments: the
294first indicates the URI for which the connection is to be made and the
295second indicates the proxy through which the connection should be
296made, or {{#f}} if a direct connection should be made to the first
297URI's host and port.
298
299This can be used for nonstandard or complex connections, like for
300example connecting to UNIX domain sockets or for supplying SSL/TLS
301client certificates.
302
303===== SSL client certificate authentication example
304
305This is how you would make a connection to an HTTPS server while
306supplying a client certificate.  Many thanks to Ryan Senior for the
307initial code.
308
309<enscript highlight="scheme">
310(use http-client uri-common openssl)
311
312(define (make-ssl-context/client-cert ca-cert-path cert-path key-path)
313  (let ((ssl-ctx (ssl-make-client-context 'tls)))
314
315    ;; Set up so the server's certificate can and will be verified
316    (ssl-load-suggested-certificate-authorities! ssl-ctx ca-cert-path)
317    (ssl-load-verify-root-certificates! ssl-ctx ca-cert-path)
318    (ssl-set-verify! ssl-ctx #t)
319
320    ;; Now load the client certificate
321    (ssl-load-certificate-chain! ssl-ctx cert-path)
322    (ssl-load-private-key! ssl-ctx key-path)
323
324    ;; Return the object we created
325    ssl-ctx))
326
327;; This creates server connectors associated with an SSL context
328(define (make-ssl-server-connector/context ssl-ctx)
329  (lambda (uri proxy)
330    (let ((remote-end (or proxy uri)))
331      (if (eq? 'https (uri-scheme remote-end))
332          ;; Only use ssl-connect for HTTPS connections
333          (ssl-connect (uri-host remote-end)
334                       (uri-port remote-end)
335                       ssl-ctx)
336          ;; Use http-client's default otherwise
337          (default-server-connector uri proxy)))))
338
339;; Now, make a context and matching connector, and register it
340(let ((ssl-ctx (make-ssl-context/client-cert
341                 "/etc/ssl/certs/ca.crt"
342                 "/etc/ssl/certs/my-client-cert.crt"
343                 "/etc/ssl/private/my-client-cert.key")))
344  (server-connector (make-ssl-server-connector/context ssl-ctx)))
345</enscript>
346
347Now, all requests made with any of the http-client procedures would
348authenticate with a server using the configured client certificate.
349
350==== Cookie management
351
352http-client's cookie management is supposed to be as automatic and
353DWIMmy as possible.  This means it will write any cookie as instructed
354by a server and all stored cookies are automatically sent back to the
355server upon a new request.
356
357However, in some cases you may want to take control of how cookies are
358stored.
359
360The API described here should be considered unstable and it may change
361dramatically when someone comes up with a better way to handle cookies.
362
363<procedure>(get-cookies-for-uri uri)</procedure>
364
365Fetch a list of all cookies which ought to be sent to the given URI.
366Cookies are vectors of two elements: a name/value pair and an alist of
367attributes.  In other words, these are the exact same values you can
368put in a {{cookie}} header.
369
370<procedure>(store-cookie! cookie-info set-cookie)</procedure>
371
372Store a cookie in the cookiejar corresponding to the Set-Cookie header
373given by {{set-cookie}}.  This overwrites any cookie that is equal to
374this cookie, as defined by RFC 2965, section 3.3.3.  Practically, this
375means that when the cookie's name, domain and path are equal to an
376existant one, it will be overwritten by the new one.  These attributes
377are taken from the {{cookie-info}} alist and expected to be there.
378
379Generally, attributes should be taken from {{set-cookie}}, but if
380missing they ought to be taken from the request URI that responded
381with the {{set-cookie}}.
382
383<enscript highlight="scheme">
384(store-cookie! `((path . ,(make-uri path: '(/ "")))
385                 (domain . "some.host.com")
386                 (secure . #t))
387               `#(("COOKIE_NAME" . "cookie-value")
388                  ((path . ,(make-uri path: '(/ ""))))))
389</enscript>
390
391<procedure>(delete-cookie! cookie-name cookie-info)</procedure>
392
393Removes any cookie from the cookiejar that is equal to the given
394cookie (again, in the sense of RFC 2965, section 3.3.3).
395The {{cookie-name}} must match and the {{path}} and {{domain}} values for
396the {{cookie-info}} alist must match.
397
398==== Authentication support
399
400When a 401 Unauthorized response is received, in most interactive
401clients, the user is normally asked to authenticate.  To support this
402type of interaction, http-client offers the following parameter:
403
404<parameter>(determine-username/password [HANDLER])</parameter>
405
406The procedure in this parameter is called whenever the remote
407host requests authentication via a 401 Unauthorized response.
408
409The {{HANDLER}} is a procedure of two arguments; the URI for the
410resource currently being requested and the realm (a string) which
411wants credentials.  The procedure should return two string values:
412the username and the password to use for authentication.
413
414The default value is a procedure which extracts the username and
415password components from the URI.
416
417For proxy authentication support, see {{determine-proxy-username/password}}
418in the next section.
419
420<parameter>(http-authenticators [AUTHENTICATORS])</parameter>
421
422This parameter allows for pluggable authentication schemes.
423{{AUTHENTICATORS}} is an alist mapping authentication scheme name
424to a procedure of 7 arguments:
425
426{{(lambda (response response-header new-request request-header uri realm writer) ...)}}
427
428Here, {{response}} is the response object, {{response-header}} is the
429name of the response header which required authentication - a symbol
430which is either {{www-authenticate}} or {{proxy-authenticate}}.
431
432{{new-request}} is the request that will be sent next, to be populated
433with additional headers by the authenticator procedure, and
434{{request-header}} is the name of the request header which is expected
435to be provided and supplied with extra details by the authenticator -
436also a symbol, which is either {{authorization}} or
437{{proxy-authorization}}.
438
439{{uri}} is the URI which was requested when the authorization was
440demanded (in case of {{www-authenticate}}, the protected resource) and
441{{realm}} is the authentication realm (a string).
442
443Finally {{writer}} is the writer procedure passed by the user or
444fabricated by {{call-with-input-request}} based on the user's form
445arguments.  It's always a procedure accepting a request object.
446This is only needed when full-request authentication is desired, to
447obtain a request body.
448
449==== Proxy support
450
451http-client has support for sending requests through proxy servers.
452
453<parameter>(determine-proxy [HANDLER])</parameter>
454
455Whenever a request is sent, the library invokes the procedure stored
456in this parameter to determine through what proxy to send the request,
457if any.
458
459The {{HANDLER}} procedure receives one argument, the URI about to be
460requested, and returns either an URI-common absolute URI object
461representing the proxy or {{#f}} if no proxy should be used.
462
463The URI's path and query, if present, are ignored; only the scheme
464and authority (host, port, username, password) are used.
465
466The default value of this parameter is {{determine-proxy-from-environment}}.
467
468<enscript highlight="scheme">
469(determine-proxy
470 (lambda (url)
471   (uri-reference "http://127.0.0.1:8888/")))
472</enscript>
473
474
475If you just want to disable proxy support, you can do:
476
477<enscript highlight="scheme">
478(determine-proxy (constantly #f))   ; From unit data-structures
479</enscript>
480
481<procedure>(determine-proxy-from-environment URI)</procedure>
482
483This procedure implements the common behaviour of HTTP software under
484UNIX:
485
486* First it checks if the requested URI's host (or an asterisk) is listed in the {{NO_PROXY}} environment variable (if suffixed with a port number, the port is also compared).  If a match is found, no proxy is used.
487* Then it will check if the {{$(protocol)_proxy}} or the {{$(PROTOCOL)_PROXY}} variable (in that order) are set.  If so, that's used.  {{protocol}} here actually means "scheme", so the URI's scheme is used, suffixed with {{_proxy}}. This means {{http_proxy}} is used for HTTP requests and {{https_proxy}} is used for HTTPS requests, but see the next point.
488* If the scheme is {{http}} and the environment variable {{REQUEST_METHOD}} is present, {{CGI_HTTP_PROXY}} is used instead of {{HTTP_PROXY}} to prevent a "[[https://httpoxy.org|httpoxy]]" attack.  This makes the assumption that {{REQUEST_METHOD}} is set because the library is being used in a CGI script.
489* If there's still no match, it looks for {{all_proxy}} or {{ALL_PROXY}}, in that order. If one of these environment variables are set, that value is used as a fallback proxy.
490* Finally, if none of these checks resulted in a proxy URI, no proxy will be used.
491
492Some UNIX software expects plain hostnames or hostname port
493combinations separated by colons, but (currently) this library expects
494full URIs, like most modern UNIX programs.
495
496<parameter>(determine-proxy-username/password [HANDLER])</parameter>
497
498The procedure in this parameter is called whenever the proxy requests
499authentication via a 407 Proxy Authentication Required response. This
500basically works the same as authentication against an origin server.
501
502The {{HANDLER}} is a procedure of two arguments; the URI for the
503''proxy'' currently being used and the realm (a string) which wants
504credentials.  The procedure should return two string values: the
505username and the password to use for authentication.
506
507The default value is a procedure which extracts the username and
508password components from the proxy's URI.
509
510
511=== Changelog
512
513* trunk Add {{max-idle-connections}} to avoid FD exhaustion (thanks to [[/users/alaric-blagrave-snellpym|Alaric]] for pointing out this issue).
514* 0.10 Do not read {{HTTP_PROXY}} if {{REQUEST_METHOD}} is present (running in a CGI script), to prevent "[[https://httpoxy.org|httpoxy]]" attack (CVE-2016-6287).
515* 0.9 Add support for custom connector procedures.  Thanks to Ryan Senior for suggesting support for https client certificates, which this makes possible.
516* 0.8 Fix bug in multipart/form-data file uploads with non-file components in the form data causing a crash.  Thanks to Ryan Senior for reporting the bug and testing the fix.
517* 0.7.2 Add {{call-with-input-request*}}. Thanks to [[/users/mario-domenech-goulart|Mario Goulart]] for suggesting this.
518* 0.7.1 Fix delimited port handling of {{peek-char}} which caused mysterious openssl errors.  Thanks to [[/users/mario-domenech-goulart|Mario Goulart]] for a reproducible test case.
519* 0.7 Reduce CPU usage by implementing custom {{read-string!}} and {{read-line}} procedures in {{make-delimited-input-port}}. Improved error reporting (show URI as string, and always include it in error messages). Gracefully handle premature disconnection by retrying (as per RFC2616, 8.2.4).  Make openssl an optional dependency to make it easier to install on Windows.
520* 0.6.1 Work around a bug in {{read-string!}} in CHICKEN core which caused random errors.
521* 0.6 Provide a proper condition when encountering unsupported URI schemes (thanks to [[/users/christian-kellermann|Christian Kellermann]]).  Fix response body reading in error situations (thanks to [[/users/andyjpb|Andy Bennett]]).  Update request writer to use new {{finish-request-body}} from intarweb 1.0.
522* 0.5.1 Restore compatibility with message-digest and string-utils egg.
523* 0.5 Improve detection of dropped connections (prevents unneccessary "connection reset" exceptions to propagate into the program). Simplify interface by switching to {{POST}} when a {{writer}} is given to {{with-input-from-request}} and {{call-with-input-request}}.  Add support for multipart forms (file upload). Fix error in case of missing username when authorization was required (introduced by version 0.4.2). Put loop call in tail position (thanks to [[/users/felix-winkelmann|Felix]]) Automatically discard remaining data on the input port, if any, to avoid problems on subsequent requests. Add rudimentary support for parameterizable authentication schemes.
524* 0.4.2 Allow missing passwords in URIs for authentication
525* 0.4.1 Fix connection status check so when the remote end closed the connection we don't try to read from it anymore (thanks to Daishi Kato and Thomas Hintz)
526* 0.4 Fix redirection code on 303, and off-by-1 mistake in redirects count (thanks to Moritz Heidkamp). Add arguments to exn objects (thanks to Christian Kellermann). Also accept an empty alist for POSTdata. Fix URI path comparisons in cookies (thanks to Daishi Kato)
527* 0.3 Fixed handling of missing Path parameters in set-cookie headers. Reported by Hugo Arregui. Improve set-cookie handling by only passing Path and Domain when matching Set-Cookie header included those parameters.
528* 0.2 Added proxy support and many many bugfixes
529* 0.1 Initial version
530
531=== License
532
533  Copyright (c) 2008-2016, Peter Bex
534  Parts copyright (c) 2000-2004, Felix L. Winkelmann
535  All rights reserved.
536 
537  Redistribution and use in source and binary forms, with or without
538  modification, are permitted provided that the following conditions are
539  met:
540 
541  Redistributions of source code must retain the above copyright
542  notice, this list of conditions and the following disclaimer.
543 
544  Redistributions in binary form must reproduce the above copyright
545  notice, this list of conditions and the following disclaimer in the
546  documentation and/or other materials provided with the distribution.
547 
548  Neither the name of the author nor the names of its contributors may
549  be used to endorse or promote products derived from this software
550  without specific prior written permission.
551 
552  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
553  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
554  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
555  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
556  COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
557  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
558  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
559  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
560  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
561  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
562  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
563  OF THE POSSIBILITY OF SUCH DAMAGE.
Note: See TracBrowser for help on using the repository browser.