55import java .io .ByteArrayInputStream ;
66import java .io .ByteArrayOutputStream ;
77import java .io .DataInputStream ;
8- import java .io .File ;
98import java .io .IOException ;
109import java .io .InputStream ;
1110import java .io .OutputStream ;
1211import java .io .UnsupportedEncodingException ;
1312import java .net .ServerSocket ;
1413import java .net .Socket ;
1514import java .net .URLDecoder ;
16- import java .nio .file .Files ;
17- import java .nio .file .attribute .BasicFileAttributes ;
18- import java .nio .file .attribute .FileTime ;
19- import java .text .DateFormat ;
2015import java .util .ArrayList ;
2116import java .util .Collection ;
2217import java .util .Collections ;
2318import java .util .HashMap ;
24- import java .util .Locale ;
19+ import java .util .List ;
2520import java .util .Map ;
2621import java .util .zip .ZipEntry ;
2722import java .util .zip .ZipOutputStream ;
2823
2924/**
30- * WebServer
25+ * Minimal AppServer with adequate performance for a single user
3126 *
3227 * @author chrisws
3328 */
3429public abstract class WebServer {
3530 private static final int BUFFER_SIZE = 32768 ;
3631 private static final int SEND_SIZE = BUFFER_SIZE / 4 ;
32+ private static final int LINE_SIZE = 128 ;
3733 private static final String UTF_8 = "utf-8" ;
34+ private static final String TOKEN = "token" ;
3835
3936 public WebServer () {
4037 super ();
@@ -62,6 +59,49 @@ public void run() {
6259 protected abstract void log (String message , Exception exception );
6360 protected abstract void log (String message );
6461
62+ /**
63+ * Returns the HTTP headers from the given stream
64+ */
65+ private List <String > getHeaders (InputStream stream ) throws IOException {
66+ List <String > result = new ArrayList <>();
67+ ByteArrayOutputStream line = new ByteArrayOutputStream (LINE_SIZE );
68+ final char [] endHeader = {'\r' , '\n' , '\r' , '\n' };
69+ int index = 0 ;
70+ for (int b = stream .read (); b != -1 ; b = stream .read ()) {
71+ if (b == endHeader [index ]) {
72+ index ++;
73+ if (index == endHeader .length ) {
74+ // end of headers
75+ break ;
76+ } else if (index == 2 ) {
77+ // end of line
78+ result .add (line .toString ());
79+ line .reset ();
80+ }
81+ } else {
82+ line .write (b );
83+ index = 0 ;
84+ }
85+ }
86+ return result ;
87+ }
88+
89+ /**
90+ * Reads a line of text from the given stream
91+ */
92+ private String getLine (InputStream stream ) throws IOException {
93+ StringBuilder result = new StringBuilder ();
94+ while (stream .available () != 0 ) {
95+ int b = stream .read ();
96+ if (b == -1 || b == 10 || b == 13 ) {
97+ break ;
98+ } else {
99+ result .append (Character .toChars (b ));
100+ }
101+ }
102+ return result .toString ();
103+ }
104+
65105 /**
66106 * Parses HTTP GET parameters with the given name
67107 */
@@ -90,25 +130,8 @@ private Map<String, Collection<String>> getParameters(String url) throws IOExcep
90130 * Parses HTTP POST data from the given input stream
91131 */
92132 private Map <String , String > getPostData (InputStream inputStream , final String line ) throws IOException {
93- int length = 0 ;
94- final String lengthHeader = "content-length: " ;
95- String nextLine = line ;
96- while (nextLine != null && nextLine .length () > 0 ) {
97- if (nextLine .toLowerCase (Locale .ENGLISH ).startsWith (lengthHeader )) {
98- length = Integer .parseInt (nextLine .substring (lengthHeader .length ()));
99- }
100- nextLine = readLine (inputStream );
101- }
102- StringBuilder postData = new StringBuilder ();
103- for (int i = 0 ; i < length ; i ++) {
104- int b = inputStream .read ();
105- if (b == -1 ) {
106- break ;
107- } else {
108- postData .append (Character .toChars (b ));
109- }
110- }
111- String [] fields = postData .toString ().split ("&" );
133+ String postData = getLine (inputStream );
134+ String [] fields = postData .split ("&" );
112135 Map <String , String > result = new HashMap <>();
113136 for (String nextField : fields ) {
114137 int eq = nextField .indexOf ("=" );
@@ -181,25 +204,35 @@ private Response handleFileList() throws IOException {
181204 }
182205
183206 /**
184- * Handler for HTTP GET
207+ * Handler for GET requests
185208 */
186- private void handleGet (Socket socket , String url ) throws IOException {
187- Map <String , Collection <String >> parameters = getParameters ( url );
209+ private void handleGet (Socket socket , String token , List < String > headers , String url ,
210+ Map <String , Collection <String >> parameters ) throws IOException {
188211 if (url .startsWith ("/api/download?" )) {
189- handleDownload (parameters ).send (socket );
212+ if (hasTokenCookie (headers , token )) {
213+ handleDownload (parameters ).send (socket , null );
214+ }
190215 } else {
191- handleWebResponse (url ).send (socket );
216+ handleWebResponse (url ).send (socket , null );
192217 }
193218 }
194219
195220 /**
196- * Handler for HTTP POST
221+ * Handler for POST requests
197222 */
198- private void handlePost (Socket socket , Map <String , String > postData , String url ) throws IOException {
199- if (url .startsWith ("/api/files" )) {
200- handleFileList ().send (socket );
223+ private void handlePost (Socket socket , String token , String url ,
224+ Map <String , String > parameters ) throws IOException {
225+ String userToken = parameters .get (TOKEN );
226+ log ("userToken=" + userToken );
227+ if (token .equals (userToken )) {
228+ if (url .startsWith ("/api/files" )) {
229+ handleFileList ().send (socket , token );
230+ }
231+ log ("Sent POST response" );
232+ } else {
233+ log ("Invalid token" );
234+ handleError ("invalid token" ).send (socket , null );
201235 }
202- log ("Sent POST response" );
203236 }
204237
205238 /**
@@ -218,17 +251,17 @@ private Response handleWebResponse(String asset) throws IOException {
218251 }
219252
220253 /**
221- * Reads a line of text from the given stream
254+ * Inspects the headers to ensure the token cookie is present
222255 */
223- private String readLine ( InputStream stream ) throws IOException {
224- ByteArrayOutputStream out = new ByteArrayOutputStream ( 128 ) ;
225- int b ;
226- for ( b = stream . read (); b != - 1 && b != '\n' ; b = stream . read ( )) {
227- if ( b != '\r' ) {
228- out . write ( b ) ;
256+ private boolean hasTokenCookie ( List < String > headers , String token ) {
257+ boolean result = false ;
258+ for ( String header : headers ) {
259+ if ( header . startsWith ( "Cookie: " ) && header . contains ( TOKEN ) && header . contains ( token )) {
260+ result = true ;
261+ break ;
229262 }
230263 }
231- return b == - 1 ? null : out . size () == 0 ? "" : out . toString () ;
264+ return result ;
232265 }
233266
234267 /**
@@ -251,28 +284,23 @@ private void runServer(final int socketNum, final String token) throws IOExcepti
251284 socket = serverSocket .accept ();
252285 log ("Accepted connection from " + socket .getRemoteSocketAddress ().toString ());
253286 inputStream = new DataInputStream (socket .getInputStream ());
254- String line = readLine (inputStream );
255- if (line != null ) {
287+ List <String > headers = getHeaders (inputStream );
288+ if (!headers .isEmpty ()) {
289+ String line = headers .get (0 );
256290 log (line );
257291 String [] fields = line .split ("\\ s" );
258292 if ("GET" .equals (fields [0 ]) && fields .length > 1 ) {
259- handleGet (socket , fields [1 ]);
260- } else if ("POST" .equals (fields [0 ])) {
261- Map <String , String > postData = getPostData (inputStream , line );
262- String userToken = postData .get ("token" );
263- log ("userToken=" + userToken );
264- if (token .equals (userToken )) {
265- handlePost (socket , postData , fields [1 ]);
266- } else {
267- log ("Invalid token" );
268- handleError ("invalid token" ).send (socket );
269- }
293+ Map <String , Collection <String >> parameters = getParameters (fields [1 ]);
294+ handleGet (socket , token , headers , fields [1 ], parameters );
295+ } else if ("POST" .equals (fields [0 ]) && fields .length > 1 ) {
296+ Map <String , String > parameters = getPostData (inputStream , line );
297+ handlePost (socket , token , fields [1 ], parameters );
270298 } else {
271299 log ("Invalid request" );
272300 }
273301 }
274- } catch (IOException e ) {
275- log ("Server failed: " , e );
302+ } catch (Throwable e ) {
303+ log ("Server failed" );
276304 break ;
277305 } finally {
278306 log ("socket cleanup" );
@@ -291,17 +319,14 @@ private void runServer(final int socketNum, final String token) throws IOExcepti
291319 * BASIC file details
292320 */
293321 public static class FileData {
294- String fileName ;
295- String date ;
296- long size ;
297-
298- FileData (File file ) throws IOException {
299- BasicFileAttributes attr = Files .readAttributes (file .toPath (), BasicFileAttributes .class );
300- FileTime lastModifiedTime = attr .lastModifiedTime ();
301- DateFormat dateFormat = DateFormat .getDateInstance (DateFormat .DEFAULT );
302- this .fileName = file .getName ();
303- this .date = dateFormat .format (lastModifiedTime .toMillis ());
304- this .size = file .length ();
322+ private final String fileName ;
323+ private final String date ;
324+ private final long size ;
325+
326+ public FileData (String fileName , String date , long size ) {
327+ this .fileName = fileName ;
328+ this .date = date ;
329+ this .size = size ;
305330 }
306331 }
307332
@@ -379,13 +404,16 @@ long getLength() {
379404 /**
380405 * Sends the response to the given socket
381406 */
382- void send (Socket socket ) throws IOException {
407+ void send (Socket socket , String session ) throws IOException {
383408 log ("sendResponse() entered" );
384409 String contentLength = "Content-length: " + length + "\r \n " ;
385410 BufferedOutputStream out = new BufferedOutputStream (socket .getOutputStream ());
386411 out .write ("HTTP/1.0 200 OK\r \n " .getBytes ());
387412 out .write ("Content-type: text/html\r \n " .getBytes ());
388413 out .write (contentLength .getBytes ());
414+ if (session != null ) {
415+ out .write (("Set-Cookie: " + TOKEN + "=" + session + "\r \n " ).getBytes ());
416+ }
389417 out .write ("Server: SmallBASIC for Android\r \n \r \n " .getBytes ());
390418 socket .setSendBufferSize (SEND_SIZE );
391419 int sent = toStream (out );
0 commit comments