123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- /* ====================================================================
- 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.hpsf;
-
- import java.io.IOException;
- import java.io.OutputStream;
- import java.io.UnsupportedEncodingException;
- import java.math.BigInteger;
- import java.util.Date;
- import java.util.LinkedList;
- import java.util.List;
-
- import org.apache.logging.log4j.LogManager;
- import org.apache.logging.log4j.Logger;
- import org.apache.poi.util.IOUtils;
- import org.apache.poi.util.LittleEndian;
- import org.apache.poi.util.LittleEndianByteArrayInputStream;
- import org.apache.poi.util.LittleEndianConsts;
-
- /**
- * Supports reading and writing of variant data.<p>
- *
- * <strong>FIXME (3):</strong> Reading and writing should be made more
- * uniform than it is now. The following items should be resolved:
- *
- * <ul>
- *
- * <li>Reading requires a length parameter that is 4 byte greater than the
- * actual data, because the variant type field is included.
- *
- * <li>Reading reads from a byte array while writing writes to an byte array
- * output stream.
- *
- * </ul>
- */
- public class VariantSupport extends Variant {
- /**
- * HPSF is able to read these {@link Variant} types.
- */
- public static final int[] SUPPORTED_TYPES = { Variant.VT_EMPTY,
- Variant.VT_I2, Variant.VT_I4, Variant.VT_I8, Variant.VT_R8,
- Variant.VT_FILETIME, Variant.VT_LPSTR, Variant.VT_LPWSTR,
- Variant.VT_CF, Variant.VT_BOOL };
-
-
- private static final Logger LOG = LogManager.getLogger(VariantSupport.class);
-
- private static boolean logUnsupportedTypes;
-
- /**
- * Keeps a list of the variant types an "unsupported" message has already
- * been issued for.
- */
- private static List<Long> unsupportedMessage;
-
- private static final byte[] paddingBytes = new byte[3];
-
-
- /**
- * Specifies whether warnings about unsupported variant types are to be
- * written to {@code System.err} or not.
- *
- * @param logUnsupportedTypes If {@code true} warnings will be written,
- * if {@code false} they won't.
- */
- public static void setLogUnsupportedTypes(final boolean logUnsupportedTypes) {
- VariantSupport.logUnsupportedTypes = logUnsupportedTypes;
- }
-
- /**
- * Checks whether logging of unsupported variant types warning is turned
- * on or off.
- *
- * @return {@code true} if logging is turned on, else
- * {@code false}.
- */
- public static boolean isLogUnsupportedTypes() {
- return logUnsupportedTypes;
- }
-
-
-
- /**
- * Writes a warning to {@code System.err} that a variant type is
- * unsupported by HPSF. Such a warning is written only once for each variant
- * type. Log messages can be turned on or off by
- *
- * @param ex The exception to log
- */
- protected static void writeUnsupportedTypeMessage
- (final UnsupportedVariantTypeException ex) {
- if (isLogUnsupportedTypes())
- {
- if (unsupportedMessage == null) {
- unsupportedMessage = new LinkedList<>();
- }
- Long vt = Long.valueOf(ex.getVariantType());
- if (!unsupportedMessage.contains(vt))
- {
- LOG.atError().withThrowable(ex).log("Unsupported type");
- unsupportedMessage.add(vt);
- }
- }
- }
-
-
-
- /**
- * Checks whether HPSF supports the specified variant type. Unsupported
- * types should be implemented included in the {@link #SUPPORTED_TYPES}
- * array.
- *
- * @see Variant
- * @param variantType the variant type to check
- * @return {@code true} if HPFS supports this type, else
- * {@code false}
- */
- public boolean isSupportedType(final int variantType) {
- for (int st : SUPPORTED_TYPES) {
- if (variantType == st) {
- return true;
- }
- }
- return false;
- }
-
-
-
- /**
- * Reads a variant type from a byte array.
- *
- * @param src The byte array
- * @param offset The offset in the byte array where the variant starts
- * @param length The length of the variant including the variant type field
- * @param type The variant type to read
- * @param codepage The codepage to use for non-wide strings
- * @return A Java object that corresponds best to the variant field. For
- * example, a VT_I4 is returned as a {@link Long}, a VT_LPSTR as a
- * {@link String}.
- * @throws ReadingNotSupportedException if a property is to be written
- * who's variant type HPSF does not yet support
- * @throws UnsupportedEncodingException if the specified codepage is not
- * supported.
- * @see Variant
- */
- public static Object read( final byte[] src, final int offset,
- final int length, final long type, final int codepage )
- throws ReadingNotSupportedException, UnsupportedEncodingException {
- LittleEndianByteArrayInputStream lei = new LittleEndianByteArrayInputStream(src, offset);
- return read( lei, length, type, codepage );
- }
-
- public static Object read( LittleEndianByteArrayInputStream lei,
- final int length, final long type, final int codepage )
- throws ReadingNotSupportedException, UnsupportedEncodingException {
- final int offset = lei.getReadIndex();
- TypedPropertyValue typedPropertyValue = new TypedPropertyValue( (int) type, null );
- try {
- typedPropertyValue.readValue(lei);
- } catch ( UnsupportedOperationException exc ) {
- try {
- final byte[] v = IOUtils.toByteArray(lei, length, CodePageString.getMaxRecordLength());
- throw new ReadingNotSupportedException( type, v );
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- switch ( (int) type ) {
- /*
- * we have more property types that can be converted into Java
- * objects, but current API need to be preserved, and it returns
- * other types as byte arrays. In future major versions it shall be
- * changed -- sergey
- */
- case Variant.VT_EMPTY:
- case Variant.VT_I1:
- case Variant.VT_UI1:
- case Variant.VT_UI2:
- case Variant.VT_I4:
- case Variant.VT_UI4:
- case Variant.VT_I8:
- case Variant.VT_UI8:
- case Variant.VT_R4:
- case Variant.VT_R8:
- return typedPropertyValue.getValue();
-
- /*
- * also for backward-compatibility with prev. versions of POI
- * --sergey
- */
- case Variant.VT_I2:
- return ( (Short) typedPropertyValue.getValue() ).intValue();
-
- case Variant.VT_FILETIME:
- Filetime filetime = (Filetime) typedPropertyValue.getValue();
- return filetime.getJavaValue();
-
- case Variant.VT_LPSTR:
- CodePageString cpString = (CodePageString) typedPropertyValue.getValue();
- return cpString.getJavaValue( codepage );
-
- case Variant.VT_LPWSTR:
- UnicodeString uniString = (UnicodeString) typedPropertyValue.getValue();
- return uniString.toJavaString();
-
- // if(l1 < 0) {
- /*
- * YK: reading the ClipboardData packet (VT_CF) is not quite
- * correct. The size of the data is determined by the first four
- * bytes of the packet while the current implementation calculates
- * it in the Section constructor. Test files in Bugzilla 42726 and
- * 45583 clearly show that this approach does not always work. The
- * workaround below attempts to gracefully handle such cases instead
- * of throwing exceptions.
- *
- * August 20, 2009
- */
- // l1 = LittleEndian.getInt(src, o1); o1 += LittleEndianConts.INT_SIZE;
- // }
- // final byte[] v = new byte[l1];
- // System.arraycopy(src, o1, v, 0, v.length);
- // value = v;
- // break;
- case Variant.VT_CF:
- ClipboardData clipboardData = (ClipboardData) typedPropertyValue.getValue();
- return clipboardData.toByteArray();
-
- case Variant.VT_BOOL:
- VariantBool bool = (VariantBool) typedPropertyValue.getValue();
- return bool.getValue();
-
- /*
- * it is not very good, but what can do without breaking current
- * API? --sergey
- */
- default:
- final int unpadded = lei.getReadIndex()-offset;
- lei.setReadIndex(offset);
- final byte[] v = IOUtils.safelyAllocate(unpadded, CodePageString.getMaxRecordLength());
- lei.readFully( v, 0, unpadded );
- throw new ReadingNotSupportedException( type, v );
- }
- }
-
- /**
- * Writes a variant value to an output stream. This method ensures that
- * always a multiple of 4 bytes is written.
- *
- * @param out The stream to write the value to.
- * @param type The variant's type.
- * @param value The variant's value.
- * @param codepage The codepage to use to write non-wide strings
- * @return The number of entities that have been written. In many cases an
- * "entity" is a byte but this is not always the case.
- * @throws IOException if an I/O exceptions occurs
- * @throws WritingNotSupportedException if a property is to be written
- * who's variant type HPSF does not yet support
- */
- public static int write(final OutputStream out, final long type,
- final Object value, final int codepage)
- throws IOException, WritingNotSupportedException {
- int length = -1;
- switch ((int) type) {
- case Variant.VT_BOOL: {
- if (value instanceof Boolean) {
- int bb = ((Boolean)value) ? 0xff : 0x00;
- out.write(bb);
- out.write(bb);
- length = 2;
- }
- break;
- }
-
- case Variant.VT_LPSTR:
- if (value instanceof String) {
- CodePageString codePageString = new CodePageString();
- codePageString.setJavaValue( (String)value, codepage );
- length = codePageString.write( out );
- }
- break;
-
- case Variant.VT_LPWSTR:
- if (value instanceof String) {
- UnicodeString uniString = new UnicodeString();
- uniString.setJavaValue((String)value);
- length = uniString.write(out);
- }
- break;
-
- case Variant.VT_CF:
- if (value instanceof byte[]) {
- final byte[] cf = (byte[]) value;
- out.write(cf);
- length = cf.length;
- }
- break;
-
- case Variant.VT_EMPTY:
- LittleEndian.putUInt(Variant.VT_EMPTY, out);
- length = LittleEndianConsts.INT_SIZE;
- break;
-
- case Variant.VT_I2:
- if (value instanceof Number) {
- LittleEndian.putShort( out, ((Number)value).shortValue() );
- length = LittleEndianConsts.SHORT_SIZE;
- }
- break;
-
- case Variant.VT_UI2:
- if (value instanceof Number) {
- LittleEndian.putUShort( ((Number)value).intValue(), out );
- length = LittleEndianConsts.SHORT_SIZE;
- }
- break;
-
- case Variant.VT_I4:
- if (value instanceof Number) {
- LittleEndian.putInt( ((Number)value).intValue(), out);
- length = LittleEndianConsts.INT_SIZE;
- }
- break;
-
- case Variant.VT_UI4:
- if (value instanceof Number) {
- LittleEndian.putUInt( ((Number)value).longValue(), out);
- length = LittleEndianConsts.INT_SIZE;
- }
- break;
-
- case Variant.VT_I8:
- if (value instanceof Number) {
- LittleEndian.putLong( ((Number)value).longValue(), out);
- length = LittleEndianConsts.LONG_SIZE;
- }
- break;
-
- case Variant.VT_UI8: {
- if (value instanceof Number) {
- BigInteger bi = (value instanceof BigInteger) ? (BigInteger)value : BigInteger.valueOf(((Number)value).longValue());
- if (bi.bitLength() > 64) {
- throw new WritingNotSupportedException(type, value);
- }
-
- byte[] biBytesBE = bi.toByteArray(), biBytesLE = new byte[LittleEndianConsts.LONG_SIZE];
- int i=biBytesBE.length;
- for (byte b : biBytesBE) {
- if (i<=LittleEndianConsts.LONG_SIZE) {
- biBytesLE[i-1] = b;
- }
- i--;
- }
-
- out.write(biBytesLE);
- length = LittleEndianConsts.LONG_SIZE;
- }
- break;
- }
-
- case Variant.VT_R4: {
- if (value instanceof Number) {
- int floatBits = Float.floatToIntBits(((Number)value).floatValue());
- LittleEndian.putInt(floatBits, out);
- length = LittleEndianConsts.INT_SIZE;
- }
- break;
- }
-
- case Variant.VT_R8:
- if (value instanceof Number) {
- LittleEndian.putDouble( ((Number)value).doubleValue(), out);
- length = LittleEndianConsts.DOUBLE_SIZE;
- }
- break;
-
- case Variant.VT_FILETIME:
- Filetime filetimeValue = (value instanceof Date) ? new Filetime((Date)value) : new Filetime();
- length = filetimeValue.write( out );
- break;
-
- default:
- break;
- }
-
- /* The variant type is not supported yet. However, if the value
- * is a byte array we can write it nevertheless. */
- if (length == -1) {
- if (value instanceof byte[]) {
- final byte[] b = (byte[]) value;
- out.write(b);
- length = b.length;
- writeUnsupportedTypeMessage(new WritingNotSupportedException(type, value));
- } else {
- throw new WritingNotSupportedException(type, value);
- }
- }
-
- /* pad values to 4-bytes */
- int padding = (4-(length & 0x3)) & 0x3;
- out.write(paddingBytes, 0, padding);
-
- return length + padding;
- }
- }
|