| /* ==================================================================== |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You 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 org.apache.poi.ddf; |
| |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| import java.util.function.Supplier; |
| |
| import org.apache.poi.sl.usermodel.PictureData.PictureType; |
| import org.apache.poi.util.IOUtils; |
| import org.apache.poi.util.LittleEndian; |
| |
| /** |
| * The BSE record is related closely to the {@code EscherBlipRecord} and stores |
| * extra information about the blip. A blip record is actually stored inside |
| * the BSE record even though the BSE record isn't actually a container record. |
| * |
| * @see EscherBlipRecord |
| */ |
| public final class EscherBSERecord extends EscherRecord { |
| |
| //arbitrarily selected; may need to increase |
| private static final int MAX_RECORD_LENGTH = 100_000; |
| |
| public static final short RECORD_ID = EscherRecordTypes.BSE.typeID; |
| |
| private byte field_1_blipTypeWin32; |
| private byte field_2_blipTypeMacOS; |
| private final byte[] field_3_uid = new byte[16]; |
| private short field_4_tag; |
| private int field_5_size; |
| private int field_6_ref; |
| private int field_7_offset; |
| private byte field_8_usage; |
| private byte field_9_name; |
| private byte field_10_unused2; |
| private byte field_11_unused3; |
| private EscherBlipRecord field_12_blipRecord; |
| |
| private byte[] _remainingData = new byte[0]; |
| |
| public EscherBSERecord() { |
| setRecordId(RECORD_ID); |
| } |
| |
| public EscherBSERecord(EscherBSERecord other) { |
| super(other); |
| |
| field_1_blipTypeWin32 = other.field_1_blipTypeWin32; |
| field_2_blipTypeMacOS = other.field_2_blipTypeMacOS; |
| System.arraycopy(other.field_3_uid, 0, field_3_uid, 0, field_3_uid.length); |
| field_4_tag = other.field_4_tag; |
| field_5_size = other.field_5_size; |
| field_6_ref = other.field_6_ref; |
| field_7_offset = other.field_7_offset; |
| field_8_usage = other.field_8_usage; |
| field_9_name = other.field_9_name; |
| field_10_unused2 = other.field_10_unused2; |
| field_11_unused3 = other.field_11_unused3; |
| field_12_blipRecord = other.field_12_blipRecord.copy(); |
| _remainingData = other._remainingData.clone(); |
| } |
| |
| @Override |
| public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) { |
| int bytesRemaining = readHeader( data, offset ); |
| int pos = offset + 8; |
| field_1_blipTypeWin32 = data[pos]; |
| field_2_blipTypeMacOS = data[pos + 1]; |
| System.arraycopy( data, pos + 2, field_3_uid, 0, 16 ); |
| field_4_tag = LittleEndian.getShort( data, pos + 18 ); |
| field_5_size = LittleEndian.getInt( data, pos + 20 ); |
| field_6_ref = LittleEndian.getInt( data, pos + 24 ); |
| field_7_offset = LittleEndian.getInt( data, pos + 28 ); |
| field_8_usage = data[pos + 32]; |
| field_9_name = data[pos + 33]; |
| field_10_unused2 = data[pos + 34]; |
| field_11_unused3 = data[pos + 35]; |
| bytesRemaining -= 36; |
| |
| int bytesRead = 0; |
| if (bytesRemaining > 0) { |
| // Some older escher formats skip this last record |
| field_12_blipRecord = (EscherBlipRecord) recordFactory.createRecord( data, pos + 36 ); |
| bytesRead = field_12_blipRecord.fillFields( data, pos + 36, recordFactory ); |
| } |
| pos += 36 + bytesRead; |
| bytesRemaining -= bytesRead; |
| |
| _remainingData = IOUtils.safelyClone(data, pos, bytesRemaining, MAX_RECORD_LENGTH); |
| |
| return bytesRemaining + 8 + 36 + (field_12_blipRecord == null ? 0 : field_12_blipRecord.getRecordSize()) ; |
| |
| } |
| |
| @Override |
| public int serialize(int offset, byte[] data, EscherSerializationListener listener) { |
| listener.beforeRecordSerialize( offset, getRecordId(), this ); |
| |
| if (_remainingData == null) { |
| _remainingData = new byte[0]; |
| } |
| |
| LittleEndian.putShort( data, offset, getOptions() ); |
| LittleEndian.putShort( data, offset + 2, getRecordId() ); |
| int blipSize = field_12_blipRecord == null ? 0 : field_12_blipRecord.getRecordSize(); |
| int remainingBytes = _remainingData.length + 36 + blipSize; |
| LittleEndian.putInt( data, offset + 4, remainingBytes ); |
| |
| data[offset + 8] = field_1_blipTypeWin32; |
| data[offset + 9] = field_2_blipTypeMacOS; |
| System.arraycopy(field_3_uid, 0, data, offset + 10, 16); |
| LittleEndian.putShort( data, offset + 26, field_4_tag ); |
| LittleEndian.putInt( data, offset + 28, field_5_size ); |
| LittleEndian.putInt( data, offset + 32, field_6_ref ); |
| LittleEndian.putInt( data, offset + 36, field_7_offset ); |
| data[offset + 40] = field_8_usage; |
| data[offset + 41] = field_9_name; |
| data[offset + 42] = field_10_unused2; |
| data[offset + 43] = field_11_unused3; |
| int bytesWritten = 0; |
| if (field_12_blipRecord != null) { |
| bytesWritten = field_12_blipRecord.serialize( offset + 44, data, new NullEscherSerializationListener() ); |
| } |
| System.arraycopy( _remainingData, 0, data, offset + 44 + bytesWritten, _remainingData.length ); |
| int pos = offset + 8 + 36 + _remainingData.length + bytesWritten; |
| |
| listener.afterRecordSerialize(pos, getRecordId(), pos - offset, this); |
| return pos - offset; |
| } |
| |
| @Override |
| public int getRecordSize() { |
| int field_12_size = 0; |
| if(field_12_blipRecord != null) { |
| field_12_size = field_12_blipRecord.getRecordSize(); |
| } |
| int remaining_size = 0; |
| if(_remainingData != null) { |
| remaining_size = _remainingData.length; |
| } |
| return 8 + 1 + 1 + 16 + 2 + 4 + 4 + 4 + 1 + 1 + |
| 1 + 1 + field_12_size + remaining_size; |
| } |
| |
| @Override |
| public String getRecordName() { |
| return EscherRecordTypes.BSE.recordName; |
| } |
| |
| /** |
| * The expected blip type under windows (failure to match this blip type will result in |
| * Excel converting to this format). |
| * |
| * @return win32 blip type |
| */ |
| public byte getBlipTypeWin32() { |
| return field_1_blipTypeWin32; |
| } |
| |
| public PictureType getPictureTypeWin32() { |
| return PictureType.forNativeID(field_1_blipTypeWin32); |
| } |
| |
| /** |
| * Set the expected win32 blip type |
| * |
| * @param blipTypeWin32 win32 blip type |
| */ |
| public void setBlipTypeWin32(byte blipTypeWin32) { |
| field_1_blipTypeWin32 = blipTypeWin32; |
| } |
| |
| /** |
| * The expected blip type under MacOS (failure to match this blip type will result in |
| * Excel converting to this format). |
| * |
| * @return MacOS blip type |
| */ |
| public byte getBlipTypeMacOS() { |
| return field_2_blipTypeMacOS; |
| } |
| |
| public PictureType getPictureTypeMacOS() { |
| return PictureType.forNativeID(field_2_blipTypeMacOS); |
| } |
| |
| /** |
| * Set the expected MacOS blip type |
| * |
| * @param blipTypeMacOS MacOS blip type |
| */ |
| public void setBlipTypeMacOS(byte blipTypeMacOS) { |
| field_2_blipTypeMacOS = blipTypeMacOS; |
| } |
| |
| /** |
| * 16 byte MD4 checksum. |
| * |
| * @return 16 byte MD4 checksum |
| */ |
| public byte[] getUid() { |
| return field_3_uid; |
| } |
| |
| /** |
| * 16 byte MD4 checksum. |
| * |
| * @param uid 16 byte MD4 checksum |
| */ |
| public void setUid(byte[] uid) { |
| if (uid == null || uid.length != 16) { |
| throw new IllegalArgumentException("uid must be byte[16]"); |
| } |
| System.arraycopy(uid, 0, field_3_uid, 0, field_3_uid.length); |
| } |
| |
| /** |
| * unused |
| * |
| * @return an unknown tag |
| */ |
| public short getTag() { |
| return field_4_tag; |
| } |
| |
| /** |
| * unused |
| * |
| * @param tag unknown tag |
| */ |
| public void setTag(short tag) { |
| field_4_tag = tag; |
| } |
| |
| /** |
| * Blip size in stream. |
| * |
| * @return the blip size |
| */ |
| public int getSize() { |
| return field_5_size; |
| } |
| |
| /** |
| * Blip size in stream. |
| * |
| * @param size blip size |
| */ |
| public void setSize(int size) { |
| field_5_size = size; |
| } |
| |
| /** |
| * The reference count of this blip. |
| * |
| * @return the reference count |
| */ |
| public int getRef() { |
| return field_6_ref; |
| } |
| |
| /** |
| * The reference count of this blip. |
| * |
| * @param ref the reference count |
| */ |
| public void setRef(int ref) { |
| field_6_ref = ref; |
| } |
| |
| /** |
| * File offset in the delay stream. |
| * |
| * @return the file offset |
| */ |
| public int getOffset() { |
| return field_7_offset; |
| } |
| |
| /** |
| * File offset in the delay stream. |
| * |
| * @param offset the file offset |
| */ |
| public void setOffset(int offset) { |
| field_7_offset = offset; |
| } |
| |
| /** |
| * Defines the way this blip is used. |
| * |
| * @return the blip usage |
| */ |
| public byte getUsage() { |
| return field_8_usage; |
| } |
| |
| /** |
| * Defines the way this blip is used. |
| * |
| * @param usage the blip usae |
| */ |
| public void setUsage(byte usage) { |
| field_8_usage = usage; |
| } |
| |
| /** |
| * The length in characters of the blip name. |
| * |
| * @return the blip name length |
| */ |
| public byte getName() { |
| return field_9_name; |
| } |
| |
| /** |
| * The length in characters of the blip name. |
| * |
| * @param name the blip name length |
| */ |
| public void setName(byte name) { |
| field_9_name = name; |
| } |
| |
| public byte getUnused2() { |
| return field_10_unused2; |
| } |
| |
| public void setUnused2(byte unused2) { |
| field_10_unused2 = unused2; |
| } |
| |
| public byte getUnused3() { |
| return field_11_unused3; |
| } |
| |
| public void setUnused3(byte unused3) { |
| field_11_unused3 = unused3; |
| } |
| |
| public EscherBlipRecord getBlipRecord() { |
| return field_12_blipRecord; |
| } |
| |
| public void setBlipRecord(EscherBlipRecord blipRecord) { |
| field_12_blipRecord = blipRecord; |
| } |
| |
| /** |
| * Any remaining data in this record. |
| * |
| * @return the remaining bytes |
| */ |
| public byte[] getRemainingData() { |
| return _remainingData; |
| } |
| |
| /** |
| * Any remaining data in this record. |
| * |
| * @param remainingData the remaining bytes |
| */ |
| public void setRemainingData(byte[] remainingData) { |
| _remainingData = (remainingData == null) ? new byte[0] : remainingData.clone(); |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| final Map<String, Supplier<?>> m = new LinkedHashMap<>(super.getGenericProperties()); |
| m.put("blipTypeWin32", this::getBlipTypeWin32); |
| m.put("pictureTypeWin32", this::getPictureTypeWin32); |
| m.put("blipTypeMacOS", this::getBlipTypeMacOS); |
| m.put("pictureTypeMacOS", this::getPictureTypeMacOS); |
| m.put("suid", this::getUid); |
| m.put("tag", this::getTag); |
| m.put("size", this::getSize); |
| m.put("ref", this::getRef); |
| m.put("offset", this::getOffset); |
| m.put("usage", this::getUsage); |
| m.put("name", this::getName); |
| m.put("unused2", this::getUnused2); |
| m.put("unused3", this::getUnused3); |
| m.put("blipRecord", this::getBlipRecord); |
| m.put("remainingData", this::getRemainingData); |
| return Collections.unmodifiableMap(m); |
| } |
| |
| @Override |
| public Enum getGenericRecordType() { |
| return EscherRecordTypes.BSE; |
| } |
| |
| @Override |
| public EscherBSERecord copy() { |
| return new EscherBSERecord(this); |
| } |
| } |