Apache Emulation Part 4

In the previous blog post we broke down the binary data format that Apache used to communicate with mod-jaxer. In this post we will cover how we handle taking an http request, and converting it to a binary package that can be recognized by mod Jaxer.

So the image above breaks down generally the content of the data that gets passed into mod-jaxer from Apache. It basically seems to be what amounts to a JSON-like structure of a series of key-values pairs that are serialized into a format that can be easily read as series of zero-terminated strings in C.

What we end up with is effective three distinct blocks. The first block being headers received from the client, the second block block being information about the file being requested by the client via Apache, and lastly the Server environment headers on end.

Since the format for all three of these is effectively the same, we can describe the process for anyone of these given blocks. First we take the block number, which is a single byte 01, 02 or 03 depending on the block. Next we have a big endian short value of the length of the block. We get the length by adding up the length of the string lengths of the key and values for that block, and then adding four bytes for each key-value pair, as each string has a leading length byte and a terminating zero byte. Then we remove one as the last value in the block is not zero terminated, and then we add three for the number of pair leading bytes.

Next is the number of pairs leading bytes which is zero, followed by the number of key-value pairs followed by zero. Then the actual content of the block is the actual content of the length value we calculated. We give a byte for the length of a key string, followed by the key string, followed by a zero byte, followed by a byte length of the value, followed by the value string, followed by a zero byte. And the last value is not terminated by a zero value, but I’m tempted to test to see if that can be left in without incident, so we might test that at some point.

But otherwise all of the headers can be encoded in this way, leaving the last encoding for the content. Which is the byte of 04, followed by a big endian value of the length of the content, followed directly by the bytes of the content, and that it terminated on the end with the bytes 07, 00, 00. And that’s really all there is to it. So while it’s not the most elegant, the resulting function we have to take a json header, and file as a buffer and package it into the byte format that mod-jaxer expects looks something like this:

function binary_json(src, body) {

    let bytes = [];

    for(let i = 0; i < src.length; i++) {

        // First set the block number

        bytes.push(i + 1);

        // Then we get the total number of bytes

        let length = 0;
        let keys = 0;

        let block = src[i];

        for(let key in block) {
            keys++;
            length += key.length;
            length += block[key].length;
            length += 4;
        }

        // Remove the last ending zero

        length -= 1;
        length += 3;

        bytes.push((length & 0xff00) >> 8);
        bytes.push(length & 0xff);
        bytes.push(0, keys, 0);

        // Then we encode all of the keys and values

        for(let key in block) {

            bytes.push(key.length);
            for(let k = 0; k < key.length; k++) {
                bytes.push(key.charCodeAt(k));
            }
            bytes.push(0);

            bytes.push(block[key].length);
            for(let k = 0; k < block[key].length; k++) {
                bytes.push(block[key].charCodeAt(k));
            }
            bytes.push(0);

        }

        bytes.pop();

    }

    bytes.push(src.length + 1);
    bytes.push( (body.length & 0xff00) >> 8 );
    bytes.push( body.length & 0xff );

    let header = Buffer.from(bytes);
    let footer = Buffer.from([ 0x07, 0x00, 0x00]);
    return Buffer.concat([header, body, footer]);

}

And then we can combine this function with the http placeholder and Jaxer proxy code that created in previous posts, and the exciting news is that we do get a response from mod-jaxer using this technique. The issue is that mod-jaxer return the bytes using the same encoding format, so our next blog post will be about doing the reverse, of reading the bytes provided from mod-jaxer into a json and buffer representation. For now while the code is incomplete and messy, but for context, the dev-code for emulating Apache modules can be found here: https://blog.wsd.sh/wp-content/uploads/2019/10/apache_emu.txt