/public/javascripts/jQuery-validationEngine-2.6.1/test/NanoHTTPD.java
Java | 759 lines | 515 code | 71 blank | 173 comment | 85 complexity | 699f7b305e671fd63a03034af8f4cd40 MD5 | raw file
Possible License(s): AGPL-1.0
1import java.io.BufferedReader;
2import java.io.ByteArrayInputStream;
3import java.io.File;
4import java.io.FileInputStream;
5import java.io.IOException;
6import java.io.InputStream;
7import java.io.InputStreamReader;
8import java.io.OutputStream;
9import java.io.PrintWriter;
10import java.net.ServerSocket;
11import java.net.Socket;
12import java.net.URLEncoder;
13import java.util.Date;
14import java.util.Enumeration;
15import java.util.Hashtable;
16import java.util.Locale;
17import java.util.Properties;
18import java.util.StringTokenizer;
19import java.util.TimeZone;
20
21/**
22 * A simple, tiny, nicely embeddable HTTP 1.0 server in Java
23 *
24 * <p> NanoHTTPD version 1.14,
25 * Copyright © 2001,2005-2010 Jarno Elonen (elonen@iki.fi, http://iki.fi/elonen/)
26 *
27 * <p><b>Features + limitations: </b><ul>
28 *
29 * <li> Only one Java file </li>
30 * <li> Java 1.1 compatible </li>
31 * <li> Released as open source, Modified BSD licence </li>
32 * <li> No fixed config files, logging, authorization etc. (Implement yourself if you need them.) </li>
33 * <li> Supports parameter parsing of GET and POST methods </li>
34 * <li> Supports both dynamic content and file serving </li>
35 * <li> Never caches anything </li>
36 * <li> Doesn't limit bandwidth, request time or simultaneous connections </li>
37 * <li> Default code serves files and shows all HTTP parameters and headers</li>
38 * <li> File server supports directory listing, index.html and index.htm </li>
39 * <li> File server does the 301 redirection trick for directories without '/'</li>
40 * <li> File server supports simple skipping for files (continue download) </li>
41 * <li> File server uses current directory as a web root </li>
42 * <li> File server serves also very long files without memory overhead </li>
43 * <li> Contains a built-in list of most common mime types </li>
44 * <li> All header names are converted lowercase so they don't vary between browsers/clients </li>
45 *
46 * </ul>
47 *
48 * <p><b>Ways to use: </b><ul>
49 *
50 * <li> Run as a standalone app, serves files from current directory and shows requests</li>
51 * <li> Subclass serve() and embed to your own program </li>
52 * <li> Call serveFile() from serve() with your own base directory </li>
53 *
54 * </ul>
55 *
56 * See the end of the source file for distribution license
57 * (Modified BSD licence)
58 */
59public class NanoHTTPD
60{
61 // ==================================================
62 // API parts
63 // ==================================================
64
65 /**
66 * Override this to customize the server.<p>
67 *
68 * (By default, this delegates to serveFile() and allows directory listing.)
69 *
70 * @parm uri Percent-decoded URI without parameters, for example "/index.cgi"
71 * @parm method "GET", "POST" etc.
72 * @parm parms Parsed, percent decoded parameters from URI and, in case of POST, data.
73 * @parm header Header entries, percent decoded
74 * @return HTTP response, see class Response for details
75 */
76 public Response serve( String uri, String method, Properties header, Properties parms )
77 {
78 System.out.println( method + " '" + uri + "' " );
79
80 Enumeration e = header.propertyNames();
81 while ( e.hasMoreElements())
82 {
83 String value = (String)e.nextElement();
84 // orefalo: way to much logging
85 // System.out.println( " HDR: '" + value + "' = '" +
86 // header.getProperty( value ) + "'" );
87 }
88 e = parms.propertyNames();
89 while ( e.hasMoreElements())
90 {
91 String value = (String)e.nextElement();
92 System.out.println( " PRM: '" + value + "' = '" +
93 parms.getProperty( value ) + "'" );
94 }
95
96 return serveFile( uri, header, new File("."), true );
97 }
98
99 /**
100 * HTTP response.
101 * Return one of these from serve().
102 */
103 public class Response
104 {
105 /**
106 * Default constructor: response = HTTP_OK, data = mime = 'null'
107 */
108 public Response()
109 {
110 this.status = HTTP_OK;
111 }
112
113 /**
114 * Basic constructor.
115 */
116 public Response( String status, String mimeType, InputStream data )
117 {
118 this.status = status;
119 this.mimeType = mimeType;
120 this.data = data;
121 }
122
123 /**
124 * Convenience method that makes an InputStream out of
125 * given text.
126 */
127 public Response( String status, String mimeType, String txt )
128 {
129 this.status = status;
130 this.mimeType = mimeType;
131 this.data = new ByteArrayInputStream( txt.getBytes());
132 }
133
134 /**
135 * Adds given line to the header.
136 */
137 public void addHeader( String name, String value )
138 {
139 header.put( name, value );
140 }
141
142 /**
143 * HTTP status code after processing, e.g. "200 OK", HTTP_OK
144 */
145 public String status;
146
147 /**
148 * MIME type of content, e.g. "text/html"
149 */
150 public String mimeType;
151
152 /**
153 * Data of the response, may be null.
154 */
155 public InputStream data;
156
157 /**
158 * Headers for the HTTP response. Use addHeader()
159 * to add lines.
160 */
161 public Properties header = new Properties();
162 }
163
164 /**
165 * Some HTTP response status codes
166 */
167 public static final String
168 HTTP_OK = "200 OK",
169 HTTP_REDIRECT = "301 Moved Permanently",
170 HTTP_FORBIDDEN = "403 Forbidden",
171 HTTP_NOTFOUND = "404 Not Found",
172 HTTP_BADREQUEST = "400 Bad Request",
173 HTTP_INTERNALERROR = "500 Internal Server Error",
174 HTTP_NOTIMPLEMENTED = "501 Not Implemented";
175
176 /**
177 * Common mime types for dynamic content
178 */
179 public static final String
180 MIME_PLAINTEXT = "text/plain",
181 MIME_HTML = "text/html",
182 MIME_DEFAULT_BINARY = "application/octet-stream";
183
184 // ==================================================
185 // Socket & server code
186 // ==================================================
187
188 /**
189 * Starts a HTTP server to given port.<p>
190 * Throws an IOException if the socket is already in use
191 */
192 public NanoHTTPD( int port ) throws IOException
193 {
194 myTcpPort = port;
195 myServerSocket = new ServerSocket( myTcpPort );
196 myThread = new Thread( new Runnable()
197 {
198 public void run()
199 {
200 try
201 {
202 while( true )
203 new HTTPSession( myServerSocket.accept());
204 }
205 catch ( IOException ioe )
206 {}
207 }
208 });
209 myThread.setDaemon( true );
210 myThread.start();
211 }
212
213 /**
214 * Stops the server.
215 */
216 public void stop()
217 {
218 try
219 {
220 myServerSocket.close();
221 myThread.join();
222 }
223 catch ( IOException ioe ) {}
224 catch ( InterruptedException e ) {}
225 }
226
227
228 /**
229 * Starts as a standalone file server and waits for Enter.
230 */
231 public static void main( String[] args )
232 {
233 System.out.println( "NanoHTTPD 1.14 (C) 2001,2005-2010 Jarno Elonen\n" +
234 "(Command line options: [port] [--licence])\n" );
235
236 // Show licence if requested
237 int lopt = -1;
238 for ( int i=0; i<args.length; ++i )
239 if ( args[i].toLowerCase().endsWith( "licence" ))
240 {
241 lopt = i;
242 System.out.println( LICENCE + "\n" );
243 }
244
245 // Change port if requested
246 int port = 80;
247 if ( args.length > 0 && lopt != 0 )
248 port = Integer.parseInt( args[0] );
249
250 if ( args.length > 1 &&
251 args[1].toLowerCase().endsWith( "licence" ))
252 System.out.println( LICENCE + "\n" );
253
254 NanoHTTPD nh = null;
255 try
256 {
257 nh = new NanoHTTPD( port );
258 }
259 catch( IOException ioe )
260 {
261 System.err.println( "Couldn't start server:\n" + ioe );
262 System.exit( -1 );
263 }
264 nh.myFileDir = new File("");
265
266 System.out.println( "Now serving files in port " + port + " from \"" +
267 new File("").getAbsolutePath() + "\"" );
268 System.out.println( "Hit Enter to stop.\n" );
269
270 try { System.in.read(); } catch( Throwable t ) {};
271 }
272
273 /**
274 * Handles one session, i.e. parses the HTTP request
275 * and returns the response.
276 */
277 private class HTTPSession implements Runnable
278 {
279 public HTTPSession( Socket s )
280 {
281 mySocket = s;
282 Thread t = new Thread( this );
283 t.setDaemon( true );
284 t.start();
285 }
286
287 public void run()
288 {
289 try
290 {
291 InputStream is = mySocket.getInputStream();
292 if ( is == null) return;
293 BufferedReader in = new BufferedReader( new InputStreamReader( is ));
294
295 // Read the request line
296 String inLine = in.readLine();
297 if (inLine == null) return;
298 StringTokenizer st = new StringTokenizer( inLine );
299 if ( !st.hasMoreTokens())
300 sendError( HTTP_BADREQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html" );
301
302 String method = st.nextToken();
303
304 if ( !st.hasMoreTokens())
305 sendError( HTTP_BADREQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html" );
306
307 String uri = st.nextToken();
308
309 // Decode parameters from the URI
310 Properties parms = new Properties();
311 int qmi = uri.indexOf( '?' );
312 if ( qmi >= 0 )
313 {
314 decodeParms( uri.substring( qmi+1 ), parms );
315 uri = decodePercent( uri.substring( 0, qmi ));
316 }
317 else uri = decodePercent(uri);
318
319
320 // If there's another token, it's protocol version,
321 // followed by HTTP headers. Ignore version but parse headers.
322 // NOTE: this now forces header names uppercase since they are
323 // case insensitive and vary by client.
324 Properties header = new Properties();
325 if ( st.hasMoreTokens())
326 {
327 String line = in.readLine();
328 while ( line.trim().length() > 0 )
329 {
330 int p = line.indexOf( ':' );
331 header.put( line.substring(0,p).trim().toLowerCase(), line.substring(p+1).trim());
332 line = in.readLine();
333 }
334 }
335
336 // If the method is POST, there may be parameters
337 // in data section, too, read it:
338 if ( method.equalsIgnoreCase( "POST" ))
339 {
340 long size = 0x7FFFFFFFFFFFFFFFl;
341 String contentLength = header.getProperty("content-length");
342 if (contentLength != null)
343 {
344 try { size = Integer.parseInt(contentLength); }
345 catch (NumberFormatException ex) {}
346 }
347 String postLine = "";
348 char buf[] = new char[512];
349 int read = in.read(buf);
350 while ( read >= 0 && size > 0 && !postLine.endsWith("\r\n") )
351 {
352 size -= read;
353 postLine += String.valueOf(buf, 0, read);
354 if ( size > 0 )
355 read = in.read(buf);
356 }
357 postLine = postLine.trim();
358 decodeParms( postLine, parms );
359 }
360
361 // Ok, now do the serve()
362 Response r = serve( uri, method, header, parms );
363 if ( r == null )
364 sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: Serve() returned a null response." );
365 else
366 sendResponse( r.status, r.mimeType, r.header, r.data );
367
368 in.close();
369 }
370 catch ( IOException ioe )
371 {
372 try
373 {
374 sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
375 }
376 catch ( Throwable t ) {}
377 }
378 catch ( InterruptedException ie )
379 {
380 // Thrown by sendError, ignore and exit the thread.
381 }
382 }
383
384 /**
385 * Decodes the percent encoding scheme. <br/>
386 * For example: "an+example%20string" -> "an example string"
387 */
388 private String decodePercent( String str ) throws InterruptedException
389 {
390 try
391 {
392 StringBuffer sb = new StringBuffer();
393 for( int i=0; i<str.length(); i++ )
394 {
395 char c = str.charAt( i );
396 switch ( c )
397 {
398 case '+':
399 sb.append( ' ' );
400 break;
401 case '%':
402 sb.append((char)Integer.parseInt( str.substring(i+1,i+3), 16 ));
403 i += 2;
404 break;
405 default:
406 sb.append( c );
407 break;
408 }
409 }
410 return new String( sb.toString().getBytes());
411 }
412 catch( Exception e )
413 {
414 sendError( HTTP_BADREQUEST, "BAD REQUEST: Bad percent-encoding." );
415 return null;
416 }
417 }
418
419 /**
420 * Decodes parameters in percent-encoded URI-format
421 * ( e.g. "name=Jack%20Daniels&pass=Single%20Malt" ) and
422 * adds them to given Properties. NOTE: this doesn't support multiple
423 * identical keys due to the simplicity of Properties -- if you need multiples,
424 * you might want to replace the Properties with a Hastable of Vectors or such.
425 */
426 private void decodeParms( String parms, Properties p )
427 throws InterruptedException
428 {
429 if ( parms == null )
430 return;
431
432 StringTokenizer st = new StringTokenizer( parms, "&" );
433 while ( st.hasMoreTokens())
434 {
435 String e = st.nextToken();
436 int sep = e.indexOf( '=' );
437 if ( sep >= 0 )
438 p.put( decodePercent( e.substring( 0, sep )).trim(),
439 decodePercent( e.substring( sep+1 )));
440 }
441 }
442
443 /**
444 * Returns an error message as a HTTP response and
445 * throws InterruptedException to stop furhter request processing.
446 */
447 private void sendError( String status, String msg ) throws InterruptedException
448 {
449 sendResponse( status, MIME_PLAINTEXT, null, new ByteArrayInputStream( msg.getBytes()));
450 throw new InterruptedException();
451 }
452
453 /**
454 * Sends given response to the socket.
455 */
456 private void sendResponse( String status, String mime, Properties header, InputStream data )
457 {
458 try
459 {
460 if ( status == null )
461 throw new Error( "sendResponse(): Status can't be null." );
462
463 OutputStream out = mySocket.getOutputStream();
464 PrintWriter pw = new PrintWriter( out );
465 pw.print("HTTP/1.0 " + status + " \r\n");
466
467 if ( mime != null )
468 pw.print("Content-Type: " + mime + "\r\n");
469
470 if ( header == null || header.getProperty( "Date" ) == null )
471 pw.print( "Date: " + gmtFrmt.format( new Date()) + "\r\n");
472
473 if ( header != null )
474 {
475 Enumeration e = header.keys();
476 while ( e.hasMoreElements())
477 {
478 String key = (String)e.nextElement();
479 String value = header.getProperty( key );
480 pw.print( key + ": " + value + "\r\n");
481 }
482 }
483
484 pw.print("\r\n");
485 pw.flush();
486
487 if ( data != null )
488 {
489 byte[] buff = new byte[2048];
490 while (true)
491 {
492 int read = data.read( buff, 0, 2048 );
493 if (read <= 0)
494 break;
495 out.write( buff, 0, read );
496 }
497 }
498 out.flush();
499 out.close();
500 if ( data != null )
501 data.close();
502 }
503 catch( IOException ioe )
504 {
505 // Couldn't write? No can do.
506 try { mySocket.close(); } catch( Throwable t ) {}
507 }
508 }
509
510 private Socket mySocket;
511 };
512
513 /**
514 * URL-encodes everything between "/"-characters.
515 * Encodes spaces as '%20' instead of '+'.
516 */
517 private String encodeUri( String uri )
518 {
519 String newUri = "";
520 StringTokenizer st = new StringTokenizer( uri, "/ ", true );
521 while ( st.hasMoreTokens())
522 {
523 String tok = st.nextToken();
524 if ( tok.equals( "/" ))
525 newUri += "/";
526 else if ( tok.equals( " " ))
527 newUri += "%20";
528 else
529 {
530 newUri += URLEncoder.encode( tok );
531 // For Java 1.4 you'll want to use this instead:
532 // try { newUri += URLEncoder.encode( tok, "UTF-8" ); } catch ( UnsupportedEncodingException uee )
533 }
534 }
535 return newUri;
536 }
537
538 private int myTcpPort;
539 private final ServerSocket myServerSocket;
540 private Thread myThread;
541
542 File myFileDir;
543
544 // ==================================================
545 // File server code
546 // ==================================================
547
548 /**
549 * Serves file from homeDir and its' subdirectories (only).
550 * Uses only URI, ignores all headers and HTTP parameters.
551 */
552 public Response serveFile( String uri, Properties header, File homeDir,
553 boolean allowDirectoryListing )
554 {
555 // Make sure we won't die of an exception later
556 if ( !homeDir.isDirectory())
557 return new Response( HTTP_INTERNALERROR, MIME_PLAINTEXT,
558 "INTERNAL ERRROR: serveFile(): given homeDir is not a directory." );
559
560 // Remove URL arguments
561 uri = uri.trim().replace( File.separatorChar, '/' );
562 if ( uri.indexOf( '?' ) >= 0 )
563 uri = uri.substring(0, uri.indexOf( '?' ));
564
565 // Prohibit getting out of current directory
566 if ( uri.startsWith( ".." ) || uri.endsWith( ".." ) || uri.indexOf( "../" ) >= 0 )
567 return new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT,
568 "FORBIDDEN: Won't serve ../ for security reasons." );
569
570 File f = new File( homeDir, uri );
571 if ( !f.exists())
572 return new Response( HTTP_NOTFOUND, MIME_PLAINTEXT,
573 "Error 404, file not found." );
574
575 // List the directory, if necessary
576 if ( f.isDirectory())
577 {
578 // Browsers get confused without '/' after the
579 // directory, send a redirect.
580 if ( !uri.endsWith( "/" ))
581 {
582 uri += "/";
583 Response r = new Response( HTTP_REDIRECT, MIME_HTML,
584 "<html><body>Redirected: <a href=\"" + uri + "\">" +
585 uri + "</a></body></html>");
586 r.addHeader( "Location", uri );
587 return r;
588 }
589
590 // First try index.html and index.htm
591 if ( new File( f, "index.html" ).exists())
592 f = new File( homeDir, uri + "/index.html" );
593 else if ( new File( f, "index.htm" ).exists())
594 f = new File( homeDir, uri + "/index.htm" );
595
596 // No index file, list the directory
597 else if ( allowDirectoryListing )
598 {
599 String[] files = f.list();
600 String msg = "<html><body><h1>Directory " + uri + "</h1><br/>";
601
602 if ( uri.length() > 1 )
603 {
604 String u = uri.substring( 0, uri.length()-1 );
605 int slash = u.lastIndexOf( '/' );
606 if ( slash >= 0 && slash < u.length())
607 msg += "<b><a href=\"" + uri.substring(0, slash+1) + "\">..</a></b><br/>";
608 }
609
610 for ( int i=0; i<files.length; ++i )
611 {
612 File curFile = new File( f, files[i] );
613 boolean dir = curFile.isDirectory();
614 if ( dir )
615 {
616 msg += "<b>";
617 files[i] += "/";
618 }
619
620 msg += "<a href=\"" + encodeUri( uri + files[i] ) + "\">" +
621 files[i] + "</a>";
622
623 // Show file size
624 if ( curFile.isFile())
625 {
626 long len = curFile.length();
627 msg += " <font size=2>(";
628 if ( len < 1024 )
629 msg += curFile.length() + " bytes";
630 else if ( len < 1024 * 1024 )
631 msg += curFile.length()/1024 + "." + (curFile.length()%1024/10%100) + " KB";
632 else
633 msg += curFile.length()/(1024*1024) + "." + curFile.length()%(1024*1024)/10%100 + " MB";
634
635 msg += ")</font>";
636 }
637 msg += "<br/>";
638 if ( dir ) msg += "</b>";
639 }
640 return new Response( HTTP_OK, MIME_HTML, msg );
641 }
642 else
643 {
644 return new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT,
645 "FORBIDDEN: No directory listing." );
646 }
647 }
648
649 try
650 {
651 // Get MIME type from file name extension, if possible
652 String mime = null;
653 int dot = f.getCanonicalPath().lastIndexOf( '.' );
654 if ( dot >= 0 )
655 mime = (String)theMimeTypes.get( f.getCanonicalPath().substring( dot + 1 ).toLowerCase());
656 if ( mime == null )
657 mime = MIME_DEFAULT_BINARY;
658
659 // Support (simple) skipping:
660 long startFrom = 0;
661 String range = header.getProperty( "range" );
662 if ( range != null )
663 {
664 if ( range.startsWith( "bytes=" ))
665 {
666 range = range.substring( "bytes=".length());
667 int minus = range.indexOf( '-' );
668 if ( minus > 0 )
669 range = range.substring( 0, minus );
670 try {
671 startFrom = Long.parseLong( range );
672 }
673 catch ( NumberFormatException nfe ) {}
674 }
675 }
676
677 FileInputStream fis = new FileInputStream( f );
678 fis.skip( startFrom );
679 Response r = new Response( HTTP_OK, mime, fis );
680 r.addHeader( "Content-length", "" + (f.length() - startFrom));
681 r.addHeader( "Content-range", "" + startFrom + "-" +
682 (f.length()-1) + "/" + f.length());
683 return r;
684 }
685 catch( IOException ioe )
686 {
687 return new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, "FORBIDDEN: Reading file failed." );
688 }
689 }
690
691 /**
692 * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE
693 */
694 private static Hashtable theMimeTypes = new Hashtable();
695 static
696 {
697 StringTokenizer st = new StringTokenizer(
698 "htm text/html "+
699 "html text/html "+
700 //orefalo: added css and js mime types
701 "css text/css "+
702 "js application/javascript "+
703 "txt text/plain "+
704 "asc text/plain "+
705 "gif image/gif "+
706 "jpg image/jpeg "+
707 "jpeg image/jpeg "+
708 "png image/png "+
709 "mp3 audio/mpeg "+
710 "m3u audio/mpeg-url " +
711 "pdf application/pdf "+
712 "doc application/msword "+
713 "ogg application/x-ogg "+
714 "zip application/octet-stream "+
715 "exe application/octet-stream "+
716 "class application/octet-stream " );
717 while ( st.hasMoreTokens())
718 theMimeTypes.put( st.nextToken(), st.nextToken());
719 }
720
721 /**
722 * GMT date formatter
723 */
724 private static java.text.SimpleDateFormat gmtFrmt;
725 static
726 {
727 gmtFrmt = new java.text.SimpleDateFormat( "E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
728 gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));
729 }
730
731 /**
732 * The distribution licence
733 */
734 private static final String LICENCE =
735 "Copyright (C) 2001,2005-2010 by Jarno Elonen <elonen@iki.fi>\n"+
736 "\n"+
737 "Redistribution and use in source and binary forms, with or without\n"+
738 "modification, are permitted provided that the following conditions\n"+
739 "are met:\n"+
740 "\n"+
741 "Redistributions of source code must retain the above copyright notice,\n"+
742 "this list of conditions and the following disclaimer. Redistributions in\n"+
743 "binary form must reproduce the above copyright notice, this list of\n"+
744 "conditions and the following disclaimer in the documentation and/or other\n"+
745 "materials provided with the distribution. The name of the author may not\n"+
746 "be used to endorse or promote products derived from this software without\n"+
747 "specific prior written permission. \n"+
748 " \n"+
749 "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"+
750 "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"+
751 "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n"+
752 "IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n"+
753 "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n"+
754 "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"+
755 "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n"+
756 "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"+
757 "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n"+
758 "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.";
759}