2015-10-19 17:43:46 +00:00
package me.totalfreedom.totalfreedommod.httpd ;
2013-08-27 00:39:30 +00:00
2014-11-20 22:20:31 +00:00
import java.io.BufferedReader ;
import java.io.ByteArrayInputStream ;
import java.io.Closeable ;
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.PrintWriter ;
import java.io.RandomAccessFile ;
import java.io.SequenceInputStream ;
import java.io.UnsupportedEncodingException ;
import java.net.InetSocketAddress ;
import java.net.ServerSocket ;
import java.net.Socket ;
import java.net.SocketException ;
import java.net.URLDecoder ;
2013-08-27 00:39:30 +00:00
import java.nio.ByteBuffer ;
import java.nio.channels.FileChannel ;
import java.text.SimpleDateFormat ;
2014-11-20 22:20:31 +00:00
import java.util.ArrayList ;
import java.util.Calendar ;
import java.util.Date ;
import java.util.HashMap ;
import java.util.Iterator ;
import java.util.List ;
import java.util.Locale ;
import java.util.Map ;
import java.util.StringTokenizer ;
import java.util.TimeZone ;
2015-10-19 17:43:46 +00:00
import me.totalfreedom.totalfreedommod.util.FLog ;
2013-08-27 00:39:30 +00:00
/ * *
* A simple , tiny , nicely embeddable HTTP server in Java
* < p / >
* < p / >
* NanoHTTPD
2015-11-15 23:32:04 +00:00
* < p >
* < / p > Copyright ( c ) 2012 - 2013 by Paul S . Hawke , 2001 , 2005 - 2013 by Jarno Elonen , 2010 by Konstantinos Togias < / p >
2013-08-27 00:39:30 +00:00
* < p / >
* < p / >
* < b > Features + limitations : < / b >
* < ul >
* < p / >
* < li > Only one Java file < / li >
* < li > Java 5 compatible < / li >
* < li > Released as open source , Modified BSD licence < / li >
* < li > No fixed config files , logging , authorization etc . ( Implement yourself if you need them . ) < / li >
* < li > Supports parameter parsing of GET and POST methods ( + rudimentary PUT support in 1 . 25 ) < / li >
* < li > Supports both dynamic content and file serving < / li >
* < li > Supports file upload ( since version 1 . 2 , 2010 ) < / li >
* < li > Supports partial content ( streaming ) < / li >
* < li > Supports ETags < / li >
* < li > Never caches anything < / li >
* < li > Doesn ' t limit bandwidth , request time or simultaneous connections < / li >
* < li > Default code serves files and shows all HTTP parameters and headers < / li >
* < li > File server supports directory listing , index . html and index . htm < / li >
* < li > File server supports partial content ( streaming ) < / li >
* < li > File server supports ETags < / li >
* < li > File server does the 301 redirection trick for directories without '/' < / li >
* < li > File server supports simple skipping for files ( continue download ) < / li >
* < li > File server serves also very long files without memory overhead < / li >
* < li > Contains a built - in list of most common mime types < / li >
* < li > All header names are converted lowercase so they don ' t vary between browsers / clients < / li >
* < p / >
* < / ul >
* < p / >
* < p / >
* < b > How to use : < / b >
* < ul >
* < p / >
* < li > Subclass and implement serve ( ) and embed to your own program < / li >
* < p / >
* < / ul >
* < p / >
* See the separate " LICENSE.md " file for the distribution license ( Modified BSD licence )
* /
2015-11-15 23:32:04 +00:00
public abstract class NanoHTTPD {
2013-08-27 00:39:30 +00:00
/ * *
* Common mime type for dynamic content : plain text
* /
public static final String MIME_PLAINTEXT = " text/plain " ;
/ * *
* Common mime type for dynamic content : html
* /
public static final String MIME_HTML = " text/html " ;
2013-12-18 13:12:15 +00:00
// TFM Start
/ * *
* Common mime type for dynamic content : json
* /
public static final String MIME_JSON = " application/json " ;
// TFM End
2013-08-27 00:39:30 +00:00
/ * *
2013-09-18 01:31:46 +00:00
* Pseudo - Parameter to use to store the actual query string in the parameters map for later re - processing .
2013-08-27 00:39:30 +00:00
* /
2013-09-18 01:31:46 +00:00
private static final String QUERY_STRING_PARAMETER = " NanoHttpd.QUERY_STRING " ;
2013-08-27 00:39:30 +00:00
private final String hostname ;
private final int myPort ;
private ServerSocket myServerSocket ;
private Thread myThread ;
/ * *
2013-09-18 01:31:46 +00:00
* Pluggable strategy for asynchronously executing requests .
2013-08-27 00:39:30 +00:00
* /
2013-09-18 01:31:46 +00:00
private AsyncRunner asyncRunner ;
/ * *
* Pluggable strategy for creating and cleaning up temporary files .
* /
private TempFileManagerFactory tempFileManagerFactory ;
2013-08-27 00:39:30 +00:00
/ * *
* Constructs an HTTP server on given port .
* /
2015-11-15 23:32:04 +00:00
public NanoHTTPD ( int port ) {
2013-08-27 00:39:30 +00:00
this ( null , port ) ;
}
/ * *
* Constructs an HTTP server on given hostname and port .
* /
2015-11-15 23:32:04 +00:00
public NanoHTTPD ( String hostname , int port ) {
2013-08-27 00:39:30 +00:00
this . hostname = hostname ;
this . myPort = port ;
setTempFileManagerFactory ( new DefaultTempFileManagerFactory ( ) ) ;
setAsyncRunner ( new DefaultAsyncRunner ( ) ) ;
}
2015-11-15 23:32:04 +00:00
private static final void safeClose ( ServerSocket serverSocket ) {
if ( serverSocket ! = null ) {
try {
2013-09-18 01:31:46 +00:00
serverSocket . close ( ) ;
2015-11-15 23:32:04 +00:00
} catch ( IOException e ) {
2013-09-18 01:31:46 +00:00
}
}
}
2015-11-15 23:32:04 +00:00
private static final void safeClose ( Socket socket ) {
if ( socket ! = null ) {
try {
2013-09-18 01:31:46 +00:00
socket . close ( ) ;
2015-11-15 23:32:04 +00:00
} catch ( IOException e ) {
2013-09-18 01:31:46 +00:00
}
}
}
2015-11-15 23:32:04 +00:00
private static final void safeClose ( Closeable closeable ) {
if ( closeable ! = null ) {
try {
2013-09-18 01:31:46 +00:00
closeable . close ( ) ;
2015-11-15 23:32:04 +00:00
} catch ( IOException e ) {
2013-09-18 01:31:46 +00:00
}
}
}
2013-08-27 00:39:30 +00:00
/ * *
* Start the server .
2013-09-18 01:31:46 +00:00
*
2013-08-27 00:39:30 +00:00
* @throws IOException if the socket is in use .
* /
2015-11-15 23:32:04 +00:00
public void start ( ) throws IOException {
2013-08-27 00:39:30 +00:00
myServerSocket = new ServerSocket ( ) ;
myServerSocket . bind ( ( hostname ! = null ) ? new InetSocketAddress ( hostname , myPort ) : new InetSocketAddress ( myPort ) ) ;
2015-11-15 23:32:04 +00:00
myThread = new Thread ( new Runnable ( ) {
2013-08-27 00:39:30 +00:00
@Override
2015-11-15 23:32:04 +00:00
public void run ( ) {
do {
try {
2013-08-27 00:39:30 +00:00
final Socket finalAccept = myServerSocket . accept ( ) ;
final InputStream inputStream = finalAccept . getInputStream ( ) ;
2015-11-15 23:32:04 +00:00
if ( inputStream = = null ) {
2013-08-27 00:39:30 +00:00
safeClose ( finalAccept ) ;
2015-11-15 23:32:04 +00:00
} else {
asyncRunner . exec ( new Runnable ( ) {
2013-08-27 00:39:30 +00:00
@Override
2015-11-15 23:32:04 +00:00
public void run ( ) {
2013-08-27 00:39:30 +00:00
OutputStream outputStream = null ;
2015-11-15 23:32:04 +00:00
try {
2013-08-27 00:39:30 +00:00
outputStream = finalAccept . getOutputStream ( ) ;
TempFileManager tempFileManager = tempFileManagerFactory . create ( ) ;
2013-09-03 14:28:56 +00:00
HTTPSession session = new HTTPSession ( tempFileManager , inputStream , outputStream , finalAccept ) ;
2015-11-15 23:32:04 +00:00
while ( ! finalAccept . isClosed ( ) ) {
2013-08-27 00:39:30 +00:00
session . execute ( ) ;
}
2015-11-15 23:32:04 +00:00
} catch ( Exception e ) {
2013-08-27 00:39:30 +00:00
// When the socket is closed by the client, we throw our own SocketException
// to break the "keep alive" loop above.
2015-11-15 23:32:04 +00:00
if ( ! ( e instanceof SocketException & & " NanoHttpd Shutdown " . equals ( e . getMessage ( ) ) ) ) {
2015-10-19 17:43:46 +00:00
FLog . severe ( e ) ;
2013-08-27 00:39:30 +00:00
}
2015-11-15 23:32:04 +00:00
} finally {
2013-08-27 00:39:30 +00:00
safeClose ( outputStream ) ;
safeClose ( inputStream ) ;
safeClose ( finalAccept ) ;
}
}
} ) ;
}
2015-11-15 23:32:04 +00:00
} catch ( IOException e ) {
2013-08-27 00:39:30 +00:00
}
2015-11-15 23:32:04 +00:00
} while ( ! myServerSocket . isClosed ( ) ) ;
2013-08-27 00:39:30 +00:00
}
} ) ;
myThread . setDaemon ( true ) ;
myThread . setName ( " NanoHttpd Main Listener " ) ;
myThread . start ( ) ;
}
/ * *
* Stop the server .
* /
2015-11-15 23:32:04 +00:00
public void stop ( ) {
try {
2013-08-27 00:39:30 +00:00
safeClose ( myServerSocket ) ;
myThread . join ( ) ;
2015-11-15 23:32:04 +00:00
} catch ( Exception e ) {
2015-10-19 17:43:46 +00:00
FLog . severe ( e ) ;
2013-08-27 00:39:30 +00:00
}
}
2015-11-15 23:32:04 +00:00
public final int getListeningPort ( ) {
2013-09-18 01:31:46 +00:00
return myServerSocket = = null ? - 1 : myServerSocket . getLocalPort ( ) ;
}
2015-11-15 23:32:04 +00:00
public final boolean wasStarted ( ) {
2013-09-18 01:31:46 +00:00
return myServerSocket ! = null & & myThread ! = null ;
}
2015-11-15 23:32:04 +00:00
public final boolean isAlive ( ) {
2013-09-18 01:31:46 +00:00
return wasStarted ( ) & & ! myServerSocket . isClosed ( ) & & myThread . isAlive ( ) ;
}
2013-08-27 00:39:30 +00:00
/ * *
* Override this to customize the server .
* < p / >
* < p / >
* ( By default , this delegates to serveFile ( ) and allows directory listing . )
*
2015-11-15 23:32:04 +00:00
* @param uri Percent - decoded URI without parameters , for example " /index.cgi "
* @param method " GET " , " POST " etc .
* @param parms Parsed , percent decoded parameters from URI and , in case of POST , data .
2013-08-27 00:39:30 +00:00
* @param headers Header entries , percent decoded
* @return HTTP response , see class Response for details
* /
2013-09-18 01:31:46 +00:00
@Deprecated
public Response serve ( String uri , Method method , Map < String , String > headers , Map < String , String > parms ,
2015-11-15 23:32:04 +00:00
Map < String , String > files ) {
2013-09-18 01:31:46 +00:00
return new Response ( Response . Status . NOT_FOUND , MIME_PLAINTEXT , " Not Found " ) ;
}
2013-08-27 00:39:30 +00:00
/ * *
* Override this to customize the server .
* < p / >
* < p / >
* ( By default , this delegates to serveFile ( ) and allows directory listing . )
*
* @param session The HTTP session
* @return HTTP response , see class Response for details
* /
2015-11-15 23:32:04 +00:00
public Response serve ( HTTPSession session ) {
2013-08-27 00:39:30 +00:00
Map < String , String > files = new HashMap < String , String > ( ) ;
2013-09-18 01:31:46 +00:00
Method method = session . getMethod ( ) ;
2015-11-15 23:32:04 +00:00
if ( Method . PUT . equals ( method ) | | Method . POST . equals ( method ) ) {
try {
2013-09-18 01:31:46 +00:00
session . parseBody ( files ) ;
2015-11-15 23:32:04 +00:00
} catch ( IOException ioe ) {
2013-09-18 01:31:46 +00:00
return new Response ( Response . Status . INTERNAL_ERROR , MIME_PLAINTEXT , " SERVER INTERNAL ERROR: IOException: " + ioe . getMessage ( ) ) ;
2015-11-15 23:32:04 +00:00
} catch ( ResponseException re ) {
2013-09-18 01:31:46 +00:00
return new Response ( re . getStatus ( ) , MIME_PLAINTEXT , re . getMessage ( ) ) ;
}
2013-08-27 00:39:30 +00:00
}
2013-09-18 01:31:46 +00:00
return serve ( session . getUri ( ) , method , session . getHeaders ( ) , session . getParms ( ) , files ) ;
2013-08-27 00:39:30 +00:00
}
/ * *
* Decode percent encoded < code > String < / code > values .
2013-09-18 01:31:46 +00:00
*
2013-08-27 00:39:30 +00:00
* @param str the percent encoded < code > String < / code >
* @return expanded form of the input , for example " foo%20bar " becomes " foo bar "
* /
2015-11-15 23:32:04 +00:00
protected String decodePercent ( String str ) {
2013-08-27 00:39:30 +00:00
String decoded = null ;
2015-11-15 23:32:04 +00:00
try {
2013-08-27 00:39:30 +00:00
decoded = URLDecoder . decode ( str , " UTF8 " ) ;
2015-11-15 23:32:04 +00:00
} catch ( UnsupportedEncodingException ignored ) {
2013-08-27 00:39:30 +00:00
}
return decoded ;
}
/ * *
* Decode parameters from a URL , handing the case where a single parameter name might have been
2015-11-15 23:32:04 +00:00
* supplied several times , by return lists of values . In general these lists will contain a single
2013-08-27 00:39:30 +00:00
* element .
*
* @param parms original < b > NanoHttpd < / b > parameters values , as passed to the < code > serve ( ) < / code > method .
* @return a map of < code > String < / code > ( parameter name ) to < code > List & lt ; String & gt ; < / code > ( a list of the values supplied ) .
* /
2015-11-15 23:32:04 +00:00
protected Map < String , List < String > > decodeParameters ( Map < String , String > parms ) {
2013-08-27 00:39:30 +00:00
return this . decodeParameters ( parms . get ( QUERY_STRING_PARAMETER ) ) ;
}
/ * *
* Decode parameters from a URL , handing the case where a single parameter name might have been
2015-11-15 23:32:04 +00:00
* supplied several times , by return lists of values . In general these lists will contain a single
2013-08-27 00:39:30 +00:00
* element .
*
* @param queryString a query string pulled from the URL .
* @return a map of < code > String < / code > ( parameter name ) to < code > List & lt ; String & gt ; < / code > ( a list of the values supplied ) .
* /
2015-11-15 23:32:04 +00:00
protected Map < String , List < String > > decodeParameters ( String queryString ) {
2013-08-27 00:39:30 +00:00
Map < String , List < String > > parms = new HashMap < String , List < String > > ( ) ;
2015-11-15 23:32:04 +00:00
if ( queryString ! = null ) {
2013-08-27 00:39:30 +00:00
StringTokenizer st = new StringTokenizer ( queryString , " & " ) ;
2015-11-15 23:32:04 +00:00
while ( st . hasMoreTokens ( ) ) {
2013-08-27 00:39:30 +00:00
String e = st . nextToken ( ) ;
int sep = e . indexOf ( '=' ) ;
String propertyName = ( sep > = 0 ) ? decodePercent ( e . substring ( 0 , sep ) ) . trim ( ) : decodePercent ( e ) . trim ( ) ;
2015-11-15 23:32:04 +00:00
if ( ! parms . containsKey ( propertyName ) ) {
2013-08-27 00:39:30 +00:00
parms . put ( propertyName , new ArrayList < String > ( ) ) ;
}
String propertyValue = ( sep > = 0 ) ? decodePercent ( e . substring ( sep + 1 ) ) : null ;
2015-11-15 23:32:04 +00:00
if ( propertyValue ! = null ) {
2013-08-27 00:39:30 +00:00
parms . get ( propertyName ) . add ( propertyValue ) ;
}
}
}
return parms ;
}
// ------------------------------------------------------------------------------- //
//
// Threading Strategy.
//
// ------------------------------------------------------------------------------- //
/ * *
* Pluggable strategy for asynchronously executing requests .
2013-09-18 01:31:46 +00:00
*
2013-08-27 00:39:30 +00:00
* @param asyncRunner new strategy for handling threads .
* /
2015-11-15 23:32:04 +00:00
public void setAsyncRunner ( AsyncRunner asyncRunner ) {
2013-08-27 00:39:30 +00:00
this . asyncRunner = asyncRunner ;
}
2013-09-18 01:31:46 +00:00
// ------------------------------------------------------------------------------- //
//
// Temp file handling strategy.
//
// ------------------------------------------------------------------------------- //
2013-08-27 00:39:30 +00:00
/ * *
2013-09-18 01:31:46 +00:00
* Pluggable strategy for creating and cleaning up temporary files .
*
* @param tempFileManagerFactory new strategy for handling temp files .
2013-08-27 00:39:30 +00:00
* /
2015-11-15 23:32:04 +00:00
public void setTempFileManagerFactory ( TempFileManagerFactory tempFileManagerFactory ) {
2013-09-18 01:31:46 +00:00
this . tempFileManagerFactory = tempFileManagerFactory ;
2013-08-27 00:39:30 +00:00
}
/ * *
2013-09-18 01:31:46 +00:00
* HTTP Request methods , with the ability to decode a < code > String < / code > back to its enum value .
2013-08-27 00:39:30 +00:00
* /
2015-11-15 23:32:04 +00:00
public enum Method {
2013-09-18 01:31:46 +00:00
GET , PUT , POST , DELETE , HEAD ;
2013-08-27 00:39:30 +00:00
2015-11-15 23:32:04 +00:00
static Method lookup ( String method ) {
for ( Method m : Method . values ( ) ) {
if ( m . toString ( ) . equalsIgnoreCase ( method ) ) {
2013-09-18 01:31:46 +00:00
return m ;
}
}
return null ;
2013-08-27 00:39:30 +00:00
}
}
/ * *
2013-09-18 01:31:46 +00:00
* Pluggable strategy for asynchronously executing requests .
2013-08-27 00:39:30 +00:00
* /
2015-11-15 23:32:04 +00:00
public interface AsyncRunner {
2013-09-18 01:31:46 +00:00
void exec ( Runnable code ) ;
2013-08-27 00:39:30 +00:00
}
/ * *
* Factory to create temp file managers .
* /
2015-11-15 23:32:04 +00:00
public interface TempFileManagerFactory {
2013-08-27 00:39:30 +00:00
TempFileManager create ( ) ;
}
2013-09-18 01:31:46 +00:00
// ------------------------------------------------------------------------------- //
2013-08-27 00:39:30 +00:00
/ * *
* Temp file manager .
2013-09-18 01:31:46 +00:00
* < p / >
2015-11-15 23:32:04 +00:00
* < p >
* Temp file managers are created 1 - to - 1 with incoming requests , to create and cleanup
2013-08-27 00:39:30 +00:00
* temporary files created as a result of handling the request . < / p >
* /
2015-11-15 23:32:04 +00:00
public interface TempFileManager {
2013-08-27 00:39:30 +00:00
TempFile createTempFile ( ) throws Exception ;
void clear ( ) ;
}
/ * *
* A temp file .
2013-09-18 01:31:46 +00:00
* < p / >
2015-11-15 23:32:04 +00:00
* < p >
* Temp files are responsible for managing the actual temporary storage and cleaning
2013-08-27 00:39:30 +00:00
* themselves up when no longer needed . < / p >
* /
2015-11-15 23:32:04 +00:00
public interface TempFile {
2013-08-27 00:39:30 +00:00
OutputStream open ( ) throws Exception ;
void delete ( ) throws Exception ;
String getName ( ) ;
}
/ * *
2013-09-18 01:31:46 +00:00
* Default threading strategy for NanoHttpd .
* < p / >
2015-11-15 23:32:04 +00:00
* < p >
* By default , the server spawns a new Thread for every incoming request . These are set
* to < i > daemon < / i > status , and named according to the request number . The name is
2013-09-18 01:31:46 +00:00
* useful when profiling the application . < / p >
2013-08-27 00:39:30 +00:00
* /
2015-11-15 23:32:04 +00:00
public static class DefaultAsyncRunner implements AsyncRunner {
2013-09-18 01:31:46 +00:00
private long requestCount ;
2013-08-27 00:39:30 +00:00
@Override
2015-11-15 23:32:04 +00:00
public void exec ( Runnable code ) {
2013-09-18 01:31:46 +00:00
+ + requestCount ;
Thread t = new Thread ( code ) ;
t . setDaemon ( true ) ;
t . setName ( " NanoHttpd Request Processor (# " + requestCount + " ) " ) ;
t . start ( ) ;
2013-08-27 00:39:30 +00:00
}
}
/ * *
* Default strategy for creating and cleaning up temporary files .
2013-09-18 01:31:46 +00:00
* < p / >
2015-11-15 23:32:04 +00:00
* < p >
* < / p > This class stores its files in the standard location ( that is ,
* wherever < code > java . io . tmpdir < / code > points to ) . Files are added
2013-08-27 00:39:30 +00:00
* to an internal list , and deleted when no longer needed ( that is ,
* when < code > clear ( ) < / code > is invoked at the end of processing a
* request ) . < / p >
* /
2015-11-15 23:32:04 +00:00
public static class DefaultTempFileManager implements TempFileManager {
2013-08-27 00:39:30 +00:00
private final String tmpdir ;
private final List < TempFile > tempFiles ;
2015-11-15 23:32:04 +00:00
public DefaultTempFileManager ( ) {
2013-08-27 00:39:30 +00:00
tmpdir = System . getProperty ( " java.io.tmpdir " ) ;
tempFiles = new ArrayList < TempFile > ( ) ;
}
@Override
2015-11-15 23:32:04 +00:00
public TempFile createTempFile ( ) throws Exception {
2013-08-27 00:39:30 +00:00
DefaultTempFile tempFile = new DefaultTempFile ( tmpdir ) ;
tempFiles . add ( tempFile ) ;
return tempFile ;
}
@Override
2015-11-15 23:32:04 +00:00
public void clear ( ) {
for ( TempFile file : tempFiles ) {
try {
2013-08-27 00:39:30 +00:00
file . delete ( ) ;
2015-11-15 23:32:04 +00:00
} catch ( Exception ignored ) {
2013-08-27 00:39:30 +00:00
}
}
tempFiles . clear ( ) ;
}
}
/ * *
* Default strategy for creating and cleaning up temporary files .
2013-09-18 01:31:46 +00:00
* < p / >
2015-11-15 23:32:04 +00:00
* < p >
* < / p > < / [ > By default , files are created by < code > File . createTempFile ( ) < / code > in
2013-08-27 00:39:30 +00:00
* the directory specified . < / p >
* /
2015-11-15 23:32:04 +00:00
public static class DefaultTempFile implements TempFile {
2013-08-27 00:39:30 +00:00
private File file ;
private OutputStream fstream ;
2015-11-15 23:32:04 +00:00
public DefaultTempFile ( String tempdir ) throws IOException {
2013-08-27 00:39:30 +00:00
file = File . createTempFile ( " NanoHTTPD- " , " " , new File ( tempdir ) ) ;
fstream = new FileOutputStream ( file ) ;
}
@Override
2015-11-15 23:32:04 +00:00
public OutputStream open ( ) throws Exception {
2013-08-27 00:39:30 +00:00
return fstream ;
}
@Override
2015-11-15 23:32:04 +00:00
public void delete ( ) throws Exception {
2013-08-27 00:39:30 +00:00
safeClose ( fstream ) ;
file . delete ( ) ;
}
@Override
2015-11-15 23:32:04 +00:00
public String getName ( ) {
2013-08-27 00:39:30 +00:00
return file . getAbsolutePath ( ) ;
}
}
/ * *
* HTTP response . Return one of these from serve ( ) .
* /
2015-11-15 23:32:04 +00:00
public static class Response {
2013-08-27 00:39:30 +00:00
/ * *
* HTTP status code after processing , e . g . " 200 OK " , HTTP_OK
* /
private Status status ;
/ * *
* MIME type of content , e . g . " text/html "
* /
private String mimeType ;
/ * *
* Data of the response , may be null .
* /
private InputStream data ;
/ * *
* Headers for the HTTP response . Use addHeader ( ) to add lines .
* /
private Map < String , String > header = new HashMap < String , String > ( ) ;
/ * *
* The request method that spawned this response .
* /
private Method requestMethod ;
2013-09-18 01:31:46 +00:00
/ * *
* Use chunkedTransfer
* /
private boolean chunkedTransfer ;
2013-08-27 00:39:30 +00:00
/ * *
* Default constructor : response = HTTP_OK , mime = MIME_HTML and your supplied message
* /
2015-11-15 23:32:04 +00:00
public Response ( String msg ) {
2013-08-27 00:39:30 +00:00
this ( Status . OK , MIME_HTML , msg ) ;
}
/ * *
* Basic constructor .
* /
2015-11-15 23:32:04 +00:00
public Response ( Status status , String mimeType , InputStream data ) {
2013-08-27 00:39:30 +00:00
this . status = status ;
this . mimeType = mimeType ;
this . data = data ;
}
/ * *
* Convenience method that makes an InputStream out of given text .
* /
2015-11-15 23:32:04 +00:00
public Response ( Status status , String mimeType , String txt ) {
2013-08-27 00:39:30 +00:00
this . status = status ;
this . mimeType = mimeType ;
2015-11-15 23:32:04 +00:00
try {
2013-08-27 00:39:30 +00:00
this . data = txt ! = null ? new ByteArrayInputStream ( txt . getBytes ( " UTF-8 " ) ) : null ;
2015-11-15 23:32:04 +00:00
} catch ( java . io . UnsupportedEncodingException uee ) {
2015-10-19 17:43:46 +00:00
FLog . severe ( uee ) ;
2013-08-27 00:39:30 +00:00
}
}
/ * *
* Adds given line to the header .
* /
2015-11-15 23:32:04 +00:00
public void addHeader ( String name , String value ) {
2013-08-27 00:39:30 +00:00
header . put ( name , value ) ;
}
/ * *
* Sends given response to the socket .
* /
2015-11-15 23:32:04 +00:00
private void send ( OutputStream outputStream ) {
2013-08-27 00:39:30 +00:00
String mime = mimeType ;
SimpleDateFormat gmtFrmt = new SimpleDateFormat ( " E, d MMM yyyy HH:mm:ss 'GMT' " , Locale . US ) ;
gmtFrmt . setTimeZone ( TimeZone . getTimeZone ( " GMT " ) ) ;
2015-11-15 23:32:04 +00:00
try {
if ( status = = null ) {
2013-08-27 00:39:30 +00:00
throw new Error ( " sendResponse(): Status can't be null. " ) ;
}
PrintWriter pw = new PrintWriter ( outputStream ) ;
pw . print ( " HTTP/1.1 " + status . getDescription ( ) + " \ r \ n " ) ;
2015-11-15 23:32:04 +00:00
if ( mime ! = null ) {
2013-08-27 00:39:30 +00:00
pw . print ( " Content-Type: " + mime + " \ r \ n " ) ;
}
2015-11-15 23:32:04 +00:00
if ( header = = null | | header . get ( " Date " ) = = null ) {
2013-08-27 00:39:30 +00:00
pw . print ( " Date: " + gmtFrmt . format ( new Date ( ) ) + " \ r \ n " ) ;
}
2015-11-15 23:32:04 +00:00
if ( header ! = null ) {
for ( String key : header . keySet ( ) ) {
2013-08-27 00:39:30 +00:00
String value = header . get ( key ) ;
pw . print ( key + " : " + value + " \ r \ n " ) ;
}
}
2013-09-18 01:31:46 +00:00
pw . print ( " Connection: keep-alive \ r \ n " ) ;
2015-11-15 23:32:04 +00:00
if ( requestMethod ! = Method . HEAD & & chunkedTransfer ) {
2013-09-18 01:31:46 +00:00
sendAsChunked ( outputStream , pw ) ;
2015-11-15 23:32:04 +00:00
} else {
2013-09-18 01:31:46 +00:00
sendAsFixedLength ( outputStream , pw ) ;
2013-08-27 00:39:30 +00:00
}
outputStream . flush ( ) ;
safeClose ( data ) ;
2015-11-15 23:32:04 +00:00
} catch ( IOException ioe ) {
2013-08-27 00:39:30 +00:00
// Couldn't write? No can do.
}
}
2015-11-15 23:32:04 +00:00
private void sendAsChunked ( OutputStream outputStream , PrintWriter pw ) throws IOException {
2013-09-18 01:31:46 +00:00
pw . print ( " Transfer-Encoding: chunked \ r \ n " ) ;
pw . print ( " \ r \ n " ) ;
pw . flush ( ) ;
int BUFFER_SIZE = 16 * 1024 ;
byte [ ] CRLF = " \ r \ n " . getBytes ( ) ;
byte [ ] buff = new byte [ BUFFER_SIZE ] ;
int read ;
2015-11-15 23:32:04 +00:00
while ( ( read = data . read ( buff ) ) > 0 ) {
2013-09-18 01:31:46 +00:00
outputStream . write ( String . format ( " %x \ r \ n " , read ) . getBytes ( ) ) ;
outputStream . write ( buff , 0 , read ) ;
outputStream . write ( CRLF ) ;
}
outputStream . write ( String . format ( " 0 \ r \ n \ r \ n " ) . getBytes ( ) ) ;
}
2015-11-15 23:32:04 +00:00
private void sendAsFixedLength ( OutputStream outputStream , PrintWriter pw ) throws IOException {
2013-09-18 01:31:46 +00:00
int pending = data ! = null ? data . available ( ) : 0 ; // This is to support partial sends, see serveFile()
pw . print ( " Content-Length: " + pending + " \ r \ n " ) ;
pw . print ( " \ r \ n " ) ;
pw . flush ( ) ;
2015-11-15 23:32:04 +00:00
if ( requestMethod ! = Method . HEAD & & data ! = null ) {
2013-09-18 01:31:46 +00:00
int BUFFER_SIZE = 16 * 1024 ;
byte [ ] buff = new byte [ BUFFER_SIZE ] ;
2015-11-15 23:32:04 +00:00
while ( pending > 0 ) {
2013-09-18 01:31:46 +00:00
int read = data . read ( buff , 0 , ( ( pending > BUFFER_SIZE ) ? BUFFER_SIZE : pending ) ) ;
2015-11-15 23:32:04 +00:00
if ( read < = 0 ) {
2013-09-18 01:31:46 +00:00
break ;
}
outputStream . write ( buff , 0 , read ) ;
pending - = read ;
}
}
}
2015-11-15 23:32:04 +00:00
public Status getStatus ( ) {
2013-08-27 00:39:30 +00:00
return status ;
}
2015-11-15 23:32:04 +00:00
public void setStatus ( Status status ) {
2013-08-27 00:39:30 +00:00
this . status = status ;
}
2015-11-15 23:32:04 +00:00
public String getMimeType ( ) {
2013-08-27 00:39:30 +00:00
return mimeType ;
}
2015-11-15 23:32:04 +00:00
public void setMimeType ( String mimeType ) {
2013-08-27 00:39:30 +00:00
this . mimeType = mimeType ;
}
2015-11-15 23:32:04 +00:00
public InputStream getData ( ) {
2013-08-27 00:39:30 +00:00
return data ;
}
2015-11-15 23:32:04 +00:00
public void setData ( InputStream data ) {
2013-08-27 00:39:30 +00:00
this . data = data ;
}
2015-11-15 23:32:04 +00:00
public Method getRequestMethod ( ) {
2013-08-27 00:39:30 +00:00
return requestMethod ;
}
2015-11-15 23:32:04 +00:00
public void setRequestMethod ( Method requestMethod ) {
2013-08-27 00:39:30 +00:00
this . requestMethod = requestMethod ;
}
2015-11-15 23:32:04 +00:00
public void setChunkedTransfer ( boolean chunkedTransfer ) {
2013-09-18 01:31:46 +00:00
this . chunkedTransfer = chunkedTransfer ;
}
2013-08-27 00:39:30 +00:00
/ * *
* Some HTTP response status codes
* /
2015-11-15 23:32:04 +00:00
public enum Status {
2013-08-27 00:39:30 +00:00
OK ( 200 , " OK " ) , CREATED ( 201 , " Created " ) , ACCEPTED ( 202 , " Accepted " ) , NO_CONTENT ( 204 , " No Content " ) , PARTIAL_CONTENT ( 206 , " Partial Content " ) , REDIRECT ( 301 ,
2014-11-20 22:20:31 +00:00
" Moved Permanently " ) , NOT_MODIFIED ( 304 , " Not Modified " ) , BAD_REQUEST ( 400 , " Bad Request " ) , UNAUTHORIZED ( 401 ,
" Unauthorized " ) , FORBIDDEN ( 403 , " Forbidden " ) , NOT_FOUND ( 404 , " Not Found " ) , RANGE_NOT_SATISFIABLE ( 416 ,
" Requested Range Not Satisfiable " ) , INTERNAL_ERROR ( 500 , " Internal Server Error " ) ;
2013-08-27 00:39:30 +00:00
private final int requestStatus ;
private final String description ;
2015-11-15 23:32:04 +00:00
Status ( int requestStatus , String description ) {
2013-08-27 00:39:30 +00:00
this . requestStatus = requestStatus ;
this . description = description ;
}
2015-11-15 23:32:04 +00:00
public int getRequestStatus ( ) {
2013-08-27 00:39:30 +00:00
return this . requestStatus ;
}
2015-11-15 23:32:04 +00:00
public String getDescription ( ) {
2013-08-27 00:39:30 +00:00
return " " + this . requestStatus + " " + description ;
}
}
2015-11-15 23:32:04 +00:00
2013-08-27 00:39:30 +00:00
}
2015-11-15 23:32:04 +00:00
public static final class ResponseException extends Exception {
2013-09-18 01:31:46 +00:00
private final Response . Status status ;
2015-11-15 23:32:04 +00:00
public ResponseException ( Response . Status status , String message ) {
2013-09-18 01:31:46 +00:00
super ( message ) ;
this . status = status ;
}
2015-11-15 23:32:04 +00:00
public ResponseException ( Response . Status status , String message , Exception e ) {
2013-09-18 01:31:46 +00:00
super ( message , e ) ;
this . status = status ;
}
2015-11-15 23:32:04 +00:00
public Response . Status getStatus ( ) {
2013-09-18 01:31:46 +00:00
return status ;
}
}
/ * *
* Default strategy for creating and cleaning up temporary files .
* /
2015-11-15 23:32:04 +00:00
private class DefaultTempFileManagerFactory implements TempFileManagerFactory {
2013-09-18 01:31:46 +00:00
@Override
2015-11-15 23:32:04 +00:00
public TempFileManager create ( ) {
2013-09-18 01:31:46 +00:00
return new DefaultTempFileManager ( ) ;
}
}
2013-08-27 00:39:30 +00:00
/ * *
* Handles one session , i . e . parses the HTTP request and returns the response .
* /
2015-11-15 23:32:04 +00:00
protected class HTTPSession {
2013-08-27 00:39:30 +00:00
public static final int BUFSIZE = 8192 ;
private final TempFileManager tempFileManager ;
private final OutputStream outputStream ;
2013-09-18 01:31:46 +00:00
private final Socket socket ;
private InputStream inputStream ;
2013-08-27 00:39:30 +00:00
private int splitbyte ;
private int rlen ;
private String uri ;
private Method method ;
private Map < String , String > parms ;
private Map < String , String > headers ;
2013-09-18 01:31:46 +00:00
private CookieHandler cookies ;
2013-08-27 00:39:30 +00:00
2015-11-15 23:32:04 +00:00
public HTTPSession ( TempFileManager tempFileManager , InputStream inputStream , OutputStream outputStream , Socket socket ) {
2013-08-27 00:39:30 +00:00
this . tempFileManager = tempFileManager ;
this . inputStream = inputStream ;
this . outputStream = outputStream ;
2013-09-03 14:28:56 +00:00
this . socket = socket ;
2013-08-27 00:39:30 +00:00
}
2015-11-15 23:32:04 +00:00
public void execute ( ) throws IOException {
try {
2013-08-27 00:39:30 +00:00
// Read the first 8192 bytes.
// The full header should fit in here.
// Apache's default header limit is 8KB.
// Do NOT assume that a single read will get the entire header at once!
byte [ ] buf = new byte [ BUFSIZE ] ;
splitbyte = 0 ;
rlen = 0 ;
{
int read = inputStream . read ( buf , 0 , BUFSIZE ) ;
2015-11-15 23:32:04 +00:00
if ( read = = - 1 ) {
2013-08-27 00:39:30 +00:00
// socket was been closed
throw new SocketException ( " NanoHttpd Shutdown " ) ;
}
2015-11-15 23:32:04 +00:00
while ( read > 0 ) {
2013-08-27 00:39:30 +00:00
rlen + = read ;
splitbyte = findHeaderEnd ( buf , rlen ) ;
2015-11-15 23:32:04 +00:00
if ( splitbyte > 0 ) {
2013-08-27 00:39:30 +00:00
break ;
}
read = inputStream . read ( buf , rlen , BUFSIZE - rlen ) ;
}
}
2015-11-15 23:32:04 +00:00
if ( splitbyte < rlen ) {
2013-08-27 00:39:30 +00:00
ByteArrayInputStream splitInputStream = new ByteArrayInputStream ( buf , splitbyte , rlen - splitbyte ) ;
SequenceInputStream sequenceInputStream = new SequenceInputStream ( splitInputStream , inputStream ) ;
inputStream = sequenceInputStream ;
}
parms = new HashMap < String , String > ( ) ;
headers = new HashMap < String , String > ( ) ;
// Create a BufferedReader for parsing the header.
BufferedReader hin = new BufferedReader ( new InputStreamReader ( new ByteArrayInputStream ( buf , 0 , rlen ) ) ) ;
// Decode the header into parms and header java properties
Map < String , String > pre = new HashMap < String , String > ( ) ;
decodeHeader ( hin , pre , parms , headers ) ;
method = Method . lookup ( pre . get ( " method " ) ) ;
2015-11-15 23:32:04 +00:00
if ( method = = null ) {
2013-08-27 00:39:30 +00:00
throw new ResponseException ( Response . Status . BAD_REQUEST , " BAD REQUEST: Syntax error. " ) ;
}
uri = pre . get ( " uri " ) ;
2013-09-18 01:31:46 +00:00
cookies = new CookieHandler ( headers ) ;
2013-08-27 00:39:30 +00:00
// Ok, now do the serve()
Response r = serve ( this ) ;
2015-11-15 23:32:04 +00:00
if ( r = = null ) {
2013-08-27 00:39:30 +00:00
throw new ResponseException ( Response . Status . INTERNAL_ERROR , " SERVER INTERNAL ERROR: Serve() returned a null response. " ) ;
2015-11-15 23:32:04 +00:00
} else {
2013-09-18 01:31:46 +00:00
cookies . unloadQueue ( r ) ;
2013-08-27 00:39:30 +00:00
r . setRequestMethod ( method ) ;
r . send ( outputStream ) ;
}
2015-11-15 23:32:04 +00:00
} catch ( SocketException e ) {
2013-08-27 00:39:30 +00:00
// throw it out to close socket object (finalAccept)
throw e ;
2015-11-15 23:32:04 +00:00
} catch ( IOException ioe ) {
2013-08-27 00:39:30 +00:00
Response r = new Response ( Response . Status . INTERNAL_ERROR , MIME_PLAINTEXT , " SERVER INTERNAL ERROR: IOException: " + ioe . getMessage ( ) ) ;
r . send ( outputStream ) ;
safeClose ( outputStream ) ;
2015-11-15 23:32:04 +00:00
} catch ( ResponseException re ) {
2013-08-27 00:39:30 +00:00
Response r = new Response ( re . getStatus ( ) , MIME_PLAINTEXT , re . getMessage ( ) ) ;
r . send ( outputStream ) ;
safeClose ( outputStream ) ;
2015-11-15 23:32:04 +00:00
} finally {
2013-08-27 00:39:30 +00:00
tempFileManager . clear ( ) ;
}
}
2015-11-15 23:32:04 +00:00
protected void parseBody ( Map < String , String > files ) throws IOException , ResponseException {
2013-08-27 00:39:30 +00:00
RandomAccessFile randomAccessFile = null ;
BufferedReader in = null ;
2015-11-15 23:32:04 +00:00
try {
2013-08-27 00:39:30 +00:00
randomAccessFile = getTmpBucket ( ) ;
long size ;
2015-11-15 23:32:04 +00:00
if ( headers . containsKey ( " content-length " ) ) {
2013-08-27 00:39:30 +00:00
size = Integer . parseInt ( headers . get ( " content-length " ) ) ;
2015-11-15 23:32:04 +00:00
} else if ( splitbyte < rlen ) {
2013-08-27 00:39:30 +00:00
size = rlen - splitbyte ;
2015-11-15 23:32:04 +00:00
} else {
2013-08-27 00:39:30 +00:00
size = 0 ;
}
// Now read all the body and write it to f
byte [ ] buf = new byte [ 512 ] ;
2015-11-15 23:32:04 +00:00
while ( rlen > = 0 & & size > 0 ) {
2013-08-27 00:39:30 +00:00
rlen = inputStream . read ( buf , 0 , 512 ) ;
size - = rlen ;
2015-11-15 23:32:04 +00:00
if ( rlen > 0 ) {
2013-08-27 00:39:30 +00:00
randomAccessFile . write ( buf , 0 , rlen ) ;
}
}
// Get the raw body as a byte []
ByteBuffer fbuf = randomAccessFile . getChannel ( ) . map ( FileChannel . MapMode . READ_ONLY , 0 , randomAccessFile . length ( ) ) ;
randomAccessFile . seek ( 0 ) ;
// Create a BufferedReader for easily reading it as string.
InputStream bin = new FileInputStream ( randomAccessFile . getFD ( ) ) ;
in = new BufferedReader ( new InputStreamReader ( bin ) ) ;
// If the method is POST, there may be parameters
// in data section, too, read it:
2015-11-15 23:32:04 +00:00
if ( Method . POST . equals ( method ) ) {
2013-08-27 00:39:30 +00:00
String contentType = " " ;
String contentTypeHeader = headers . get ( " content-type " ) ;
StringTokenizer st = null ;
2015-11-15 23:32:04 +00:00
if ( contentTypeHeader ! = null ) {
2013-08-27 00:39:30 +00:00
st = new StringTokenizer ( contentTypeHeader , " ,; " ) ;
2015-11-15 23:32:04 +00:00
if ( st . hasMoreTokens ( ) ) {
2013-08-27 00:39:30 +00:00
contentType = st . nextToken ( ) ;
}
}
2015-11-15 23:32:04 +00:00
if ( " multipart/form-data " . equalsIgnoreCase ( contentType ) ) {
2013-08-27 00:39:30 +00:00
// Handle multipart/form-data
2015-11-15 23:32:04 +00:00
if ( ! st . hasMoreTokens ( ) ) {
2013-08-27 00:39:30 +00:00
throw new ResponseException ( Response . Status . BAD_REQUEST , " BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html " ) ;
}
String boundaryStartString = " boundary= " ;
int boundaryContentStart = contentTypeHeader . indexOf ( boundaryStartString ) + boundaryStartString . length ( ) ;
String boundary = contentTypeHeader . substring ( boundaryContentStart , contentTypeHeader . length ( ) ) ;
2015-11-15 23:32:04 +00:00
if ( boundary . startsWith ( " \" " ) & & boundary . endsWith ( " \" " ) ) {
2013-08-27 00:39:30 +00:00
boundary = boundary . substring ( 1 , boundary . length ( ) - 1 ) ;
}
decodeMultipartData ( boundary , fbuf , in , parms , files ) ;
2015-11-15 23:32:04 +00:00
} else {
2013-08-27 00:39:30 +00:00
// Handle application/x-www-form-urlencoded
String postLine = " " ;
char pbuf [ ] = new char [ 512 ] ;
int read = in . read ( pbuf ) ;
2015-11-15 23:32:04 +00:00
while ( read > = 0 & & ! postLine . endsWith ( " \ r \ n " ) ) {
2013-08-27 00:39:30 +00:00
postLine + = String . valueOf ( pbuf , 0 , read ) ;
read = in . read ( pbuf ) ;
}
postLine = postLine . trim ( ) ;
decodeParms ( postLine , parms ) ;
}
2015-11-15 23:32:04 +00:00
} else if ( Method . PUT . equals ( method ) ) {
2013-08-27 00:39:30 +00:00
files . put ( " content " , saveTmpFile ( fbuf , 0 , fbuf . limit ( ) ) ) ;
}
2015-11-15 23:32:04 +00:00
} finally {
2013-08-27 00:39:30 +00:00
safeClose ( randomAccessFile ) ;
safeClose ( in ) ;
}
}
/ * *
* Decodes the sent headers and loads the data into Key / value pairs
* /
private void decodeHeader ( BufferedReader in , Map < String , String > pre , Map < String , String > parms , Map < String , String > headers )
2015-11-15 23:32:04 +00:00
throws ResponseException {
try {
2013-08-27 00:39:30 +00:00
// Read the request line
String inLine = in . readLine ( ) ;
2015-11-15 23:32:04 +00:00
if ( inLine = = null ) {
2013-08-27 00:39:30 +00:00
return ;
}
StringTokenizer st = new StringTokenizer ( inLine ) ;
2015-11-15 23:32:04 +00:00
if ( ! st . hasMoreTokens ( ) ) {
2013-08-27 00:39:30 +00:00
throw new ResponseException ( Response . Status . BAD_REQUEST , " BAD REQUEST: Syntax error. Usage: GET /example/file.html " ) ;
}
pre . put ( " method " , st . nextToken ( ) ) ;
2015-11-15 23:32:04 +00:00
if ( ! st . hasMoreTokens ( ) ) {
2013-08-27 00:39:30 +00:00
throw new ResponseException ( Response . Status . BAD_REQUEST , " BAD REQUEST: Missing URI. Usage: GET /example/file.html " ) ;
}
String uri = st . nextToken ( ) ;
// Decode parameters from the URI
int qmi = uri . indexOf ( '?' ) ;
2015-11-15 23:32:04 +00:00
if ( qmi > = 0 ) {
2013-08-27 00:39:30 +00:00
decodeParms ( uri . substring ( qmi + 1 ) , parms ) ;
uri = decodePercent ( uri . substring ( 0 , qmi ) ) ;
2015-11-15 23:32:04 +00:00
} else {
2013-08-27 00:39:30 +00:00
uri = decodePercent ( uri ) ;
}
// If there's another token, it's protocol version,
// followed by HTTP headers. Ignore version but parse headers.
// NOTE: this now forces header names lowercase since they are
// case insensitive and vary by client.
2015-11-15 23:32:04 +00:00
if ( st . hasMoreTokens ( ) ) {
2013-08-27 00:39:30 +00:00
String line = in . readLine ( ) ;
2015-11-15 23:32:04 +00:00
while ( line ! = null & & line . trim ( ) . length ( ) > 0 ) {
2013-08-27 00:39:30 +00:00
int p = line . indexOf ( ':' ) ;
2015-11-15 23:32:04 +00:00
if ( p > = 0 ) {
2013-08-27 00:39:30 +00:00
headers . put ( line . substring ( 0 , p ) . trim ( ) . toLowerCase ( ) , line . substring ( p + 1 ) . trim ( ) ) ;
}
line = in . readLine ( ) ;
}
}
pre . put ( " uri " , uri ) ;
2015-11-15 23:32:04 +00:00
} catch ( IOException ioe ) {
2013-08-27 00:39:30 +00:00
throw new ResponseException ( Response . Status . INTERNAL_ERROR , " SERVER INTERNAL ERROR: IOException: " + ioe . getMessage ( ) , ioe ) ;
}
}
/ * *
* Decodes the Multipart Body data and put it into Key / Value pairs .
* /
private void decodeMultipartData ( String boundary , ByteBuffer fbuf , BufferedReader in , Map < String , String > parms ,
2015-11-15 23:32:04 +00:00
Map < String , String > files ) throws ResponseException {
try {
2013-08-27 00:39:30 +00:00
int [ ] bpositions = getBoundaryPositions ( fbuf , boundary . getBytes ( ) ) ;
int boundarycount = 1 ;
String mpline = in . readLine ( ) ;
2015-11-15 23:32:04 +00:00
while ( mpline ! = null ) {
if ( ! mpline . contains ( boundary ) ) {
2013-08-27 00:39:30 +00:00
throw new ResponseException ( Response . Status . BAD_REQUEST , " BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html " ) ;
}
boundarycount + + ;
Map < String , String > item = new HashMap < String , String > ( ) ;
mpline = in . readLine ( ) ;
2015-11-15 23:32:04 +00:00
while ( mpline ! = null & & mpline . trim ( ) . length ( ) > 0 ) {
2013-08-27 00:39:30 +00:00
int p = mpline . indexOf ( ':' ) ;
2015-11-15 23:32:04 +00:00
if ( p ! = - 1 ) {
2013-08-27 00:39:30 +00:00
item . put ( mpline . substring ( 0 , p ) . trim ( ) . toLowerCase ( ) , mpline . substring ( p + 1 ) . trim ( ) ) ;
}
mpline = in . readLine ( ) ;
}
2015-11-15 23:32:04 +00:00
if ( mpline ! = null ) {
2013-08-27 00:39:30 +00:00
String contentDisposition = item . get ( " content-disposition " ) ;
2015-11-15 23:32:04 +00:00
if ( contentDisposition = = null ) {
2013-08-27 00:39:30 +00:00
throw new ResponseException ( Response . Status . BAD_REQUEST , " BAD REQUEST: Content type is multipart/form-data but no content-disposition info found. Usage: GET /example/file.html " ) ;
}
StringTokenizer st = new StringTokenizer ( contentDisposition , " ; " ) ;
Map < String , String > disposition = new HashMap < String , String > ( ) ;
2015-11-15 23:32:04 +00:00
while ( st . hasMoreTokens ( ) ) {
2013-08-27 00:39:30 +00:00
String token = st . nextToken ( ) ;
int p = token . indexOf ( '=' ) ;
2015-11-15 23:32:04 +00:00
if ( p ! = - 1 ) {
2013-08-27 00:39:30 +00:00
disposition . put ( token . substring ( 0 , p ) . trim ( ) . toLowerCase ( ) , token . substring ( p + 1 ) . trim ( ) ) ;
}
}
String pname = disposition . get ( " name " ) ;
pname = pname . substring ( 1 , pname . length ( ) - 1 ) ;
String value = " " ;
2015-11-15 23:32:04 +00:00
if ( item . get ( " content-type " ) = = null ) {
while ( mpline ! = null & & ! mpline . contains ( boundary ) ) {
2013-08-27 00:39:30 +00:00
mpline = in . readLine ( ) ;
2015-11-15 23:32:04 +00:00
if ( mpline ! = null ) {
2013-08-27 00:39:30 +00:00
int d = mpline . indexOf ( boundary ) ;
2015-11-15 23:32:04 +00:00
if ( d = = - 1 ) {
2013-08-27 00:39:30 +00:00
value + = mpline ;
2015-11-15 23:32:04 +00:00
} else {
2013-08-27 00:39:30 +00:00
value + = mpline . substring ( 0 , d - 2 ) ;
}
}
}
2015-11-15 23:32:04 +00:00
} else {
if ( boundarycount > bpositions . length ) {
2013-08-27 00:39:30 +00:00
throw new ResponseException ( Response . Status . INTERNAL_ERROR , " Error processing request " ) ;
}
int offset = stripMultipartHeaders ( fbuf , bpositions [ boundarycount - 2 ] ) ;
String path = saveTmpFile ( fbuf , offset , bpositions [ boundarycount - 1 ] - offset - 4 ) ;
files . put ( pname , path ) ;
value = disposition . get ( " filename " ) ;
value = value . substring ( 1 , value . length ( ) - 1 ) ;
2015-11-15 23:32:04 +00:00
do {
2013-08-27 00:39:30 +00:00
mpline = in . readLine ( ) ;
2015-11-15 23:32:04 +00:00
} while ( mpline ! = null & & ! mpline . contains ( boundary ) ) ;
2013-08-27 00:39:30 +00:00
}
parms . put ( pname , value ) ;
}
}
2015-11-15 23:32:04 +00:00
} catch ( IOException ioe ) {
2013-08-27 00:39:30 +00:00
throw new ResponseException ( Response . Status . INTERNAL_ERROR , " SERVER INTERNAL ERROR: IOException: " + ioe . getMessage ( ) , ioe ) ;
}
}
/ * *
* Find byte index separating header from body . It must be the last byte of the first two sequential new lines .
* /
2015-11-15 23:32:04 +00:00
private int findHeaderEnd ( final byte [ ] buf , int rlen ) {
2013-08-27 00:39:30 +00:00
int splitbyte = 0 ;
2015-11-15 23:32:04 +00:00
while ( splitbyte + 3 < rlen ) {
if ( buf [ splitbyte ] = = '\r' & & buf [ splitbyte + 1 ] = = '\n' & & buf [ splitbyte + 2 ] = = '\r' & & buf [ splitbyte + 3 ] = = '\n' ) {
2013-08-27 00:39:30 +00:00
return splitbyte + 4 ;
}
splitbyte + + ;
}
return 0 ;
}
/ * *
* Find the byte positions where multipart boundaries start .
* /
2015-11-15 23:32:04 +00:00
private int [ ] getBoundaryPositions ( ByteBuffer b , byte [ ] boundary ) {
2013-08-27 00:39:30 +00:00
int matchcount = 0 ;
int matchbyte = - 1 ;
List < Integer > matchbytes = new ArrayList < Integer > ( ) ;
2015-11-15 23:32:04 +00:00
for ( int i = 0 ; i < b . limit ( ) ; i + + ) {
if ( b . get ( i ) = = boundary [ matchcount ] ) {
if ( matchcount = = 0 ) {
2013-08-27 00:39:30 +00:00
matchbyte = i ;
}
matchcount + + ;
2015-11-15 23:32:04 +00:00
if ( matchcount = = boundary . length ) {
2013-08-27 00:39:30 +00:00
matchbytes . add ( matchbyte ) ;
matchcount = 0 ;
matchbyte = - 1 ;
}
2015-11-15 23:32:04 +00:00
} else {
2013-08-27 00:39:30 +00:00
i - = matchcount ;
matchcount = 0 ;
matchbyte = - 1 ;
}
}
int [ ] ret = new int [ matchbytes . size ( ) ] ;
2015-11-15 23:32:04 +00:00
for ( int i = 0 ; i < ret . length ; i + + ) {
2013-08-27 00:39:30 +00:00
ret [ i ] = matchbytes . get ( i ) ;
}
return ret ;
}
/ * *
* Retrieves the content of a sent file and saves it to a temporary file . The full path to the saved file is returned .
* /
2015-11-15 23:32:04 +00:00
private String saveTmpFile ( ByteBuffer b , int offset , int len ) {
2013-08-27 00:39:30 +00:00
String path = " " ;
2015-11-15 23:32:04 +00:00
if ( len > 0 ) {
2013-08-27 00:39:30 +00:00
FileOutputStream fileOutputStream = null ;
2015-11-15 23:32:04 +00:00
try {
2013-08-27 00:39:30 +00:00
TempFile tempFile = tempFileManager . createTempFile ( ) ;
ByteBuffer src = b . duplicate ( ) ;
fileOutputStream = new FileOutputStream ( tempFile . getName ( ) ) ;
FileChannel dest = fileOutputStream . getChannel ( ) ;
src . position ( offset ) . limit ( offset + len ) ;
dest . write ( src . slice ( ) ) ;
path = tempFile . getName ( ) ;
2015-11-15 23:32:04 +00:00
} catch ( Exception e ) { // Catch exception if any
2015-10-19 17:43:46 +00:00
FLog . severe ( e ) ;
2015-11-15 23:32:04 +00:00
} finally {
2013-08-27 00:39:30 +00:00
safeClose ( fileOutputStream ) ;
}
}
return path ;
}
2015-11-15 23:32:04 +00:00
private RandomAccessFile getTmpBucket ( ) {
try {
2013-08-27 00:39:30 +00:00
TempFile tempFile = tempFileManager . createTempFile ( ) ;
return new RandomAccessFile ( tempFile . getName ( ) , " rw " ) ;
2015-11-15 23:32:04 +00:00
} catch ( Exception e ) {
2015-10-19 17:43:46 +00:00
FLog . severe ( e ) ;
2013-08-27 00:39:30 +00:00
}
return null ;
}
/ * *
* It returns the offset separating multipart file headers from the file ' s data .
* /
2015-11-15 23:32:04 +00:00
private int stripMultipartHeaders ( ByteBuffer b , int offset ) {
2013-08-27 00:39:30 +00:00
int i ;
2015-11-15 23:32:04 +00:00
for ( i = offset ; i < b . limit ( ) ; i + + ) {
if ( b . get ( i ) = = '\r' & & b . get ( + + i ) = = '\n' & & b . get ( + + i ) = = '\r' & & b . get ( + + i ) = = '\n' ) {
2013-08-27 00:39:30 +00:00
break ;
}
}
return i + 1 ;
}
/ * *
* Decodes parameters in percent - encoded URI - format ( e . g . " name=Jack%20Daniels&pass=Single%20Malt " ) and
* adds them to given Map . NOTE : this doesn ' t support multiple identical keys due to the simplicity of Map .
* /
2015-11-15 23:32:04 +00:00
private void decodeParms ( String parms , Map < String , String > p ) {
if ( parms = = null ) {
2013-08-27 00:39:30 +00:00
p . put ( QUERY_STRING_PARAMETER , " " ) ;
return ;
}
p . put ( QUERY_STRING_PARAMETER , parms ) ;
StringTokenizer st = new StringTokenizer ( parms , " & " ) ;
2015-11-15 23:32:04 +00:00
while ( st . hasMoreTokens ( ) ) {
2013-08-27 00:39:30 +00:00
String e = st . nextToken ( ) ;
int sep = e . indexOf ( '=' ) ;
2015-11-15 23:32:04 +00:00
if ( sep > = 0 ) {
2013-08-27 00:39:30 +00:00
p . put ( decodePercent ( e . substring ( 0 , sep ) ) . trim ( ) ,
decodePercent ( e . substring ( sep + 1 ) ) ) ;
2015-11-15 23:32:04 +00:00
} else {
2013-08-27 00:39:30 +00:00
p . put ( decodePercent ( e ) . trim ( ) , " " ) ;
}
}
}
2015-11-15 23:32:04 +00:00
public final Map < String , String > getParms ( ) {
2013-08-27 00:39:30 +00:00
return parms ;
}
2015-11-15 23:32:04 +00:00
public final Map < String , String > getHeaders ( ) {
2013-08-27 00:39:30 +00:00
return headers ;
}
2015-11-15 23:32:04 +00:00
public final String getUri ( ) {
2013-08-27 00:39:30 +00:00
return uri ;
}
2015-11-15 23:32:04 +00:00
public final Method getMethod ( ) {
2013-08-27 00:39:30 +00:00
return method ;
}
2015-11-15 23:32:04 +00:00
public final InputStream getInputStream ( ) {
2013-08-27 00:39:30 +00:00
return inputStream ;
}
2013-09-03 14:28:56 +00:00
2015-11-15 23:32:04 +00:00
public CookieHandler getCookies ( ) {
2013-09-18 01:31:46 +00:00
return cookies ;
}
2015-11-15 23:32:04 +00:00
public Socket getSocket ( ) {
2013-09-03 14:28:56 +00:00
return socket ;
}
2013-08-27 00:39:30 +00:00
}
2015-11-15 23:32:04 +00:00
public static class Cookie {
2013-09-18 01:31:46 +00:00
private String n , v , e ;
2013-08-27 00:39:30 +00:00
2015-11-15 23:32:04 +00:00
public Cookie ( String name , String value , String expires ) {
2013-09-18 01:31:46 +00:00
n = name ;
v = value ;
e = expires ;
2013-08-27 00:39:30 +00:00
}
2015-11-15 23:32:04 +00:00
public Cookie ( String name , String value ) {
2013-09-18 01:31:46 +00:00
this ( name , value , 30 ) ;
2013-08-27 00:39:30 +00:00
}
2015-11-15 23:32:04 +00:00
public Cookie ( String name , String value , int numDays ) {
2013-09-18 01:31:46 +00:00
n = name ;
v = value ;
e = getHTTPTime ( numDays ) ;
2013-08-27 00:39:30 +00:00
}
2015-11-15 23:32:04 +00:00
public String getHTTPHeader ( ) {
2013-09-18 01:31:46 +00:00
String fmt = " %s=%s; expires=%s " ;
return String . format ( fmt , n , v , e ) ;
2013-08-27 00:39:30 +00:00
}
2015-11-15 23:32:04 +00:00
public static String getHTTPTime ( int days ) {
2013-09-18 01:31:46 +00:00
Calendar calendar = Calendar . getInstance ( ) ;
SimpleDateFormat dateFormat = new SimpleDateFormat ( " EEE, dd MMM yyyy HH:mm:ss z " , Locale . US ) ;
dateFormat . setTimeZone ( TimeZone . getTimeZone ( " GMT " ) ) ;
calendar . add ( Calendar . DAY_OF_MONTH , days ) ;
return dateFormat . format ( calendar . getTime ( ) ) ;
2013-08-27 00:39:30 +00:00
}
}
2013-09-18 01:31:46 +00:00
/ * *
* Provides rudimentary support for cookies .
* Doesn ' t support ' path ' , ' secure ' nor ' httpOnly ' .
* Feel free to improve it and / or add unsupported features .
*
* @author LordFokas
* /
2015-11-15 23:32:04 +00:00
public class CookieHandler implements Iterable < String > {
2013-09-18 01:31:46 +00:00
private HashMap < String , String > cookies = new HashMap < String , String > ( ) ;
private ArrayList < Cookie > queue = new ArrayList < Cookie > ( ) ;
2015-11-15 23:32:04 +00:00
public CookieHandler ( Map < String , String > httpHeaders ) {
2013-09-18 01:31:46 +00:00
String raw = httpHeaders . get ( " cookie " ) ;
2015-11-15 23:32:04 +00:00
if ( raw ! = null ) {
2013-09-18 01:31:46 +00:00
String [ ] tokens = raw . split ( " ; " ) ;
2015-11-15 23:32:04 +00:00
for ( String token : tokens ) {
2013-09-18 01:31:46 +00:00
String [ ] data = token . trim ( ) . split ( " = " ) ;
2015-11-15 23:32:04 +00:00
if ( data . length = = 2 ) {
2013-09-18 01:31:46 +00:00
cookies . put ( data [ 0 ] , data [ 1 ] ) ;
}
}
2013-08-27 00:39:30 +00:00
}
}
2013-09-18 01:31:46 +00:00
@Override
2015-11-15 23:32:04 +00:00
public Iterator < String > iterator ( ) {
2013-09-18 01:31:46 +00:00
return cookies . keySet ( ) . iterator ( ) ;
}
2013-08-27 00:39:30 +00:00
2013-09-18 01:31:46 +00:00
/ * *
* Read a cookie from the HTTP Headers .
*
* @param name The cookie ' s name .
* @return The cookie ' s value if it exists , null otherwise .
* /
2015-11-15 23:32:04 +00:00
public String read ( String name ) {
2013-09-18 01:31:46 +00:00
return cookies . get ( name ) ;
}
2013-08-27 00:39:30 +00:00
2013-09-18 01:31:46 +00:00
/ * *
* Sets a cookie .
*
2015-11-15 23:32:04 +00:00
* @param name The cookie ' s name .
* @param value The cookie ' s value .
2013-09-18 01:31:46 +00:00
* @param expires How many days until the cookie expires .
* /
2015-11-15 23:32:04 +00:00
public void set ( String name , String value , int expires ) {
2013-09-18 01:31:46 +00:00
queue . add ( new Cookie ( name , value , Cookie . getHTTPTime ( expires ) ) ) ;
}
2015-11-15 23:32:04 +00:00
public void set ( Cookie cookie ) {
2013-09-18 01:31:46 +00:00
queue . add ( cookie ) ;
}
/ * *
* Set a cookie with an expiration date from a month ago , effectively deleting it on the client side .
*
* @param name The cookie name .
* /
2015-11-15 23:32:04 +00:00
public void delete ( String name ) {
2013-09-18 01:31:46 +00:00
set ( name , " -delete- " , - 30 ) ;
}
/ * *
* Internally used by the webserver to add all queued cookies into the Response ' s HTTP Headers .
*
* @param response The Response object to which headers the queued cookies will be added .
* /
2015-11-15 23:32:04 +00:00
public void unloadQueue ( Response response ) {
for ( Cookie cookie : queue ) {
2013-09-18 01:31:46 +00:00
response . addHeader ( " Set-Cookie " , cookie . getHTTPHeader ( ) ) ;
}
}
2013-08-27 00:39:30 +00:00
}
2015-11-15 23:32:04 +00:00
2013-09-18 01:31:46 +00:00
}