All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netpreserve.jwarc.ChunkedBody.rl Maven / Gradle / Ivy

// recompile: ragel -J ChunkedBody.rl -o ChunkedBody.java
// diagram:   ragel -Vp ChunkedBody.rl | dot -Tpng | feh -

package org.netpreserve.jwarc;

import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;

class ChunkedBody extends MessageBody {
    private final ReadableByteChannel channel;
    private final ByteBuffer buffer;
    private long position = 0;
    private long remaining = 0;
    private long chunkLength = -1;
    private boolean finished;

    public ChunkedBody(ReadableByteChannel channel, ByteBuffer buffer) {
        this.channel = channel;
        this.buffer = buffer;
    }

    public boolean isOpen() {
        return channel.isOpen();
    }

    public void close() throws IOException {
        channel.close();
    }

    public long position() {
        return position;
    }

    public int read(ByteBuffer dst) throws IOException {
        while (chunkLength != 0) {
            if (!buffer.hasRemaining()) {
                // optimisation: let large reads bypass our buffer
                if (remaining >= buffer.capacity() && dst.remaining() >= buffer.capacity()) {
                    int n = IOUtils.transfer(channel, dst, remaining);
                    if (n < 0) throw new EOFException("EOF reached before end of chunked encoding");
                    remaining -= n;
                    position += n;
                    return n;
                }

                // refill
                buffer.compact();
                if (channel.read(buffer) < 0) {
                    throw new EOFException("EOF reached before end of chunked encoding");
                }
                buffer.flip();
            }

            // if we're in the middle of a chunk satisfy it from our buffer
            if (remaining > 0) {
                int n = IOUtils.transfer(buffer, dst, remaining);
                remaining -= n;
                position += n;
                return n;
            }

            // otherwise run the header parser
            chunkLength = -1;
            parse();
            remaining = chunkLength;
        }
        return -1;
    }

    %%{
        machine chunked;

        getkey buffer.get(p);
        action add_length { tmp = tmp * 16 + Character.digit(buffer.get(p), 16); }
        action end_header { if (tmp != 0) { chunkLength = tmp; tmp = 0; fbreak; } }
        action end_final  { chunkLength = 0; }

        hexdigit = "0".."9" | "a".."f" | "A".."F";
        tchar = "!" | "#" | "$" | "%" | "&" | "'" | "*" | "+" | "-" | "." |
                "^" | "_" | "`" | "|" | "~" | digit | alpha;
        qdtext = "\t" | " " | "!" | "#".."[" | "]".."~" | 0x80..0xFF;
        token = tchar+;
        quoted_pair = "\\" (0x00..0x09 | 0x0B..0x0C | 0x0E..0x7F);
        quoted_string = '"' (qdtext | quoted_pair)* '"';

        obs_text = 0x80..0xff;
        CRLF = "\r\n";
        CTL = cntrl | 127;
        WS = " " | "\t";
        RWS = WS+;
        OWS = WS*;
        LWS = CRLF RWS;
        VCHAR = 0x21..0x7E;
        field_vchar = VCHAR | obs_text;
        WORD = field_vchar+;
        TEXT = WORD (RWS WORD)*;


        field_name = token;
        field_value_first = OWS (TEXT OWS)?;
        field_value_folded = LWS (TEXT OWS)?;
        field_value = field_value_first (field_value_folded)*;
        named_field = field_name ":" field_value CRLF;
        named_fields = named_field* CRLF;

        chunk_ext_val = token | quoted_string;
        chunk_extension = ';' token '=' chunk_ext_val;
        chunk_length = hexdigit+ $add_length;
        chunk_header = chunk_length chunk_extension* WS* CRLF @end_header;
        chunk = chunk_header CRLF;
        last_chunk = "0"+ chunk_extension* WS* CRLF;
        chunks := chunk* last_chunk named_fields @end_final;
    }%%

    private int cs = chunked_start;
    private long tmp;

    private void parse() throws ParsingException {
        int p = buffer.position();
        int pe = buffer.limit();
        %% write exec;
        buffer.position(p);
        if (cs == chunked_error) {
            throw new ParsingException("chunked encoding at position " + p + ": "
                    + getErrorContext(buffer, (int) p, 40));
        }
    }

    %% write data;
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy