Coffee Space


Listen:

Server One Liner

Preview Image

Preview Image

Background

I happened upon a GitHub gist containing a community created list of server one liners. The idea is that the server should fulfil the following:

After reviewing the list at the time, I noticed there were no Java entries that didn't have some kind of dependency. I wrote a basic Java web server quite some years ago and decided it would be a good exercise in minimalism.

Existing Solutions

Curl

renatoathaydes commented on 10 Mar 2019:

Hi, your list is missing an entry for Java.

I am the author of a HTTP server that fits your requirements. You can install it as a single jar with:

curl https://jcenter.bintray.com/com/athaydes/rawhttp/rawhttp-cli/1.0/rawhttp-cli-1.0-all.jar -o rawhttp.jar

Then, run with:

java -jar ./rawhttp.jar serve . -p 8000

Works with Java 8+ including latest Java 11 versions.

Documentation

Sources

Firstly, random binaries from some unknown place are disgusting - who knows what this code runs without decompiling it.

Secondly it feels as if the use of curl is quite ridiculous, especially as it already has so much web capability within it's own right.

Sun HTTP Server

informvisu commented on 5 Oct 2019:

All you need is JAVA 1.6+

./httpServer.sh

/opt/java_1.6/bin/javac HTTPServer.java
/opt/java_1.6/bin/java HTTPServer $1

HTTPServer.java

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Date;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

public class HTTPServer {
    static int port = 8000;
    public static void main(String[] args) throws Exception {
        if(args.length > 0) try {
            port = Integer.parseInt(args[0]);
        } catch (Exception e) {System.out.println("Invalid port number "+args[0]);}
        HttpServer server = HttpServer.create(new InetSocketAddress(InetAddress.getLocalHost(), port), 5);
        System.out.println("Server is listening at "+server.getAddress());
        server.createContext("/", new MyHandler());
        server.setExecutor(null); // creates a default executor
        server.start();
    }
    static class MyHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            String response = "This is the response"; //TODO construct your own response
            t.sendResponseHeaders(200, response.length());
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }
}

Okay, we're getting there, but I still have a few complaints:

  • The imports can be simplified, for example using import java.io.* to reduce to imports down to one.
  • The use of a Sun/Oracle library basically removes the possibility of use with OpenJDK (boo).
  • IS NOT ONE LINE.
  • Doesn't have to do any hard work - it's basically all implemented in the libraries.

Solution

The solution I posted is here:

echo -e 'import java.net.*;import java.nio.file.*;public class M{public static void main(String[] args)throws Exception{byte[]b=new byte[16777215];ServerSocket s=new ServerSocket(3333);for(;;)try{Socket c=s.accept();c.getInputStream().read(b);c.getOutputStream().write(("HTTP/1.1 200 OK\\r\\n\\r\\n"+new String(Files.readAllBytes(Paths.get(new String(b).split(" ")[1].substring(1))))).getBytes());c.getOutputStream().flush();c.close();}catch(Exception e){}}}'>M.java;javac M.java;java M

Okay, it's quite compact and if we un-pack the Java source, we get something like this (with added comments for readability):

/* Imports used by the program */
import java.net.*;
import java.nio.file.*;

public class M{
  /**
   * main()
   *
   * The main entry method into the program. Note that we ignore all command
   * line arguments, but Java insists we pretend to need them.
   *
   * @param args The command line arguments.
   **/
  public static void main(String[] args) throws Exception{
    /* A buffer for reading input files into */
    byte[] b = new byte[16777215];
    /* Start the server on the given port */
    ServerSocket s = new ServerSocket(3333);
    /* Infinte loop the server */
    for(;;)
      /* Catch any thrown exception */
      try{
        /* Block until there is a new connection */
        Socket c = s.accept();
        /* Read the input from the client (we could infinitely hang here!) */
        c.getInputStream().read(b);
        /* Form a response */
        c.getOutputStream().write(
          (
            /* Basic HTTP response header */
            "HTTP/1.1 200 OK\\r\\n\\r\\n" +
            /* Form the file content */
            new String(
              Files.readAllBytes(
                Paths.get(
                  /* Assume the second "part" is the fiel to be loaded */
                  /* Note that we reuse our input buffer as an output buffer */
                  new String(b).split(" ")[1].substring(1)
                  /* The substring removes the forward-slash */
                )
              )
            )
          /* Convert String to byte array to be written */
          ).getBytes()
        );
        /* Make sure all of the bytes are written before closing */
        c.getOutputStream().flush();
        /* Be kind and close the stream off */
        c.close();
      }catch(Exception e){
        /* We don't care if this loop dies, just don't crash the server */
      }
  }
}

I think it almost fulfils the task, but there are some very glaring issues (i.e. never use this code in production):

And that's just to name a few that instantly stick out at me. For a small test server (on a private network), or teaching students the basics of web servers, it will suffice.

It would actually make a very nice project for cyber security students to explore and report on the very large attack surface, possibly firstly without seeing the code and then after getting to see the code.