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

com.elopteryx.paint.upload.impl.Base64Decoder Maven / Gradle / Ivy

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.elopteryx.paint.upload.impl;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

/**
 * An efficient and flexible MIME Base64 implementation.
 *
 * @author Jason T. Greene
 */
class Base64Decoder {
    
    private static final byte[] ENCODING_TABLE;
    private static final byte[] DECODING_TABLE = new byte[80];

    static {
        ENCODING_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes(StandardCharsets.US_ASCII);
        for (int i = 0; i < ENCODING_TABLE.length; i++) {
            int v = (ENCODING_TABLE[i] & 0xFF) - 43;
            DECODING_TABLE[v] = (byte)(i + 1);  // zero = illegal
        }
    }

    private int state;
    private int last;
    private static final int SKIP = 0x0FD00;
    private static final int MARK = 0x0FE00;
    private static final int DONE = 0x0FF00;
    private static final int ERROR = 0xF0000;

    private static int nextByte(ByteBuffer buffer, int state, int last, boolean ignoreErrors) throws IOException {
        return nextByte(buffer.get() & 0xFF, state, last, ignoreErrors);
    }

    private static int nextByte(int c, int state, int last, boolean ignoreErrors) throws IOException {
        if (last == MARK) {
            if (c != '=') {
                throw new IOException("Expected padding character");
            }
            return DONE;
        }
        if (c == '=') {
            if (state == 2) {
                return MARK;
            } else if (state == 3) {
                return DONE;
            } else {
                throw new IOException("Unexpected padding character");
            }
        }
        if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
            return SKIP;
        }
        if (c < 43 || c > 122) {
            if (ignoreErrors) {
                return ERROR;
            }
            throw new IOException("Invalid base64 character encountered: " + c);
        }
        int b = (DECODING_TABLE[c - 43] & 0xFF) - 1;
        if (b < 0) {
            if (ignoreErrors) {
                return ERROR;
            }
            throw new IOException("Invalid base64 character encountered: " + c);
        }
        return b;
    }

    /**
     * Decodes one Base64 byte buffer into another. This method will return and save state
     * if the target does not have the required capacity. Subsequent calls with a new target will
     * resume reading where it last left off (the source buffer's position). Similarly not all of the
     * source data need be available, this method can be repetitively called as data is made available.
     *
     * 

The decoder will skip white space, but will error if it detects corruption.

* * @param source the byte buffer to read encoded data from * @param target the byte buffer to write decoded data to * @throws java.io.IOException if the encoded data is corrupted */ public void decode(ByteBuffer source, ByteBuffer target) throws IOException { if (target == null) throw new IllegalStateException(); int last = this.last; int state = this.state; int remaining = source.remaining(); int targetRemaining = target.remaining(); int b = 0; while (remaining-- > 0 && targetRemaining > 0) { b = nextByte(source, state, last, false); if (b == MARK) { last = MARK; if (--remaining <= 0) { break; } b = nextByte(source, state, last, false); } if (b == DONE) { last = state = 0; break; } if (b == SKIP) { continue; } // ( 6 | 2) (4 | 4) (2 | 6) if (state == 0) { last = b << 2; state++; if (remaining-- <= 0) { break; } b = nextByte(source, state, last, false); if ((b & 0xF000) != 0) { source.position(source.position() - 1); continue; } } if (state == 1) { target.put((byte)(last | (b >>> 4))); last = (b & 0x0F) << 4; state++; if (remaining-- <= 0 || --targetRemaining <= 0) { break; } b = nextByte(source, state, last, false); if ((b & 0xF000) != 0) { source.position(source.position() - 1); continue; } } if (state == 2) { target.put((byte) (last | (b >>> 2))); last = (b & 0x3) << 6; state++; if (remaining-- <= 0 || --targetRemaining <= 0) { break; } b = nextByte(source, state, last, false); if ((b & 0xF000) != 0) { source.position(source.position() - 1); continue; } } if (state == 3) { target.put((byte)(last | b)); last = state = 0; targetRemaining--; } } if (remaining > 0) { drain(source, b, state, last); } this.last = last; this.state = state; } private static void drain(ByteBuffer source, int b, int state, int last) { while (b != DONE && source.remaining() > 0) { try { b = nextByte(source, state, last, true); } catch (IOException e) { b = 0; } if (b == MARK) { last = MARK; continue; } // Not WS/pad if ((b & 0xF000) == 0) { source.position(source.position() - 1); break; } } if (b == DONE) { // SKIP one line of trailing whitespace while (source.remaining() > 0) { b = source.get(); if (b == '\n') { break; } else if (b != ' ' && b != '\t' && b != '\r') { source.position(source.position() - 1); break; } } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy