Package er.extensions.components

Source Code of er.extensions.components.ERXMacBinarySwissArmyKnife

/*
* Copyright (C) NetStruxr, Inc. All rights reserved.
*
* This software is published under the terms of the NetStruxr
* Public Software License version 0.5, a copy of which has been
* included with this distribution in the LICENSE.NPL file.  */

/* MacBinarySwissArmyKnife.java created by travis on Wed 20-Sep-2000 */
package er.extensions.components;

import org.apache.log4j.Logger;

import com.webobjects.foundation.NSData;
import com.webobjects.foundation.NSRange;

/* This draws liberally from code by Gregory L. Guerin:

/*
** Copyright 1998, 1999 by Gregory L. Guerin.
** Terms of use:
**  - Brief terms: OPEN SOURCE... credit fairly, use freely, consider hiring me.
**  - Complete terms: <http://www.amug.org/~glguerin/sw/index.html>
*/

/**
* Useful for extracting files from binhexed files, ie when a Mac user uploads a file.<br />
*
*/

public class ERXMacBinarySwissArmyKnife {

    /////////////////////////////////////////  log4j category  //////////////////////////////////////
    public final static Logger log = Logger.getLogger(ERXMacBinarySwissArmyKnife.class);

    /**
    ** Offsets in header where the fields are located.
     */
    public static final int
        ZERO_1_AT = 0,
        NAME_LEN_AT = 1,
        NAME_BYTES_AT = 2,
        FILE_TYPE_AT = 65,
        FILE_CREATOR_AT = 69,
        FINDER_FLAGS1_AT = 73,
        ZERO_2_AT = 74,
        FINDER_VERT_AT = 75,
        FINDER_HORZ_AT = 77,
        FINDER_WINDOW_AT = 79,
        FLAG_LOCKED_AT = 81,
        ZERO_3_AT = 82,
        LEN_DATA_FORK_AT = 83,
        LEN_RES_FORK_AT = 87,
        WHEN_CREATED_AT = 91,
        WHEN_MODIFIED_AT = 95,
        LEN_COMMENT_AT = 99,
        FINDER_FLAGS2_AT = 101,
        MB3_SIGNATURE_AT = 102// 'mBIN'
        NAME_SCRIPT_AT = 106,
        FINDER_FLAGS3_AT = 107,    // 1-byte
        LEN_SECONDARY_AT = 120,
        VERSION_TARGET_AT = 122,
        VERSION_MIN_AT = 123,
        CRC_AT = 124,
        VERSION_OBSOLETE_AT = 126,
        _AT = 0;

    /**
        ** Possible values in VERSION_TARGET_AT and VERSION_MIN_AT byte-sized fields.
     */
    public static final int
        MB1_VERSION = 128,    // MacBinary I
        MB2_VERSION = 129,    // MacBinary II
        MB3_VERSION = 130;    // MacBinary III

    /**
        ** Distinctive int-sized value in MB3_SIGNATURE_AT field, only present
     ** for MB-3 format.
     */
    public static final int
        MB3_SIGNATURE = 0x6D42494E;    // 'mBIN'


    /**
        ** Bit-masks for different levels.
     ** The mask at 0 clears only the "do not restore" flags, but none of the "reserved" bits.
     ** "Reserved" bits are cleared to zero in the other masks -- you may not want
     */
    protected static final int[] levelMasks =
    {
        0x00FFFE7F,
        0x0000FC00,    // MacBinary-1
        0x0000FC4E,    // MacBinary-2
        0x0000FC4E,    // MacBinary-3
    };

    /**
        ** Length of a MacBinary header, always the first component of the file,
     ** and the only required element.  A MacBinary file must be at least this length.
     */
    public static final int MACBINARY_HEADER_LEN = 128;

    /**
        ** Enforce this limit on data-fork length, due either to Mac OS API or field-width.
     */
    public static final long LIMIT_DATAFORK = Integer.MAX_VALUE;

    /**
        ** Enforce this limit on resource-fork length, due either to Mac OS API or field-width.
     */
    public static final int LIMIT_RESFORK = (16 * 1024 * 1024) - 1;

    /**
        ** Enforce this limit on name-length, regardless of available space in header.
     */
    public static final int LIMIT_NAME = 31;

    private byte[] myBytes;
    private static final byte[] noBytes = new byte[ 0 ];

    protected static boolean strict = false;


    public ERXMacBinarySwissArmyKnife() {
        super();
    }

    public ERXMacBinarySwissArmyKnife(NSData fileData) {
        this();
        setByteArray(fileData.bytes(0,fileData.length()));
    }

    /**
        ** The array's index is the negative of the result-code.
     ** The result-code meanings are described at MacBinaryHeader.checkFormat().
     **
     ** @see MacBinaryHeader#checkFormat
     */
    private static String[] errorText =
    {
        "Zero-length name",
        "Invalid fork-lengths",
        "Non-zero byte at 74",
        "Non-zero bytes at 0 and/or 82"
    };

    /**
        ** Return a brief text String explaining a format or result-code.
     */
    private static String formatExplained( int format ) {
        if ( format > 0 )
            return ( "MacBinary-" + format );
        return ( errorText[ -format ] );
    }

    public boolean isMacBinary(NSData fileData) {
        try {
            // Though we are lax in accepting the file-header, we are strict in displaying
            // the format-number.  This lets us spy into files to discover their true internal form.

            int dataLength;
            if (fileData.length() >= MACBINARY_HEADER_LEN) {
                dataLength = MACBINARY_HEADER_LEN;
            } else {
                //System.out.println("The data is too short to be a MacBinary.  Only received " + fileData.length() + "bytes.");
                return false;
            }

            // Feed bytes into a MacBinaryHeader and see what turns up...
            myBytes = fileData.bytes(0,dataLength);

            int fileFormat = checkFormat( strict );

            if ( fileFormat <= 0 ) {
                // System.out.println( "  ## Not MacBinary: " + formatExplained( fileFormat ) );
                return false;
            }

        } catch ( Throwable why ){
            // System.out.println( "### Problem with file: " );
            // why.printStackTrace( System.out );
            return false;
        }

        return true;
    }

    public NSData unwrapMacBinary(NSData fileContents) {
        int dataForkLength = getDataForkLength();
        return  fileContents.subdataWithRange( new NSRange( MACBINARY_HEADER_LEN,dataForkLength ) );
    }


    public int checkFormat( boolean strictFormat ) {
        // Check for absolute minimal conformance to MacBinary header form...
        int a = getUByteAt( ZERO_1_AT );
        int b = getUByteAt( ZERO_3_AT );
        if ( a != ||  b != 0 )
            return ( -3 );

        // Check for more strict conformance to MacBinary header form...
        if ( strictFormat  &&  getUByteAt( ZERO_2_AT ) != 0   )
            return ( -2 );

        // Check for "safe" values known to exist in all MacBinary headers.
        // This is only done after the minimal conformance checks above.
        a = getDataForkLength();
        b = getResourceForkLength();
        if ( a < ||  a > LIMIT_DATAFORK  ||  b < ||  b > LIMIT_RESFORK )
            return ( -1 );

        // Check for usable name-len... Note that this check makes names with
        // length [32-63] illegal, even though they are legal on non-HFS (400K) diskettes.
        // This seemed safe enough, considering that names of that actual length
        // are very unlikely to appear in anything of recent vintage.
        // If need to decode something that old, change it here.
        a = getUByteAt( NAME_LEN_AT );
        if ( a < ||  a > LIMIT_NAME )
            return ( 0 );

        // Getting here, it's now known to be at least MacBinary I format.

        // Check for MacBinary II features...
        if ( ! isValidCRC()  ||  getUByteAt( VERSION_MIN_AT ) < MB2_VERSION )
            return ( 1 );

        // now known to be at least MacBinary II format.

        // Check for MacBinary III features...
        a = getIntAt( MB3_SIGNATURE_AT );
        if ( a != MB3_SIGNATURE )
            return ( 2 );

        // now known to be at least MacBinary III format.

        return ( 3 );

    }

    /**
        ** Return the signed byte at the given offset.
     */
    public byte getByteAt( int offset ) { return ( myBytes[ offset ] ); }

    /**
        ** Return an int holding the unsigned byte at the given offset.
     */
    public int
        getUByteAt( int offset )
    {
            return ( 0x00FF & myBytes[ offset ] );
    }

    /**
        ** Return the signed short at the given offset.
     */
    public short
        getShortAt( int offset )
    {
            return ( (short) ( (myBytes[ offset ] << 8) + (0x00FF & myBytes[ offset + 1 ]) ) );
    }

    /**
        ** Return an int holding the unsigned short at the given offset.
     */
    public int
        getUShortAt( int offset )
    {
            return ( (0xFF00 & (myBytes[ offset ] << 8)) + (0x00FF & myBytes[ offset + 1 ]) );
    }

    /**
        ** Return the signed int at the given offset.
     */
    public int
        getIntAt( int offset )
    {
            int value = (0x0FF & myBytes[ offset ]) << 24;
            value += (0x0FF & myBytes[ offset + 1 ]) << 16;
            value += (0x0FF & myBytes[ offset + 2 ]) << 8;
            return ( value + (0x0FF & myBytes[ offset + 3 ]) );
    }

    /**
        ** Return a long holding the unsigned int at the given offset.
     */
    public long
        getUIntAt( int offset )
    {
            long value = 0xFFFFFFFFL & getIntAt( offset );
            return ( value );
    }

    /**
        ** Return the signed long at the given offset.
     */
    public long
        getLongAt( int offset )
    {
            long value = getIntAt( offset );
            return ( (value << 32) + getUIntAt( offset + 4 ) );
    }



    /**
        ** Put the given byte at the supplied offset.
     */
    public void
        putByteAt( byte value, int offset )
    {
            myBytes[ offset ] = value;
    }

    /**
        ** Put all the given bytes at the supplied offset.
     */
    public void
        putBytesAt( byte[] data, int offset )
    {
            System.arraycopy( data, 0, myBytes, offset, data.length );
    }

    /**
        ** Put the given bytes at the supplied offset.
     */
    public void
        putBytesAt( byte[] data, int offset, int count )
    {
            System.arraycopy( data, 0, myBytes, offset, count );
    }

    /**
        ** Put the given short at the supplied offset.
     */
    public void
        putShortAt( short value, int offset )
    {
            myBytes[ offset ] = (byte) (value >> 8);
            myBytes[ offset + 1 ] = (byte) (value);
    }

    /**
        ** Put the given int at the supplied offset.
     */
    public void
        putIntAt( int value, int offset )
    {
            myBytes[ offset ] = (byte) (value >> 24);
            myBytes[ offset + 1 ] = (byte) (value >> 16);
            myBytes[ offset + 2 ] = (byte) (value >> 8);
            myBytes[ offset + 3 ] = (byte) (value);
    }

    /**
        ** Put the given long at the supplied offset.
     */
    public void
        putLongAt( long value, int offset )
    {
            putIntAt( (int) (value >> 32), offset );
            putIntAt( (int) (value), offset + 4 );
    }



    /**
        ** Get the internal byte-array.
     */
    public final byte[] getByteArray() {  return ( myBytes )}

    /**
        ** Use the given array to hold bytes.
     ** Accepts null and/or zero-length arrays without error,
     ** though a subsequent get or put will throw an exception.
     */
    public final void setByteArray( byte[] bytes ) {  myBytes = bytes;  }

    /**
        ** Get the data-fork length from the header.
     */
    public int getDataForkLength() {  return ( getIntAt( LEN_DATA_FORK_AT ) )}

    /**
        ** Get the resource-fork length from the header.
     */
    public int getResourceForkLength() { return ( getIntAt( LEN_RES_FORK_AT ) ); }

    // ###  C R C  ###

    /**
        ** Calculate a MacBinary CRC using the given starting seed, and proceeding over the given
     ** range of bytes.  The returned CRC value is a 16-bit result in an int, because it's unsigned.
     */
    public static int calculateCRC( int seed, byte[] bytes, int offset, int count ) {
        for ( count += offset;  offset < count;  ++offset ) {
            seed ^= (0xFF & bytes[ offset ]) << 8;
            seed = (seed << 8) ^ crcTable[ 0xFF & (seed >> 8) ];
        }
        return ( 0xFFFF & seed );
    }


    /**
        ** Conveniently, UniCode chars are unsigned 16-bit values.
     ** Equally convenient, it's very easy to create constant String objects,
     ** then retrieve individual chars from them later.
     ** In particular, the String-constant rendering of the code consumes vastly fewer byte-codes
     ** than an ordinary array initializer would.  And we still get a char[] out of it.
     */
    private static final char[] crcTable =
        (
         "\u0000\u1021\u2042\u3063\u4084\u50a5\u60c6\u70e7" +
         "\u8108\u9129\ua14a\ub16b\uc18c\ud1ad\ue1ce\uf1ef" +
         "\u1231\u0210\u3273\u2252\u52b5\u4294\u72f7\u62d6" +
         "\u9339\u8318\ub37b\ua35a\ud3bd\uc39c\uf3ff\ue3de" +
         "\u2462\u3443\u0420\u1401\u64e6\u74c7\u44a4\u5485" +
         "\ua56a\ub54b\u8528\u9509\ue5ee\uf5cf\uc5ac\ud58d" +
         "\u3653\u2672\u1611\u0630\u76d7\u66f6\u5695\u46b4" +
         "\ub75b\ua77a\u9719\u8738\uf7df\ue7fe\ud79d\uc7bc" +
         "\u48c4\u58e5\u6886\u78a7\u0840\u1861\u2802\u3823" +
         "\uc9cc\ud9ed\ue98e\uf9af\u8948\u9969\ua90a\ub92b" +
         "\u5af5\u4ad4\u7ab7\u6a96\u1a71\u0a50\u3a33\u2a12" +
         "\udbfd\ucbdc\ufbbf\ueb9e\u9b79\u8b58\ubb3b\uab1a" +
         "\u6ca6\u7c87\u4ce4\u5cc5\u2c22\u3c03\u0c60\u1c41" +
         "\uedae\ufd8f\ucdec\uddcd\uad2a\ubd0b\u8d68\u9d49" +
         "\u7e97\u6eb6\u5ed5\u4ef4\u3e13\u2e32\u1e51\u0e70" +
         "\uff9f\uefbe\udfdd\ucffc\ubf1b\uaf3a\u9f59\u8f78" +
         "\u9188\u81a9\ub1ca\ua1eb\ud10c\uc12d\uf14e\ue16f" +
         "\u1080\u00a1\u30c2\u20e3\u5004\u4025\u7046\u6067" +
         "\u83b9\u9398\ua3fb\ub3da\uc33d\ud31c\ue37f\uf35e" +
         "\u02b1\u1290\u22f3\u32d2\u4235\u5214\u6277\u7256" +
         "\ub5ea\ua5cb\u95a8\u8589\uf56e\ue54f\ud52c\uc50d" +
         "\u34e2\u24c3\u14a0\u0481\u7466\u6447\u5424\u4405" +
         "\ua7db\ub7fa\u8799\u97b8\ue75f\uf77e\uc71d\ud73c" +
         "\u26d3\u36f2\u0691\u16b0\u6657\u7676\u4615\u5634" +
         "\ud94c\uc96d\uf90e\ue92f\u99c8\u89e9\ub98a\ua9ab" +
         "\u5844\u4865\u7806\u6827\u18c0\u08e1\u3882\u28a3" +
         "\ucb7d\udb5c\ueb3f\ufb1e\u8bf9\u9bd8\uabbb\ubb9a" +
         "\u4a75\u5a54\u6a37\u7a16\u0af1\u1ad0\u2ab3\u3a92" +
         "\ufd2e\ued0f\udd6c\ucd4d\ubdaa\uad8b\u9de8\u8dc9" +
         "\u7c26\u6c07\u5c64\u4c45\u3ca2\u2c83\u1ce0\u0cc1" +
         "\uef1f\uff3e\ucf5d\udf7c\uaf9b\ubfba\u8fd9\u9ff8" +
         "\u6e17\u7e36\u4e55\u5e74\u2e93\u3eb2\u0ed1\u1ef0"
         ).toCharArray();


    /**
        ** Calculate and set the CRC over the header bytes previously set,
     ** invoking calcCRC() to calculate the 16-bit value to store.
     ** You should only invoke this after setting every other field of interest,
     ** such as name and name-encoding, data-fork len, res-fork len, secondary-header len, etc.
     ** Since the CRC is calculated internally, there is no parameter to this method,
     ** even though it's a "setter" method.
     **<p>
     ** In most cases, you will find finishHeader() more useful since it finishes
     ** all the assembly for a particular header-format you select.
     **
     */
    public void setCRC() {  putShortAt( (short) calcCRC(), CRC_AT )}

    /**
        ** Calculate and check the header CRC.
     */
    public boolean isValidCRC() {  return ( calcCRC() == getCRC() )}

    /**
        ** Calculate and return a CRC over the header.
     ** The CRC value is in the low 16-bits of the returned int.
     */
    public int calcCRC() {  return ( calculateCRC( 0, getByteArray(), 0, CRC_AT ) )}

    /**
        ** Calculate and return an alternative header CRC.
     ** Where calcCRC() calculates over the first 124 bytes of the header,
     ** this calculates over the first 126 bytes of the header.
     ** I'm guessing that with a valid CRC-value in the header, the returned value
     ** is zero, but I don't know enough about CRC theory and practice to assert that confidently.
     ** This is more of a Greg's-hacky-toy than anything that might actually be useful.
     */
    public int calcCRC2() { return ( calculateCRC( 0, getByteArray(), 0, CRC_AT + 2 ) ); }

    /**
        ** Return the header CRC, as embedded in the header itself.
     */
    public int getCRC() { return ( getUShortAt( CRC_AT ) ); }
}
TOP

Related Classes of er.extensions.components.ERXMacBinarySwissArmyKnife

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.