| /* | |
| * Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package com.sun.jndi.ldap; | |
| import static java.nio.charset.StandardCharsets.ISO_8859_1; | |
| import static java.nio.charset.StandardCharsets.UTF_8; | |
| /** | |
| * A BER encoder. | |
| * | |
| * @author Jagane Sundar | |
| * @author Scott Seligman | |
| * @author Vincent Ryan | |
| */ | |
| public final class BerEncoder extends Ber { | |
| private int curSeqIndex; | |
| private int seqOffset[]; | |
| private static final int INITIAL_SEQUENCES = 16; | |
| private static final int DEFAULT_BUFSIZE = 1024; | |
| // When buf is full, expand its size by the following factor. | |
| private static final int BUF_GROWTH_FACTOR = 8; | |
| /** | |
| * Creates a BER buffer for encoding. | |
| */ | |
| public BerEncoder() { | |
| this(DEFAULT_BUFSIZE); | |
| } | |
| /** | |
| * Creates a BER buffer of a specified size for encoding. | |
| * Specify the initial bufsize. Buffer will be expanded as needed. | |
| * @param bufsize The number of bytes for the buffer. | |
| */ | |
| public BerEncoder(int bufsize) { | |
| buf = new byte[bufsize]; | |
| this.bufsize = bufsize; | |
| offset = 0; | |
| seqOffset = new int[INITIAL_SEQUENCES]; | |
| curSeqIndex = 0; | |
| } | |
| /** | |
| * Resets encoder to state when newly constructed. Zeros out | |
| * internal data structures. | |
| */ | |
| public void reset() { | |
| while (offset > 0) { | |
| buf[--offset] = 0; | |
| } | |
| while (curSeqIndex > 0) { | |
| seqOffset[--curSeqIndex] = 0; | |
| } | |
| } | |
| // ------------------ Accessor methods ------------ | |
| /** | |
| * Gets the number of encoded bytes in this BER buffer. | |
| */ | |
| public int getDataLen() { | |
| return offset; | |
| } | |
| /** | |
| * Gets the buffer that contains the BER encoding. Throws an | |
| * exception if unmatched beginSeq() and endSeq() pairs were | |
| * encountered. Not entire buffer contains encoded bytes. | |
| * Use getDataLen() to determine number of encoded bytes. | |
| * Use getBuffer(true) to get rid of excess bytes in array. | |
| * @throws IllegalStateException If buffer contains unbalanced sequence. | |
| */ | |
| public byte[] getBuf() { | |
| if (curSeqIndex != 0) { | |
| throw new IllegalStateException("BER encode error: Unbalanced SEQUENCEs."); | |
| } | |
| return buf; // shared buffer, be careful to use this method. | |
| } | |
| /** | |
| * Gets the buffer that contains the BER encoding, trimming unused bytes. | |
| * | |
| * @throws IllegalStateException If buffer contains unbalanced sequence. | |
| */ | |
| public byte[] getTrimmedBuf() { | |
| int len = getDataLen(); | |
| byte[] trimBuf = new byte[len]; | |
| System.arraycopy(getBuf(), 0, trimBuf, 0, len); | |
| return trimBuf; | |
| } | |
| // -------------- encoding methods ------------- | |
| /** | |
| * Begin encoding a sequence with a tag. | |
| */ | |
| public void beginSeq(int tag) { | |
| // Double the size of the SEQUENCE array if it overflows | |
| if (curSeqIndex >= seqOffset.length) { | |
| int[] seqOffsetTmp = new int[seqOffset.length * 2]; | |
| for (int i = 0; i < seqOffset.length; i++) { | |
| seqOffsetTmp[i] = seqOffset[i]; | |
| } | |
| seqOffset = seqOffsetTmp; | |
| } | |
| encodeByte(tag); | |
| seqOffset[curSeqIndex] = offset; | |
| // Save space for sequence length. | |
| // %%% Currently we save enough space for sequences up to 64k. | |
| // For larger sequences we'll need to shift the data to the right | |
| // in endSeq(). If we could instead pad the length field with | |
| // zeros, it would be a big win. | |
| ensureFreeBytes(3); | |
| offset += 3; | |
| curSeqIndex++; | |
| } | |
| /** | |
| * Terminate a BER sequence. | |
| */ | |
| public void endSeq() throws EncodeException { | |
| curSeqIndex--; | |
| if (curSeqIndex < 0) { | |
| throw new IllegalStateException("BER encode error: Unbalanced SEQUENCEs."); | |
| } | |
| int start = seqOffset[curSeqIndex] + 3; // index beyond length field | |
| int len = offset - start; | |
| if (len <= 0x7f) { | |
| shiftSeqData(start, len, -2); | |
| buf[seqOffset[curSeqIndex]] = (byte) len; | |
| } else if (len <= 0xff) { | |
| shiftSeqData(start, len, -1); | |
| buf[seqOffset[curSeqIndex]] = (byte) 0x81; | |
| buf[seqOffset[curSeqIndex] + 1] = (byte) len; | |
| } else if (len <= 0xffff) { | |
| buf[seqOffset[curSeqIndex]] = (byte) 0x82; | |
| buf[seqOffset[curSeqIndex] + 1] = (byte) (len >> 8); | |
| buf[seqOffset[curSeqIndex] + 2] = (byte) len; | |
| } else if (len <= 0xffffff) { | |
| shiftSeqData(start, len, 1); | |
| buf[seqOffset[curSeqIndex]] = (byte) 0x83; | |
| buf[seqOffset[curSeqIndex] + 1] = (byte) (len >> 16); | |
| buf[seqOffset[curSeqIndex] + 2] = (byte) (len >> 8); | |
| buf[seqOffset[curSeqIndex] + 3] = (byte) len; | |
| } else { | |
| throw new EncodeException("SEQUENCE too long"); | |
| } | |
| } | |
| /** | |
| * Shifts contents of buf in the range [start,start+len) a specified amount. | |
| * Positive shift value means shift to the right. | |
| */ | |
| private void shiftSeqData(int start, int len, int shift) { | |
| if (shift > 0) { | |
| ensureFreeBytes(shift); | |
| } | |
| System.arraycopy(buf, start, buf, start + shift, len); | |
| offset += shift; | |
| } | |
| /** | |
| * Encode a single byte. | |
| */ | |
| public void encodeByte(int b) { | |
| ensureFreeBytes(1); | |
| buf[offset++] = (byte) b; | |
| } | |
| /* | |
| private void deleteByte() { | |
| offset--; | |
| } | |
| */ | |
| /* | |
| * Encodes an int. | |
| *<blockquote><pre> | |
| * BER integer ::= 0x02 berlength byte {byte}* | |
| *</pre></blockquote> | |
| */ | |
| public void encodeInt(int i) { | |
| encodeInt(i, 0x02); | |
| } | |
| /** | |
| * Encodes an int and a tag. | |
| *<blockquote><pre> | |
| * BER integer w tag ::= tag berlength byte {byte}* | |
| *</pre></blockquote> | |
| */ | |
| public void encodeInt(int i, int tag) { | |
| int mask = 0xff800000; | |
| int intsize = 4; | |
| while( (((i & mask) == 0) || ((i & mask) == mask)) && (intsize > 1) ) { | |
| intsize--; | |
| i <<= 8; | |
| } | |
| encodeInt(i, tag, intsize); | |
| } | |
| // | |
| // encodes an int using numbytes for the actual encoding. | |
| // | |
| private void encodeInt(int i, int tag, int intsize) { | |
| // | |
| // integer ::= 0x02 asnlength byte {byte}* | |
| // | |
| if (intsize > 4) { | |
| throw new IllegalArgumentException("BER encode error: INTEGER too long."); | |
| } | |
| ensureFreeBytes(2 + intsize); | |
| buf[offset++] = (byte) tag; | |
| buf[offset++] = (byte) intsize; | |
| int mask = 0xff000000; | |
| while (intsize-- > 0) { | |
| buf[offset++] = (byte) ((i & mask) >> 24); | |
| i <<= 8; | |
| } | |
| } | |
| /** | |
| * Encodes a boolean. | |
| *<blockquote><pre> | |
| * BER boolean ::= 0x01 0x01 {0xff|0x00} | |
| *</pre></blockquote> | |
| */ | |
| public void encodeBoolean(boolean b) { | |
| encodeBoolean(b, ASN_BOOLEAN); | |
| } | |
| /** | |
| * Encodes a boolean and a tag | |
| *<blockquote><pre> | |
| * BER boolean w TAG ::= tag 0x01 {0xff|0x00} | |
| *</pre></blockquote> | |
| */ | |
| public void encodeBoolean(boolean b, int tag) { | |
| ensureFreeBytes(3); | |
| buf[offset++] = (byte) tag; | |
| buf[offset++] = 0x01; | |
| buf[offset++] = b ? (byte) 0xff : (byte) 0x00; | |
| } | |
| /** | |
| * Encodes a string. | |
| *<blockquote><pre> | |
| * BER string ::= 0x04 strlen byte1 byte2... | |
| *</pre></blockquote> | |
| * The string is converted into bytes using UTF-8 or ISO-Latin-1. | |
| */ | |
| public void encodeString(String str, boolean encodeUTF8) | |
| throws EncodeException { | |
| encodeString(str, ASN_OCTET_STR, encodeUTF8); | |
| } | |
| /** | |
| * Encodes a string and a tag. | |
| *<blockquote><pre> | |
| * BER string w TAG ::= tag strlen byte1 byte2... | |
| *</pre></blockquote> | |
| */ | |
| public void encodeString(String str, int tag, boolean encodeUTF8) | |
| throws EncodeException { | |
| encodeByte(tag); | |
| int i = 0; | |
| int count; | |
| byte[] bytes = null; | |
| if (str == null) { | |
| count = 0; | |
| } else if (encodeUTF8) { | |
| bytes = str.getBytes(UTF_8); | |
| count = bytes.length; | |
| } else { | |
| bytes = str.getBytes(ISO_8859_1); | |
| count = bytes.length; | |
| } | |
| encodeLength(count); | |
| ensureFreeBytes(count); | |
| while (i < count) { | |
| buf[offset++] = bytes[i++]; | |
| } | |
| } | |
| /** | |
| * Encodes a portion of an octet string and a tag. | |
| */ | |
| public void encodeOctetString(byte tb[], int tag, int tboffset, int length) | |
| throws EncodeException { | |
| encodeByte(tag); | |
| encodeLength(length); | |
| if (length > 0) { | |
| ensureFreeBytes(length); | |
| System.arraycopy(tb, tboffset, buf, offset, length); | |
| offset += length; | |
| } | |
| } | |
| /** | |
| * Encodes an octet string and a tag. | |
| */ | |
| public void encodeOctetString(byte tb[], int tag) throws EncodeException { | |
| encodeOctetString(tb, tag, 0, tb.length); | |
| } | |
| private void encodeLength(int len) throws EncodeException { | |
| ensureFreeBytes(4); // worst case | |
| if (len < 128) { | |
| buf[offset++] = (byte) len; | |
| } else if (len <= 0xff) { | |
| buf[offset++] = (byte) 0x81; | |
| buf[offset++] = (byte) len; | |
| } else if (len <= 0xffff) { | |
| buf[offset++] = (byte) 0x82; | |
| buf[offset++] = (byte) (len >> 8); | |
| buf[offset++] = (byte) (len & 0xff); | |
| } else if (len <= 0xffffff) { | |
| buf[offset++] = (byte) 0x83; | |
| buf[offset++] = (byte) (len >> 16); | |
| buf[offset++] = (byte) (len >> 8); | |
| buf[offset++] = (byte) (len & 0xff); | |
| } else { | |
| throw new EncodeException("string too long"); | |
| } | |
| } | |
| /** | |
| * Encodes an array of strings. | |
| */ | |
| public void encodeStringArray(String strs[], boolean encodeUTF8) | |
| throws EncodeException { | |
| if (strs == null) | |
| return; | |
| for (int i = 0; i < strs.length; i++) { | |
| encodeString(strs[i], encodeUTF8); | |
| } | |
| } | |
| /* | |
| private void encodeNull() { | |
| // | |
| // NULL ::= 0x05 0x00 | |
| // | |
| encodeByte(0x05); | |
| encodeByte(0x00); | |
| } | |
| */ | |
| /** | |
| * Ensures that there are at least "len" unused bytes in "buf". | |
| * When more space is needed "buf" is expanded by a factor of | |
| * BUF_GROWTH_FACTOR, then "len" bytes are added if "buf" still | |
| * isn't large enough. | |
| */ | |
| private void ensureFreeBytes(int len) { | |
| if (bufsize - offset < len) { | |
| int newsize = bufsize * BUF_GROWTH_FACTOR; | |
| if (newsize - offset < len) { | |
| newsize += len; | |
| } | |
| byte newbuf[] = new byte[newsize]; | |
| // Only copy bytes in the range [0, offset) | |
| System.arraycopy(buf, 0, newbuf, 0, offset); | |
| buf = newbuf; | |
| bufsize = newsize; | |
| } | |
| } | |
| } |