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

org.digidoc4j.ddoc.DataFile Maven / Gradle / Ivy

Go to download

DDoc4J is Java Library for validating DDOC documents. It's not recommended to use it directly but rather through DigiDoc4J's API.

The newest version!
package org.digidoc4j.ddoc;

import org.digidoc4j.ddoc.factory.CanonicalizationFactory;
import org.digidoc4j.ddoc.utils.ConfigManager;
import org.digidoc4j.ddoc.utils.ConvertUtils;
import org.apache.commons.codec.binary.Base64InputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Date;

/**
 * Represents a DataFile instance, that either
 * contains payload data or references and external
 * DataFile.
 * @author  Veiko Sinivee
 * @version 1.0
 */
public class DataFile implements Serializable
{
    private static final long serialVersionUID = 1L;
    /** content type of the DataFile */
    private String m_contentType;
    /** filename */
    private String m_fileName;
    /** Id attribute of this DataFile */
    private String m_id;
    /** mime type of the file */
    private String m_mimeType;
    /** container comment (bdoc2 lib ver and name) */
    private String m_comment;
    /** file size on bytes */
    private long m_size;
    /** digest value of detatched file */
    private byte[] m_digestSha1;
    /** alternative (sha1) digest if requested */
    private byte[] m_digestAlt;

    /** digest value of the XML form of 
     * If read from XML file then calculated immediately
     * otherwise on demand
     */
    private byte[] m_origDigestValue;
    /** additional attributes */
    private ArrayList m_attributes;
    /** data file contents in original form */
    private byte[] m_body;
    /** initial codepage of DataFile data */
    private String m_codepage;
    /** parent object reference */
    private SignedDoc m_sigDoc;

    /** allowed values for content type */
    public static final String CONTENT_EMBEDDED_BASE64 = "EMBEDDED_BASE64";
    public static final String CONTENT_BINARY = "BINARY";
    public static final String CONTENT_HASHCODE = "HASHCODE";

    /** the only allowed value for digest type */
    public static final String DIGEST_TYPE_SHA1 = "sha1";
    private static int block_size = 2048;
    /** log4j logger */
    private static Logger m_logger = LoggerFactory.getLogger(DataFile.class);
    /** temp file used to cache DataFile data if caching is enabled */
    private transient File m_fDfCache = null;
    private boolean m_bodyIsBase64;
    /** original input file last modified timestamp */
    private Date m_lModDt = null;


    /**
     * Creates new DataFile
     * @param id id of the DataFile
     * @param contenType DataFile content type
     * @param fileName original file name (without path!)
     * @param mimeType contents mime type
     * @param sdoc parent object
     * @throws DigiDocException for validation errors
     */
    public DataFile(String id, String contentType, String fileName, String mimeType, SignedDoc sdoc)
            throws DigiDocException
    {
        m_sigDoc = sdoc;
        setId(id);
        setContentType(contentType);
        setFileName(fileName);
        setMimeType(mimeType);
        m_size = 0;
        m_digestSha1 = null;
        m_attributes = null;
        m_body = null;
        m_codepage = "UTF-8";
        m_origDigestValue = null;
        m_fDfCache = null;
        m_bodyIsBase64 = false;
        m_comment = null;
    }

    /**
     * Accessor for temp file object used to cache DataFile data
     * if caching is enabled.
     * @return temp file object used to cache DataFile data
     */
    public File getDfCacheFile() {
        return m_fDfCache;
    }

    /**
     * Removes temporary DataFile cache file
     */
    public void cleanupDfCache() {
        if(m_fDfCache != null) {
            if(m_logger.isDebugEnabled())
                m_logger.debug("Removing cache file for df: " + m_fDfCache.getAbsolutePath());
            m_fDfCache.delete();
        }
        m_fDfCache = null;
    }

    /**
     * Accessor for body attribute.
     * Note that the body is normally NOT LOADED
     * from file and this attribute is empty!
     * @return value of body attribute
     */
    public byte[] getBody()
            throws DigiDocException
    {
        if(m_fDfCache != null) {
            try {
                byte[] data = SignedDoc.readFile(m_fDfCache);
                if(m_contentType.equals(CONTENT_EMBEDDED_BASE64))
                    data = Base64Util.decode(data);
                return data;
            } catch(Exception ex) {
                DigiDocException.handleException(ex, DigiDocException.ERR_READ_FILE);
            }
        }
        return m_body;
    }

    /**
     * Mutator for body attribute. For
     * any bigger files don't use this method!
     * If you are using very small messages onthe other hand
     * then this might speed things up.
     * This method should not be publicly used to assign
     * data to body. If you do then you must also set the
     * initial codepage and size of body!
     * @param data new value for body attribute
     */
    public void setBody(byte[] data)
            throws DigiDocException
    {
        try {
            m_body = data;
            if(data != null) {
                m_size = data.length;
                storeInTempFile();
                if(m_contentType != null) {
                    if(m_contentType.equals(CONTENT_BINARY)) { // BDOC
                        if(!isDigestsCalculated()) {
                            if(m_body != null) // small amount of data in mem
                                calcHashes(new ByteArrayInputStream(m_body));
                            else if(m_fDfCache != null) // big amount of data moved to cache file
                                calcHashes(new FileInputStream(m_fDfCache));
                        }
                        if(m_mimeType != null) {
                            String sFile = m_fileName;
                            if(sFile != null && sFile.indexOf('/') != -1 || sFile.indexOf('\\') != -1) {
                                File fT = new File(sFile);
                                sFile = fT.getName();
                            }
                            if(m_sigDoc.findManifestEntryByPath(sFile) == null) {
                                ManifestFileEntry fe = new ManifestFileEntry(m_mimeType, sFile);
                                m_sigDoc.getManifest().addFileEntry(fe);
                            }
                        }
                    }
                    if(m_contentType.equals(CONTENT_EMBEDDED_BASE64)) { // DDOC
                        if(!isDigestsCalculated()) {
                            m_size = data.length;
                            m_body = Base64Util.encode(data).getBytes();
                            m_bodyIsBase64 = true;
                        }
                    }
                }
            }
        } catch(IOException ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_WRITE_FILE);
        }
    }

    public void setBase64Body(byte[] data)
    {
        if(data != null) {
            m_size = data.length;
            m_body = Base64Util.encode(data).getBytes();
            m_bodyIsBase64 = true;
        }
    }

    public void setBodyAsData(byte[] data, boolean b64, long len)
    {
        if(data != null) {
            m_size = len;
            m_body = data;
            m_bodyIsBase64 = b64;
        }
    }

    /**
     * Returnes true if body is already converted to base64
     * @return true if body is already converted to base64
     */
    public boolean getBodyIsBase64() { return m_bodyIsBase64; }

    /**
     * Set flag to indicate that body is already converted to base64
     * @param b flag to indicate that body is already converted to base64
     */
    public void setBodyIsBase64(boolean b) { m_bodyIsBase64 = b; }

    /**
     * Returnes content file last modified timestamp
     * @return last modified timestamp
     */
    public Date getLastModDt() { return m_lModDt; }

    /**
     * Set content file last modified timestamp
     * @param d last modified timestamp
     */
    public void setLastModDt(Date d) { m_lModDt = d; }




    /**
     * Sets DataFile contents from an input stream.
     * This method allways uses temporary files to read out
     * the input stream first in order to determine the
     * size of data. Caller can close the stream after
     * invoking this method because data has been copied.
     * Data is not yet converted to base64 (if required)
     * nor is the hash code calculated at this point.
     * Please not that data is stored in original binary format,
     * so getBody() etc. will not deliver correct result
     * until digidoc has been actually written to disk and read
     * in again.
     * @param is input stream delivering the data
     */
    public void setBodyFromStream(InputStream is)
            throws DigiDocException
    {
        if(is == null) return;
        // copy data to temp file
        try {
            File fCacheDir = new File(ConfigManager.instance().
                    getStringProperty("DIGIDOC_DF_CACHE_DIR", System.getProperty("java.io.tmpdir")));
            String dfId = new Long(System.currentTimeMillis()).toString();
            m_fDfCache = File.createTempFile(dfId, ".df", fCacheDir);
            FileOutputStream fos = new FileOutputStream(m_fDfCache);
            m_body = null;
            byte[] data = new byte[2048];
            int nRead = 0;
            m_size = 0;
            do {
                nRead = is.read(data);
                if(nRead > 0) {
                    fos.write(data, 0, nRead);
                    m_size += nRead;
                }
            } while(nRead > 0);
            fos.close();
            if(m_logger.isDebugEnabled())
                m_logger.debug("DF: " + m_id + " size: " + m_size + " cache-file: " + m_fDfCache.getAbsolutePath());
        } catch(IOException ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_WRITE_FILE);
        }
    }

    public boolean isDigestsCalculated()
    {
        return (m_digestSha1 != null);
    }

    /**
     * Calculate size and digests
     * @param is data input stream
     * @param os optional output stream to write read data to (cache file)
     */
    private void calcHashesAndWriteToStream(InputStream is, OutputStream os)
            throws DigiDocException
    {
        try {
            MessageDigest sha1 = MessageDigest.getInstance(SignedDoc.SHA1_DIGEST_TYPE);
            byte[] data = new byte[2048];
            int nRead = 0;
            m_size = 0;
            do {
                nRead = is.read(data);
                if(nRead > 0) {
                    sha1.update(data, 0, nRead);
                    if(os != null)
                        os.write(data, 0, nRead);
                    m_size += nRead;
                }
            } while(nRead > 0);
            m_digestSha1 = m_origDigestValue = sha1.digest();
            if(m_logger.isDebugEnabled())
                m_logger.debug("DF: " + m_id + " size: " + m_size +
                        " dig-sha1: " + Base64Util.encode(m_digestSha1));
        } catch(Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_READ_FILE);
        }
    }

    /**
     * Calculate size and digests
     * @param is data input stream
     */
    public void calcHashes(InputStream is)
            throws DigiDocException
    {
        calcHashesAndWriteToStream(is, null);
    }

    /**
     * Calculate data file hash based on digest type and container type
     * @param digType digest type
     */
    private byte[] calcHashOfType(String digType)
            throws DigiDocException
    {
        byte[] dig = null;
        InputStream is = null;
        try {
            if(digType == null || !digType.equals(SignedDoc.SHA1_DIGEST_TYPE)) {
                throw new DigiDocException(DigiDocException.ERR_DIGEST_ALGORITHM, "Invalid digest type: " + digType, null);
            }
            if(m_sigDoc.getFormat().equals(SignedDoc.FORMAT_SK_XML) ||
                    m_sigDoc.getFormat().equals(SignedDoc.FORMAT_DIGIDOC_XML)) {
                return getDigest();
            }
            MessageDigest sha = MessageDigest.getInstance(digType);
            byte[] data = new byte[2048];
            int nRead = 0, nTotal = 0;
            is = getBodyAsStream();
            do {
                nRead = is.read(data);
                if(nRead > 0) {
                    sha.update(data, 0, nRead);
                    nTotal += nRead;
                }
            } while(nRead > 0);
            dig = sha.digest();
            if(m_logger.isDebugEnabled())
                m_logger.debug("DF: " + m_id + " size: " + nTotal + " digest: " + digType + " = " + Base64Util.encode(dig));
        } catch(Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_READ_FILE);
        } finally {
            try {
                if(is != null)
                    is.close();
            } catch(Exception ex) {
                m_logger.error("Error closing stream: " + ex);
            }
        }
        return dig;
    }

    /**
     * Set datafile cached content or cache file, calculate size and digest
     * @param is data input stream
     */
    public void setOrCacheBodyAndCalcHashes(InputStream is)
            throws DigiDocException
    {
        OutputStream os = null;
        try {
            m_fDfCache = createCacheFile();
            if(m_fDfCache != null)
                os = new FileOutputStream(m_fDfCache);
            else
                os = new ByteArrayOutputStream();
            calcHashesAndWriteToStream(is, os);
            if(m_fDfCache == null)
                m_body = ((ByteArrayOutputStream)os).toByteArray();
            if(m_logger.isDebugEnabled())
                m_logger.debug("DF: " + m_id + " size: " + m_size +
                        " cache: " + ((m_fDfCache != null) ? m_fDfCache.getAbsolutePath() : "MEMORY") +
                        " dig-sha1: " + Base64Util.encode(m_digestSha1));
        } catch(Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_WRITE_FILE);
        } finally {
            try { if(os != null) os.close(); }
            catch(Exception ex) { m_logger.error("Error closing stream: " + ex); }
        }
    }

    /**
     * Accessor for body attribute.
     * Returns the body as a string. Takes in
     * account the initial codepage. usable
     * only for EMBEDDED type of documents or
     * if body is stored in Base64 then you have to be
     * sure that the converted data is textual and
     * can be returned as a String after decoding.
     * @return body as string
     */
    public String getBodyAsString()
            throws DigiDocException
    {
        String str = null;
        if(m_fDfCache != null) {
            try {
                byte[] data = SignedDoc.readFile(m_fDfCache);
                if(m_contentType.equals(CONTENT_EMBEDDED_BASE64))
                    str = ConvertUtils.data2str(Base64Util.decode(data), m_codepage);
            } catch(Exception ex) {
                DigiDocException.handleException(ex, DigiDocException.ERR_READ_FILE);
            }
        } else {
            if(m_contentType.equals(CONTENT_EMBEDDED_BASE64))
                str = ConvertUtils.data2str(Base64Util.decode(m_body), m_codepage);
        }
        return str;
    }

    /**
     * Accessor for body attribute.
     * Returns the body as a byte array. If body contains
     * embedded base64 data then this is decoded first
     * and decoded actual payload data returned.
     * @return body as a byte array
     */
    public byte[] getBodyAsData()
            throws DigiDocException
    {
        byte[] data = null;
        if(m_fDfCache != null) {
            try {
                data = SignedDoc.readFile(m_fDfCache);
                if(m_contentType.equals(CONTENT_EMBEDDED_BASE64))
                    data = Base64Util.decode(data);
            } catch(Exception ex) {
                DigiDocException.handleException(ex, DigiDocException.ERR_READ_FILE);
            }
        } else {
            if(m_contentType.equals(CONTENT_EMBEDDED_BASE64))
                data = Base64Util.decode(m_body);
        }
        return data;
    }

    public boolean hasAccessToDataFile()
    {
        if(m_fDfCache != null || m_body != null)
            return true;
        StringBuffer sbFil = new StringBuffer();
        File fT = new File(m_fileName);
        return fT.isFile() && fT.canRead();
    }

    /**
     * Accessor for body attribute.
     * Returns the body as an input stream. If body contains
     * embedded base64 data then this is decoded first
     * and decoded actual payload data returned.
     * @return body as a byte array
     */
    public InputStream getBodyAsStream()
            throws DigiDocException
    {
        InputStream strm = null;
        if(m_logger.isDebugEnabled())
            m_logger.debug("get body as stream f-cache: " + ((m_fDfCache != null) ? m_fDfCache.getAbsolutePath() : "NULL") +
                    " file: " + ((m_fileName != null) ? m_fileName : "NULL") + " content: " + m_contentType +
                    " body: " + ((m_body != null) ? m_body.length : 0) + " is-b64: " + m_bodyIsBase64);
        if(m_fDfCache != null || m_fileName != null) {
            try {
                if(m_contentType.equals(CONTENT_EMBEDDED_BASE64)) {
                    //strm = new iaik.utils.Base64InputStream(new FileInputStream(m_fDfCache));
                    if(m_fDfCache != null)
                        strm = new Base64InputStream(new FileInputStream(m_fDfCache));
                    else if(m_body != null) {
                        if(m_logger.isDebugEnabled())
                            m_logger.debug(" body: " + ((m_body != null) ? m_body.length : 0)  + " data: \n---\n" + new String(m_body) + "\n--\n");
                        strm = new Base64InputStream(new ByteArrayInputStream(m_body));
                    }
                }
                else if(m_contentType.equals(CONTENT_BINARY)) {
                    if(m_fDfCache != null)
                        strm = new FileInputStream(m_fDfCache);
                    else if(m_body != null)
                        strm = new ByteArrayInputStream(m_body);
                    else if(m_fileName != null)
                        strm = new FileInputStream(m_fileName);
                }
            } catch(Exception ex) {
                DigiDocException.handleException(ex, DigiDocException.ERR_READ_FILE);
            }
        } else if(m_body != null) {

        }
        return strm;
    }

    /**
     * Checks if this DataFile object schould use a temp file
     * to store it's data because of memory cache size limitation
     * @return true if this object schould use temp file
     */
    public boolean schouldUseTempFile()
    {
        long lMaxDfCached = ConfigManager.instance().
                getLongProperty("DIGIDOC_MAX_DATAFILE_CACHED", Long.MAX_VALUE);
        return (lMaxDfCached > 0 && (m_size == 0 || (m_size > lMaxDfCached && (m_contentType == null || m_contentType.equals(CONTENT_EMBEDDED_BASE64)))));
    }

    /**
     * Helper method to enable temporary cache file for this DataFile
     * @return new temporary file object
     * @throws IOException
     */
    public File createCacheFile()
            throws IOException
    {
        //m_fDfCache = null;
        if((m_fDfCache == null) && schouldUseTempFile()) {
            File fCacheDir = new File(ConfigManager.instance().
                    getStringProperty("DIGIDOC_DF_CACHE_DIR", System.getProperty("java.io.tmpdir")));
            String dfId = new Long(System.currentTimeMillis()).toString();
            m_fDfCache = File.createTempFile(dfId, ".df", fCacheDir);
        }
        return m_fDfCache;
    }

    public void setCacheFile(File d)
    {
        m_fDfCache = d;
    }

    /**
     * Helper method to store body in file if it exceeds the
     * memory cache limit
     * @throws IOException
     */
    private void storeInTempFile()
            throws IOException
    {
        File f = createCacheFile();
        if(f != null) {
            FileOutputStream fos = new FileOutputStream(f);
            fos.write(m_body);
            fos.close();
            // remove memory cache if stored in file
            m_body = null;
        }
    }

    /**
     * Use this method to assign data directly to body.
     * If you do this then the input file will not be read.
     * This also sets the initial size and codepage for you
     * @param data new value for body attribute
     * @deprecated embedded xml no longer supported
     */
    public void setBody(byte[] data, String codepage)
            throws DigiDocException
    {
        try {
            m_body = data;
            m_codepage = codepage;
            m_size = m_body.length;
            // check if data must be stored in file instead
            storeInTempFile();
        } catch(IOException ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_WRITE_FILE);
        }
    }

    /**
     * Use this method to assign data directly to body.
     * Input data is an XML subtree
     * @param xml xml subtree containing input data
     * @param codepage input data's original codepage
     * @deprecated embedded xml no longer supported
     */
    public void setBody(Node xml)
            throws DigiDocException
    {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            TransformerFactory tFactory = TransformerFactory.newInstance();
            Transformer transformer = tFactory.newTransformer();
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            DOMSource source = new DOMSource(xml);
            StreamResult result = new StreamResult(bos);
            transformer.transform(source, result);
            m_body = bos.toByteArray();
            // DOM library always outputs in UTF-8
            m_codepage = "UTF-8";
            m_size = m_body.length;
            // check if data must be stored in file instead
            storeInTempFile();
        } catch(Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_XML_CONVERT);
        }
    }

    /**
     * Accessor for initialCodepage attribute.
     * @return value of initialCodepage attribute
     * @deprecated embedded xml no longer supported
     */
    public String getInitialCodepage() {
        return m_codepage;
    }

    /**
     * Mutator for initialCodepage attribute.
     * If you use setBody() or assign data from a file
     * which is not in UTF-8 and then use CONTENT_EMBEDDED
     * then you must use this method to tell the library
     * in which codepage your data is so that we
     * can convert it to UTF-8.
     * @param data new value for initialCodepage attribute
     * @deprecated embedded xml no longer supported
     */
    public void setInitialCodepage(String data)
    {
        m_codepage = data;
    }

    /**
     * Accessor for contentType attribute
     * @return value of contentType attribute
     */
    public String getContentType() {
        return m_contentType;
    }

    /**
     * Mutator for contentType attribute
     * @param str new value for contentType attribute
     * @throws DigiDocException for validation errors
     */
    public void setContentType(String str)
            throws DigiDocException
    {
        DigiDocException ex = validateContentType(str);
        if(ex != null)
            throw ex;
        m_contentType = str;
    }

    /**
     * Helper method to validate a content type
     * @param str input data
     * @return exception or null for ok
     */
    private DigiDocException validateContentType(String str)
    {
        DigiDocException ex = null;
        boolean bUseHashcode = ConfigManager.instance().getBooleanProperty("DATAFILE_HASHCODE_MODE", false);
        if(m_sigDoc != null &&
                (str == null ||
                        (!str.equals(CONTENT_EMBEDDED_BASE64) && !str.equals(CONTENT_HASHCODE)) ||
                        (str.equals(CONTENT_HASHCODE) && !bUseHashcode)))
            ex = new DigiDocException(DigiDocException.ERR_DATA_FILE_CONTENT_TYPE,
                    "Currently supports only content types EMBEDDED_BASE64 for DDOC format", null);
        return ex;
    }

    /**
     * Accessor for fileName attribute
     * @return value of fileName attribute
     */
    public String getFileName() {
        return m_fileName;
    }

    /**
     * Mutator for fileName attribute
     * @param str new value for fileName attribute
     * @throws DigiDocException for validation errors
     */
    public void setFileName(String str)
            throws DigiDocException
    {
        DigiDocException ex = validateFileName(str);
        if(ex != null)
            throw ex;
        m_fileName = str;
    }

    /**
     * Helper method to validate a file name
     * @param str input data
     * @return exception or null for ok
     */
    private DigiDocException validateFileName(String str)
    {
        DigiDocException ex = null;
        if(str == null)
            ex = new DigiDocException(DigiDocException.ERR_DATA_FILE_FILE_NAME,
                    "Filename is a required attribute", null);
        return ex;
    }

    /**
     * Accessor for id attribute
     * @return value of id attribute
     */
    public String getId() {
        return m_id;
    }

    /**
     * Mutator for id attribute
     * @param str new value for id attribute
     * @throws DigiDocException for validation errors
     */
    public void setId(String str)
            throws DigiDocException
    {
        DigiDocException ex = validateId(str, false);
        if(ex != null)
            throw ex;
        m_id = str;
    }

    /**
     * Helper method to validate an id
     * @param str input data
     * @param bStrong flag that specifies if Id atribute value is to
     * be rigorously checked (according to digidoc format) or only
     * as required by XML-DSIG
     * @return exception or null for ok
     */
    private DigiDocException validateId(String str, boolean bStrong)
    {
        DigiDocException ex = null;
        if(str == null)
            ex = new DigiDocException(DigiDocException.ERR_DATA_FILE_ID,
                    "Id is a required attribute", null);
        if(str != null && bStrong &&
                m_sigDoc.getFormat() != null &&
                (str.charAt(0) != 'D' || (!Character.isDigit(str.charAt(1)) && str.charAt(1) != 'O')))
            ex = new DigiDocException(DigiDocException.ERR_DATA_FILE_ID,
                    "Id attribute value has to be in form D or DO", null);
        return ex;
    }

    /**
     * Accessor for mimeType attribute
     * @return value of mimeType attribute
     */
    public String getMimeType() {
        return m_mimeType;
    }

    /**
     * Mutator for mimeType attribute
     * @param str new value for mimeType attribute
     * @throws DigiDocException for validation errors
     */
    public void setMimeType(String str)
            throws DigiDocException
    {
        DigiDocException ex = validateMimeType(str);
        if(ex != null)
            throw ex;
        m_mimeType = str;
    }

    /**
     * Helper method to validate a mimeType
     * @param str input data
     * @return exception or null for ok
     */
    private DigiDocException validateMimeType(String str)
    {
        DigiDocException ex = null;
        if(str == null)
            ex = new DigiDocException(DigiDocException.ERR_DATA_FILE_MIME_TYPE,
                    "MimeType is a required attribute", null);
        return ex;
    }

    /**
     * Accessor for size attribute
     * @return value of size attribute
     */
    public long getSize() {
        return m_size;
    }

    /**
     * Mutator for size attribute
     * @param l new value for size attribute
     * @throws DigiDocException for validation errors
     */
    public void setSize(long l)
            throws DigiDocException
    {
        DigiDocException ex = validateSize(l);
        if(ex != null)
            throw ex;
        m_size = l;
    }

    /**
     * Helper method to validate a mimeType
     * @param l input data
     * @return exception or null for ok
     */
    private DigiDocException validateSize(long l)
    {
        DigiDocException ex = null;
        if(l < 0)
            ex = new DigiDocException(DigiDocException.ERR_DATA_FILE_SIZE,
                    "Size must be greater or equal to zero", null);
        return ex;
    }

    /**
     * Accessor for digestType attribute
     * @return value of digestType attribute
     */
    public String getDigestType() {
        if(m_sigDoc != null && m_sigDoc.countSignatures() > 0) {
            Reference ref = m_sigDoc.getSignature(0).getSignedInfo().getReferenceForDataFile(this);
            if(ref != null)
                return ref.getDigestAlgorithm();
            else
                return SignedDoc.SHA1_DIGEST_TYPE;
        }
        return null;
    }


    /**
     * Helper method to validate a digestType
     * @param str input data
     * @return exception or null for ok
     */
    private DigiDocException validateDigestType(String str)
    {
        DigiDocException ex = null;
        if(str != null && !str.equals("sha1") && !str.equals(SignedDoc.SHA1_DIGEST_TYPE))
            ex = new DigiDocException(DigiDocException.ERR_DATA_FILE_DIGEST_TYPE,
                    "The only supported digest types are sha1", null);
        return ex;
    }

    /**
     * Accessor for digestValue attribute
     * @param desired digest type
     * @return value of digestValue attribute
     */
    public byte[] getDigestValueOfType(String digType)
            throws DigiDocException
    {
        if(digType != null) {
            if(digType.equals(SignedDoc.SHA1_DIGEST_TYPE) || digType.equals("sha1")) {
                if(m_digestSha1 == null && m_origDigestValue == null)
                    m_digestSha1 = m_origDigestValue = calcHashOfType(SignedDoc.SHA1_DIGEST_TYPE);
                return ((m_digestSha1 != null) ? m_digestSha1 : m_origDigestValue);
            }
        }
        return m_digestSha1;
    }

    /**
     * Mutator for digestValue attribute
     * @param data new value for digestValue attribute
     * @throws DigiDocException for validation errors
     */
    public void setDigestValue(byte[] data)
            throws DigiDocException
    {
        DigiDocException ex = validateDigestValue(data);
        if(ex != null)
            throw ex;
        if(data.length == SignedDoc.SHA1_DIGEST_LENGTH)
            m_digestSha1 = data;
    }

    /**
     * Accessor for digest attribute
     * @return value of digest attribute
     */
    public byte[] getDigest()
            throws DigiDocException
    {
        if(m_origDigestValue == null)
            calculateFileSizeAndDigest(null);
        return m_origDigestValue;
    }

    /**
     * Mutator for digest attribute
     * @param data new value for digest attribute
     * @throws DigiDocException for validation errors
     */
    public void setDigest(byte[] data)
            throws DigiDocException
    {
        DigiDocException ex = validateDigestValue(data);
        if(ex != null)
            throw ex;
        m_origDigestValue = data;
    }

    /**
     * Accessor for alternate digest attribute
     * @return value of digest attribute
     */
    public byte[] getAltDigest()
    {
        return m_digestAlt;
    }

    /**
     * Mutator for alternate digest attribute
     * @param b new value for alternate digest attribute
     * @throws DigiDocException for validation errors
     */
    public void setAltDigest(byte[] b)
    {
        m_digestAlt = b;
    }


    /**
     * Helper method to validate a digestValue
     * @param str input data
     * @return exception or null for ok
     */
    private DigiDocException validateDigestValue(byte[] data)
    {
        DigiDocException ex = null;
        if(data != null && data.length != SignedDoc.SHA1_DIGEST_LENGTH)
            ex = new DigiDocException(DigiDocException.ERR_DATA_FILE_DIGEST_VALUE,
                    "SHA1 digest value must be 20 bytes - is: " + data.length, null);
        return ex;
    }

    /**
     * Returns the count of attributes
     * @return count of attributes
     */
    public int countAttributes()
    {
        return ((m_attributes == null) ? 0 : m_attributes.size());
    }

    /**
     * Adds a new DataFileAttribute object
     * @param attr DataFileAttribute object to add
     */
    public void addAttribute(DataFileAttribute attr)
    {
        if(m_attributes == null)
            m_attributes = new ArrayList();
        m_attributes.add(attr);
    }

    /**
     * Returns the desired DataFileAttribute object
     * @param idx index of the DataFileAttribute object
     * @return desired DataFileAttribute object
     */
    public DataFileAttribute getAttribute(int idx) {
        return (DataFileAttribute)m_attributes.get(idx);
    }

    /**
     * Accessor for comment attribute
     * @return value of comment attribute
     */
    public String getComment()
    {
        return m_comment;
    }

    /**
     * Mutator for comment attribute
     * @param s new value for comment attribute
     */
    public void setComment(String s)
    {
        m_comment = s;
    }

    /**
     * Helper method to validate the whole
     * DataFile object
     * @param bStrong flag that specifies if Id atribute value is to
     * be rigorously checked (according to digidoc format) or only
     * as required by XML-DSIG
     * @return a possibly empty list of DigiDocException objects
     */
    public ArrayList validate(boolean bStrong)
    {
        ArrayList errs = new ArrayList();
        DigiDocException ex = validateContentType(m_contentType);
        if(ex != null)
            errs.add(ex);
        ex = validateFileName(m_fileName);
        if(ex != null)
            errs.add(ex);
        ex = validateId(m_id, bStrong);
        if(ex != null)
            errs.add(ex);
        ex = validateMimeType(m_mimeType);
        if(ex != null)
            errs.add(ex);
        ex = validateSize(m_size);
        if(ex != null)
            errs.add(ex);
        for(int i = 0; i < countAttributes(); i++) {
            DataFileAttribute attr = getAttribute(i);
            ArrayList e = attr.validate();
            if(!e.isEmpty())
                errs.addAll(e);
        }
        return errs;
    }

    /**
     * Helper method to canonicalize a piece of xml
     * @param xml data to be canonicalized
     * @return canonicalized xml
     */
    private byte[] canonicalizeXml(byte[] data) {
        try {
            CanonicalizationFactory canFac = ConfigManager.
                    instance().getCanonicalizationFactory();
            byte[] tmp = canFac.canonicalize(data,
                    SignedDoc.CANONICALIZATION_METHOD_20010315);
            return tmp;
        } catch(Exception ex) {
            m_logger.error("Canonicalizing exception: " + ex);
        }
        return null;
    }

    /**
     * Helper method for using an optimization for base64 data's
     * conversion and digest calculation. We use data blockwise to
     * conserve memory
     * @param os output stream to write data
     * @param digest existing sha1 digest to be updated
     * @param b64leftover leftover base64 data from previous block
     * @param b64left leftover data length
     * @param data new binary data
     * @param dLen number of used bytes in data
     * @param bLastBlock flag last block
     * @return length of leftover bytes from this block
     * @throws DigiDocException
     */
    private int calculateAndWriteBase64Block(OutputStream os, MessageDigest digest,
                                             byte[] b64leftover, int b64left, byte[] data, int dLen, boolean bLastBlock)
            throws DigiDocException
    {
        byte[] b64input = null;
        int b64Used, nLeft = 0, nInLen = 0;
        StringBuffer b64data = new StringBuffer();

        if(m_logger.isDebugEnabled())
            m_logger.debug("os: " + ((os != null) ? "Y" :"N") +
                    " b64left: " + b64left + " input: " + dLen + " last: " + (bLastBlock ? "Y" : "N"));
        try {
            // use data from the last block
            if(b64left > 0) {
                if(dLen > 0) {
                    b64input = new byte[dLen + b64left];
                    nInLen = b64input.length;
                    System.arraycopy(b64leftover, 0, b64input, 0, b64left);
                    System.arraycopy(data, 0, b64input, b64left, dLen);
                    if(m_logger.isDebugEnabled())
                        m_logger.debug("use left: " + b64left + " from 0 and add " + dLen);
                } else {
                    b64input = b64leftover;
                    nInLen = b64left;
                    if(m_logger.isDebugEnabled())
                        m_logger.debug("use left: " + b64left + " with no new data");
                }
            } else {
                b64input = data;
                nInLen = dLen;
                if(m_logger.isDebugEnabled())
                    m_logger.debug("use: " + nInLen + " from 0");
            }
            // encode full rows
            b64Used = Base64Util.encodeToBlock(b64input, nInLen, b64data, bLastBlock);
            nLeft = nInLen - b64Used;
            // use the encoded data
            byte[] encdata = b64data.toString().getBytes();
            if(os != null)
                os.write(encdata);
            digest.update(encdata);
            // now copy not encoded data back to buffer
            if(m_logger.isDebugEnabled())
                m_logger.debug("Leaving: " + nLeft + " of: " + b64input.length);
            if(nLeft > 0)
                System.arraycopy(b64input, b64input.length - nLeft, b64leftover, 0, nLeft);
        } catch(Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_READ_FILE);
        }
        if(m_logger.isDebugEnabled())
            m_logger.debug("left: " + nLeft + " bytes for the next run");
        return nLeft;
    }

    /**
     * Calculates the DataFiles size and digest
     * Since it calculates the digest of the external file
     * then this is only useful for detatched files
     * @throws DigiDocException for all errors
     */
    public void calculateFileSizeAndDigest(OutputStream os)
            throws DigiDocException
    {
        if(m_logger.isDebugEnabled())
            m_logger.debug("calculateFileSizeAndDigest(" + getId() + ") body: " +
                    ((m_body != null) ? "OK" : "NULL") + " base64: " + m_bodyIsBase64 +
                    " DF cache: " + ((m_fDfCache != null) ? m_fDfCache.getAbsolutePath() : "NULL"));
        FileInputStream fis = null;
        if(m_contentType.equals(CONTENT_BINARY)) {
            InputStream is = null;
            try {
                if(getDfCacheFile() != null)
                    is = getBodyAsStream();
                else if(is == null && m_body != null)
                    is = new java.io.ByteArrayInputStream(m_body);
                else if(is == null && m_fileName != null)
                    is = new java.io.FileInputStream(m_fileName);
                if(is != null)
                    calcHashes(is);
            } catch(java.io.FileNotFoundException ex) {
                throw new DigiDocException(DigiDocException.ERR_READ_FILE, "Cannot read file: " + m_fileName, null);
            } finally {
                try {
                    if(is != null)
                        is.close();
                } catch(Exception ex) {
                    m_logger.error("Error closing stream: " + ex);
                }
            }
            return;
        }

        MessageDigest sha = null;
        boolean bUse64ByteLines = true;
        String use64Flag = ConfigManager.instance().getProperty("DATAFILE_USE_64BYTE_LINES");
        if(use64Flag != null && use64Flag.equalsIgnoreCase("FALSE"))
            bUse64ByteLines = false;
        try {
            sha = MessageDigest.getInstance("SHA-1"); // TODO: fix digest type
            // if DataFile's digest has already been initialized
            // and body in memory, e.g. has been read from digidoc
            // then write directly to output stream and don't calculate again
            if(m_origDigestValue != null && m_body != null && os != null) {
                os.write(xmlHeader());
                if(m_logger.isDebugEnabled())
                    m_logger.debug("write df header1: " + xmlHeader());
                os.write(m_body);
                os.write(xmlTrailer());
                return;
            }
            String longFileName = m_fileName;
            File fIn = new File(m_fileName);
            m_fileName = fIn.getName();
            if(fIn.canRead() && m_fDfCache == null) {
                fis = new FileInputStream(longFileName);
                if(m_logger.isDebugEnabled())
                    m_logger.debug("Read file: " + longFileName);
            }
            else if(m_fDfCache != null) {
                fis = new FileInputStream(m_fDfCache);
                if(m_logger.isDebugEnabled())
                    m_logger.debug("Read cache: " + m_fDfCache);
            }
            byte[] tmp1=null,tmp2=null,tmp3=null;
            ByteArrayOutputStream sbDig = new ByteArrayOutputStream();
            sbDig.write(xmlHeader());
            // add trailer and canonicalize
            tmp3 = xmlTrailer();
            sbDig.write(tmp3);
            tmp1 = canonicalizeXml(sbDig.toByteArray());
            // now remove the end tag again and calculate digest of the start tag only
            if(tmp1 != null) {
                tmp2 = new byte[tmp1.length - tmp3.length];
                System.arraycopy(tmp1, 0, tmp2, 0, tmp2.length);
                sha.update(tmp2);
                if(os != null)
                    os.write(xmlHeader());
            }
            // reset the collecting buffer and other temp buffers
            sbDig = new ByteArrayOutputStream();
            tmp1 = tmp2 = tmp3 = null;
            // content must be read from file
            if(m_body == null) {
                if(m_logger.isDebugEnabled())
                    m_logger.debug("Reading input file: " + ((fIn.canRead() && m_fDfCache == null) ? longFileName : ((m_fDfCache != null) ? m_fDfCache.getAbsolutePath() : "no-cache")));
                byte[] buf = new byte[block_size];
                byte[] b64leftover = null;
                int fRead = 0, b64left = 0;
                ByteArrayOutputStream content = null;
                if(m_contentType.equals(CONTENT_EMBEDDED_BASE64)) {
                    // optimization for 64 char base64 lines
                    // convert to base64 online at a time to conserve memory
                    // VS: DF temp file base64 decoding fix
                    if(m_fDfCache == null) {
                        if(bUse64ByteLines)
                            b64leftover = new byte[65];
                        else
                            content = new ByteArrayOutputStream();
                    }
                }
                while((fRead = fis.read(buf)) > 0 || b64left > 0) { // read input file
                    if(m_logger.isDebugEnabled())
                        m_logger.debug("read: " + fRead + " bytes of input data");
                    if(m_contentType.equals(CONTENT_EMBEDDED_BASE64)) {
                        // VS: DF temp file base64 decoding fix
                        if(m_fDfCache != null) {
                            if(os != null)
                                os.write(buf, 0, fRead);
                            sha.update(buf, 0, fRead);
                        } else {
                            if(bUse64ByteLines) { // 1 line base64 optimization
                                b64left = calculateAndWriteBase64Block(os, sha, b64leftover,
                                        b64left, buf, fRead, fRead < block_size);
                            } else { // no optimization
                                content.write(buf, 0, fRead);
                            }
                        }
                    } else {
                        if(fRead < buf.length) {
                            tmp2= new byte[fRead];
                            System.arraycopy(buf, 0, tmp2, 0, fRead);
                            tmp1 = ConvertUtils.data2utf8(tmp2, m_codepage);
                        }
                        else
                            tmp1 = ConvertUtils.data2utf8(buf, m_codepage);
                        sbDig.write(tmp1);
                    }
                    if(m_logger.isDebugEnabled())
                        m_logger.debug("End using block: " + fRead + " in: " + ((fis != null) ? fis.available() : 0));
                } // end reading input file
                if(m_contentType.equals(CONTENT_EMBEDDED_BASE64)) {
                    // VS: DF temp file base64 decoding fix
                    if(!bUse64ByteLines && m_fDfCache == null)
                        sbDig.write(Base64Util.encode(content.toByteArray(), 0).getBytes());
                    content = null;
                }
                if(m_logger.isDebugEnabled())
                    m_logger.debug("End reading content");
            } else { // content allready in memeory
                if(m_logger.isDebugEnabled())
                    m_logger.debug("Using mem content, len: " + ((m_body != null) ? m_body.length : 0) + " b64: " + m_bodyIsBase64);
                if(m_body != null) {
                    if(bUse64ByteLines && m_contentType.equals(CONTENT_EMBEDDED_BASE64) && !m_bodyIsBase64) {
                        calculateAndWriteBase64Block(os, sha, null, 0, m_body, m_body.length, true);
                        m_body = Base64Util.encode(m_body).getBytes();
                        //sbDig.write(m_body); // this code block not used any more ?
                    } else {
                        if(m_contentType.equals(CONTENT_EMBEDDED_BASE64) && !m_bodyIsBase64) {
                            tmp1 = Base64Util.encode(m_body).getBytes();
                        } else if(m_contentType.equals(CONTENT_EMBEDDED_BASE64) && m_bodyIsBase64) {
                            tmp1 = ConvertUtils.data2utf8(m_body, m_codepage);
                        } else
                            tmp1 = ConvertUtils.data2utf8(m_body, m_codepage);
                        sbDig.write(tmp1);
                    }
                }
            }
            tmp1 = null;
            // don't need to canonicalize base64 content !
            if(m_contentType.equals(CONTENT_EMBEDDED_BASE64)) {
                // VS: DF temp file base64 decoding fix
                if(!bUse64ByteLines && m_fDfCache == null) {
                    tmp2 = sbDig.toByteArray();
                    if(tmp2 != null && tmp2.length > 0) {
                        sha.update(tmp2);
                        if(os != null)
                            os.write(tmp2);
                    }
                } else if(m_body != null && sbDig.size() > 0) {
                    tmp2 = sbDig.toByteArray();
                    if(tmp2 != null && tmp2.length > 0) {
                        sha.update(tmp2);
                        if(os != null)
                            os.write(tmp2);
                    }
                }
            } else {
                // canonicalize body
                tmp2 = sbDig.toByteArray();
                if(tmp2 != null && tmp2.length > 0) {
                    if(tmp2[0] == '<')
                        tmp2 = canonicalizeXml(tmp2);
                    if(tmp2 != null && tmp2.length > 0) {
                        sha.update(tmp2);  // crash
                        if(os != null)
                            os.write(tmp2);
                    }
                }
            }
            tmp2 = null;
            sbDig = null;
            // trailer
            tmp1 = xmlTrailer();
            sha.update(tmp1);
            if(os != null)
                os.write(tmp1);
            // now calculate the digest
            byte[] digest = sha.digest();
            setDigest(digest);
            if(m_logger.isDebugEnabled())
                m_logger.debug("DataFile: \'" + getId() + "\' length: " +
                        getSize() + " digest: " + Base64Util.encode(digest));
            m_fileName = longFileName;
        } catch(Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_READ_FILE);
        } finally {
            try {
                if(fis != null)
                    fis.close();
            } catch(Exception ex) {
                m_logger.error("Error closing file: " + ex);
            }
        }
    }


    /**
     * Writes the DataFile to an outout file
     * @param fos output stream
     * @throws DigiDocException for all errors
     */
    public void writeToFile(OutputStream fos)
            throws DigiDocException
    {
        // for detatched files just read them in
        // calculate digests and store a reference to them
        try {
            calculateFileSizeAndDigest(fos);
        } catch(DigiDocException ex) {
            throw ex;
        } catch(Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_READ_FILE);
        }
    }


    /**
     * Helper method to create the xml header
     * @return xml header
     */
    private byte[] xmlHeader()
            throws DigiDocException
    {
        StringBuffer sb = new StringBuffer("");
        return ConvertUtils.str2data(sb.toString(), "UTF-8");
    }

    /**
     * Helper method to create the xml trailer
     * @return xml trailer
     */
    private byte[] xmlTrailer()
            throws DigiDocException
    {
        return ConvertUtils.str2data("", "UTF-8");
    }

    /**
     * Converts the DataFile to XML form
     * @return XML representation of DataFile
     */
    public byte[] toXML()
            throws DigiDocException
    {
        ByteArrayOutputStream sb = new ByteArrayOutputStream();
        try {
            sb.write(xmlHeader());
            if(m_body != null) {
                //if(m_contentType.equals(CONTENT_EMBEDDED_BASE64))
                //    sb.write(Base64Util.encode(m_body).getBytes());
                if(m_contentType.equals(CONTENT_EMBEDDED_BASE64))
                    sb.write(m_body);
            }
            sb.write(xmlTrailer());
        } catch(Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_ENCODING);
        }
        return sb.toByteArray();
    }

    /**
     * Returns the stringified form of DataFile
     * @return DataFile string representation
     */
    public String toString()
    {
        String str = null;
        try {
            str = new String(toXML(), "UTF-8");
        } catch(Exception ex) {}
        return str;
    }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy