Is there a function to retrieve the underlying InputStream/OutputStream from com.sun.net.httpserver.HttpExchange?

26 views Asked by At

I have a large custom-built web server in Java, which wraps the com.sun.net.httpserver API and adds a number of utility functions to build a full-blown website. I also added the ability for the web server to handle WebSocket requests. In order to do this, the web server needs to get access to the underlying InputStream and OutputStream of the HttpExchange so it can do the necessary bit-fiddling and send the data back without HTTP headers. I have written some code to do this and verified that it works on my JVM, but the code to do so is rather... questionable, so I am curious if there is a better way to get at the underlying streams. The code I have now to get the underlying streams:

import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import com.sun.net.httpserver.HttpExchange;

import sun.misc.Unsafe;
import web.WebServer.IOStream;

/**
 * "Undefined behavior"? I think you mean, "designed effects"
 * <p>
 * Before reading this class, you should keep in mind that that this is called
 * <code>HttpExchange<b>Hack</b></code> for a reason
 * @author Derek McCants
 */
public class HttpExchangeHack
{
    /**
     * Yeah, one line in and things are already going sideways
     */
    private static Unsafe unsafe;
    static
    {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe)f.get(null);
        } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
            e.printStackTrace();
        }
    };
    
    /**
     * Retrieves the specified field from the specified parent object,
     * bypassing all Java access controls and security checks.
     * <p>
     * Or crashes the underlying JVM, depending on what mood we're in.
     * <p>
     * This works by systematically corrupting the memory around the desired
     * object until we overwrite the override flag, which allows us to ignore
     * all Java security checks.
     * @param f The private field to access
     * @param toObtainFrom The object to access the field from
     * @return The desired object, or maybe not at all if the JVM realizes
     * we're being naughty
     * @deprecated You really shouldn't be using this
     * @implNote Wow, if we're really lucky, this might even work!
     */
    public static final Object hackObjectFromField(Field f, Object toObtainFrom)
    {
        try {
            f.setAccessible(true); // welp, its worth a try
            return f.get(toObtainFrom); // this will work on < java 1.8
        } catch (Exception e) {
            try {
                for(int off = 0; off < 100; ++off)
                {
                    if(off >= 5 && off <= 11)
                        continue; // these offsets just immediately crash the JVM
                    //System.err.printf("corrupting offset %d\n", off);
                    //System.err.flush(); // in case the JVM crashes before that message can be printed
                    int old = unsafe.getInt(f, off); // save the old memory so we can restore it
                    unsafe.putInt(f, off, 1); // corrupt the memory, we are trying to overwrite the override flag
                    if(f.canAccess(toObtainFrom)) // did we get it?
                        return f.get(toObtainFrom); // we got it! return the object!!
                    unsafe.putInt(f, off, old); // ok, that didnt work, put it back
                }
            } catch (Exception e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
        }
        System.err.printf("failed to hack object!\n");
        return null;
    }
    
    /**
     * Retrieves the underlying <code>InputStream</code> and
     * <code>OutputStream</code> from a <code>HttpExchange</code> object.
     * <p>
     * Or, depending on the position of the moon and the stars, immediately
     * crashes the underlying JVM
     * @param t The <code>HttpExchange</code>
     * @return Assuming the JVM hasn't crashed or realized we're being naughty
     * by this point, then the underlying <code>InputStream</code> and
     * <code>OutputStream</code> wrapped in an <code>IOStream</code> object
     * @deprecated You really shouldn't be using this
     * @implNote Wow, if we're really lucky, this might even work!
     */
    public static IOStream hackRawStreamsFromExchange(HttpExchange t)
    {
        try {
            Field exchangeImplField = t.getClass().getDeclaredField("impl"); // illegal access? its not illegal if im only borrowing ;)
            Object exchangeImpl = hackObjectFromField(exchangeImplField, t);
            // steal the ros and ris fields from sun.net.exchangeimpl
            OutputStream ros = (OutputStream)hackObjectFromField(exchangeImpl.getClass().getDeclaredField("ros"), exchangeImpl);
            InputStream ris = (InputStream)hackObjectFromField(exchangeImpl.getClass().getDeclaredField("ris"), exchangeImpl);
            return new IOStream(ris, ros);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

I'd prefer to avoid changing API's at this point since so much code already relies on the WebServer class, but I have yet to find any official way to get the access I need.

0

There are 0 answers