test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/NanoHTTPD.java in calabash-android-0.4.7.pre4 vs test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/NanoHTTPD.java in calabash-android-0.4.7.pre6
- old
+ new
@@ -1,38 +1,36 @@
package sh.calaba.instrumentationbackend.actions;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URLEncoder;
import java.util.Date;
import java.util.Enumeration;
-import java.util.Vector;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.TimeZone;
+import java.util.Vector;
-import java.io.ByteArrayOutputStream;
-import java.io.FileOutputStream;
-
/**
* A simple, tiny, nicely embeddable HTTP 1.0 (partially 1.1) server in Java
*
- * <p> NanoHTTPD version 1.25,
- * Copyright © 2001,2005-2012 Jarno Elonen (elonen@iki.fi, http://iki.fi/elonen/)
+ * <p> NanoHTTPD version 1.27,
+ * Copyright © 2001,2005-2013 Jarno Elonen (elonen@iki.fi, http://iki.fi/elonen/)
* and Copyright © 2010 Konstantinos Togias (info@ktogias.gr, http://ktogias.gr)
*
* <p><b>Features + limitations: </b><ul>
*
* <li> Only one Java file </li>
@@ -215,35 +213,32 @@
// Socket & server code
// ==================================================
/**
* Starts a HTTP server to given port.<p>
+ * Throws an IOException if the socket is already in use
*/
- public NanoHTTPD( int port, File wwwroot )
+ public NanoHTTPD( int port, File wwwroot ) throws IOException
{
- try {
- myTcpPort = port;
- this.myRootDir = wwwroot;
- myServerSocket = new ServerSocket( myTcpPort );
- myThread = new Thread( new Runnable()
+ myTcpPort = port;
+ this.myRootDir = wwwroot;
+ myServerSocket = new ServerSocket( myTcpPort );
+ myThread = new Thread( new Runnable()
+ {
+ public void run()
{
- public void run()
+ try
{
- try
- {
- while( true )
- new HTTPSession( myServerSocket.accept());
- }
- catch ( IOException ioe )
- {}
+ while( true )
+ new HTTPSession( myServerSocket.accept());
}
- });
- myThread.setDaemon( true );
- myThread.start();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ catch ( IOException ioe )
+ {}
+ }
+ });
+ myThread.setDaemon( true );
+ myThread.start();
}
/**
* Stops the server.
*/
@@ -256,11 +251,52 @@
}
catch ( IOException ioe ) {}
catch ( InterruptedException e ) {}
}
+
/**
+ * Starts as a standalone file server and waits for Enter.
+ */
+ public static void main( String[] args )
+ {
+ myOut.println( "NanoHTTPD 1.27 (C) 2001,2005-2013 Jarno Elonen and (C) 2010 Konstantinos Togias\n" +
+ "(Command line options: [-p port] [-d root-dir] [--licence])\n" );
+
+ // Defaults
+ int port = 80;
+ File wwwroot = new File(".").getAbsoluteFile();
+
+ // Show licence if requested
+ for ( int i=0; i<args.length; ++i )
+ if(args[i].equalsIgnoreCase("-p"))
+ port = Integer.parseInt( args[i+1] );
+ else if(args[i].equalsIgnoreCase("-d"))
+ wwwroot = new File( args[i+1] ).getAbsoluteFile();
+ else if ( args[i].toLowerCase().endsWith( "licence" ))
+ {
+ myOut.println( LICENCE + "\n" );
+ break;
+ }
+
+ try
+ {
+ new NanoHTTPD( port, wwwroot );
+ }
+ catch( IOException ioe )
+ {
+ myErr.println( "Couldn't start server:\n" + ioe );
+ System.exit( -1 );
+ }
+
+ myOut.println( "Now serving files in port " + port + " from \"" + wwwroot + "\"" );
+ myOut.println( "Hit Enter to stop.\n" );
+
+ try { System.in.read(); } catch( Throwable t ) {}
+ }
+
+ /**
* Handles one session, i.e. parses the HTTP request
* and returns the response.
*/
private class HTTPSession implements Runnable
{
@@ -280,14 +316,26 @@
if ( is == null) return;
// Read the first 8192 bytes.
// The full header should fit in here.
// Apache's default header limit is 8KB.
- int bufsize = 8192;
+ // Do NOT assume that a single read will get the entire header at once!
+ final int bufsize = 8192;
byte[] buf = new byte[bufsize];
- int rlen = is.read(buf, 0, bufsize);
- if (rlen <= 0) return;
+ int splitbyte = 0;
+ int rlen = 0;
+ {
+ int read = is.read(buf, 0, bufsize);
+ while (read > 0)
+ {
+ rlen += read;
+ splitbyte = findHeaderEnd(buf, rlen);
+ if (splitbyte > 0)
+ break;
+ read = is.read(buf, rlen, bufsize - rlen);
+ }
+ }
// Create a BufferedReader for parsing the header.
ByteArrayInputStream hbis = new ByteArrayInputStream(buf, 0, rlen);
BufferedReader hin = new BufferedReader( new InputStreamReader( hbis ));
Properties pre = new Properties();
@@ -306,37 +354,24 @@
{
try { size = Integer.parseInt(contentLength); }
catch (NumberFormatException ex) {}
}
- // We are looking for the byte separating header from body.
- // It must be the last byte of the first two sequential new lines.
- int splitbyte = 0;
- boolean sbfound = false;
- while (splitbyte < rlen)
- {
- if (buf[splitbyte] == '\r' && buf[++splitbyte] == '\n' && buf[++splitbyte] == '\r' && buf[++splitbyte] == '\n') {
- sbfound = true;
- break;
- }
- splitbyte++;
- }
- splitbyte++;
-
// Write the part of body already read to ByteArrayOutputStream f
ByteArrayOutputStream f = new ByteArrayOutputStream();
- if (splitbyte < rlen) f.write(buf, splitbyte, rlen-splitbyte);
+ if (splitbyte < rlen)
+ f.write(buf, splitbyte, rlen-splitbyte);
// While Firefox sends on the first read all the data fitting
- // our buffer, Chrome and Opera sends only the headers even if
- // there is data for the body. So we do some magic here to find
+ // our buffer, Chrome and Opera send only the headers even if
+ // there is data for the body. We do some magic here to find
// out whether we have already consumed part of body, if we
// have reached the end of the data to be sent or we should
// expect the first byte of the body at the next read.
if (splitbyte < rlen)
- size -= rlen - splitbyte +1;
- else if (!sbfound || size == 0x7FFFFFFFFFFFFFFFl)
+ size -= rlen-splitbyte;
+ else if (splitbyte==0 || size == 0x7FFFFFFFFFFFFFFFl)
size = 0;
// Now read all the body and write it to f
buf = new byte[512];
while ( rlen >= 0 && size > 0 )
@@ -350,25 +385,24 @@
// Get the raw body as a byte []
byte [] fbuf = f.toByteArray();
// Create a BufferedReader for easily reading it as string.
ByteArrayInputStream bin = new ByteArrayInputStream(fbuf);
- BufferedReader in = new BufferedReader( new InputStreamReader(bin, "UTF-8"));
+ BufferedReader in = new BufferedReader( new InputStreamReader(bin));
// If the method is POST, there may be parameters
// in data section, too, read it:
if ( method.equalsIgnoreCase( "POST" ))
{
String contentType = "";
String contentTypeHeader = header.getProperty("content-type");
-
StringTokenizer st = null;
- if (contentTypeHeader != null) {
- st = new StringTokenizer(contentTypeHeader, ",; ");
- if (st.hasMoreTokens()) {
- contentType = st.nextToken();
- }
+ if( contentTypeHeader != null) {
+ st = new StringTokenizer( contentTypeHeader , "; " );
+ if ( st.hasMoreTokens()) {
+ contentType = st.nextToken();
+ }
}
if (contentType.equalsIgnoreCase("multipart/form-data"))
{
// Handle multipart/form-data
@@ -381,17 +415,17 @@
st.nextToken();
String boundary = st.nextToken();
decodeMultipartData(boundary, fbuf, in, parms, files);
}
- else if (contentType.toLowerCase().startsWith("application/json"))
+ else if (contentType.toLowerCase().startsWith("application/json"))
{
StringBuffer sb = new StringBuffer();
String line = null;
- while ((line = in.readLine()) != null)
+ while ((line = in.readLine()) != null)
{
- sb.append(line + "\n");
+ sb.append(line + "\n");
}
parms.put("json", sb.toString());
}
else
{
@@ -523,15 +557,15 @@
String contentDisposition = item.getProperty("content-disposition");
if (contentDisposition == null)
{
sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but no content-disposition info found. Usage: GET /example/file.html" );
}
- StringTokenizer st = new StringTokenizer( contentDisposition , "; " );
+ StringTokenizer st = new StringTokenizer( contentDisposition , ";" );
Properties disposition = new Properties();
while ( st.hasMoreTokens())
{
- String token = st.nextToken();
+ String token = st.nextToken().trim();
int p = token.indexOf( '=' );
if (p!=-1)
disposition.put( token.substring(0,p).trim().toLowerCase(), token.substring(p+1).trim());
}
String pname = disposition.getProperty("name");
@@ -574,10 +608,26 @@
sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
}
}
/**
+ * Find byte index separating header from body.
+ * It must be the last byte of the first two sequential new lines.
+ **/
+ private int findHeaderEnd(final byte[] buf, int rlen)
+ {
+ int splitbyte = 0;
+ while (splitbyte + 3 < rlen)
+ {
+ if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n')
+ return splitbyte + 4;
+ splitbyte++;
+ }
+ return 0;
+ }
+
+ /**
* Find the byte positions where multipart boundaries start.
**/
public int[] getBoundaryPositions(byte[] b, byte[] boundary)
{
int matchcount = 0;
@@ -658,29 +708,30 @@
*/
private String decodePercent( String str ) throws InterruptedException
{
try
{
- StringBuffer sb = new StringBuffer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
for( int i=0; i<str.length(); i++ )
{
char c = str.charAt( i );
switch ( c )
{
case '+':
- sb.append( ' ' );
+ baos.write( (int)' ' );
break;
case '%':
- sb.append((char)Integer.parseInt( str.substring(i+1,i+3), 16 ));
+ baos.write(Integer.parseInt( str.substring(i+1,i+3), 16 ));
i += 2;
break;
default:
- sb.append( c );
+ baos.write( (int)c );
break;
}
}
- return sb.toString();
+
+ return new String( baos.toByteArray(), "UTF-8");
}
catch( Exception e )
{
sendError( HTTP_BADREQUEST, "BAD REQUEST: Bad percent-encoding." );
return null;
@@ -706,10 +757,12 @@
String e = st.nextToken();
int sep = e.indexOf( '=' );
if ( sep >= 0 )
p.put( decodePercent( e.substring( 0, sep )).trim(),
decodePercent( e.substring( sep+1 )));
+ else
+ p.put( decodePercent( e ).trim(), "" );
}
}
/**
* Returns an error message as a HTTP response and
@@ -969,10 +1022,12 @@
{
if ( startFrom >= fileLen)
{
res = new Response( HTTP_RANGE_NOT_SATISFIABLE, MIME_PLAINTEXT, "" );
res.addHeader( "Content-Range", "bytes 0-0/" + fileLen);
+ if ( mime.startsWith( "application/" ))
+ res.addHeader( "Content-Disposition", "attachment; filename=\"" + f.getName() + "\"");
res.addHeader( "ETag", etag);
}
else
{
if ( endAt < 0 )
@@ -987,10 +1042,12 @@
fis.skip( startFrom );
res = new Response( HTTP_PARTIALCONTENT, mime, fis );
res.addHeader( "Content-Length", "" + dataLen);
res.addHeader( "Content-Range", "bytes " + startFrom + "-" + endAt + "/" + fileLen);
+ if ( mime.startsWith( "application/" ))
+ res.addHeader( "Content-Disposition", "attachment; filename=\"" + f.getName() + "\"");
res.addHeader( "ETag", etag);
}
}
else
{
@@ -998,10 +1055,12 @@
res = new Response( HTTP_NOTMODIFIED, mime, "");
else
{
res = new Response( HTTP_OK, mime, new FileInputStream( f ));
res.addHeader( "Content-Length", "" + fileLen);
+ if ( mime.startsWith( "application/" ))
+ res.addHeader( "Content-Disposition", "attachment; filename=\"" + f.getName() + "\"");
res.addHeader( "ETag", etag);
}
}
}
}
@@ -1036,11 +1095,11 @@
"mp4 video/mp4 " +
"ogv video/ogg " +
"flv video/x-flv " +
"mov video/quicktime " +
"swf application/x-shockwave-flash " +
- "js application/javascript "+
+ "js application/javascript "+
"pdf application/pdf "+
"doc application/msword "+
"ogg application/x-ogg "+
"zip application/octet-stream "+
"exe application/octet-stream "+
@@ -1067,10 +1126,10 @@
/**
* The distribution licence
*/
private static final String LICENCE =
- "Copyright (C) 2001,2005-2011 by Jarno Elonen <elonen@iki.fi>\n"+
+ "Copyright (C) 2001,2005-2013 by Jarno Elonen <elonen@iki.fi>\n"+
"and Copyright (C) 2010 by Konstantinos Togias <info@ktogias.gr>\n"+
"\n"+
"Redistribution and use in source and binary forms, with or without\n"+
"modification, are permitted provided that the following conditions\n"+
"are met:\n"+