/*
* DataFile.java
* PROJECT: JDigiDoc
* DESCRIPTION: Digi Doc functions for creating
* and reading signed documents.
* AUTHOR: Veiko Sinivee, S|E|B IT Partner Estonia
*==================================================
* Copyright (C) AS Sertifitseerimiskeskus
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* GNU Lesser General Public Licence is available at
* http://www.gnu.org/copyleft/lesser.html
*==================================================
*/
package es.uji.security.crypto.openxades.digidoc;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.security.MessageDigest;
import java.util.ArrayList;
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 org.apache.log4j.Logger;
import org.w3c.dom.Node;
import es.uji.security.crypto.config.ConfigManager;
import es.uji.security.crypto.openxades.digidoc.factory.CanonicalizationFactory;
import es.uji.security.crypto.openxades.digidoc.factory.FactoryManager;
import es.uji.security.crypto.openxades.digidoc.utils.ConvertUtils;
import es.uji.security.util.Base64;
/**
* 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
{
/**
* Comment for <code>serialVersionUID</code>
*/
private static final long serialVersionUID = 1L;
/** content type of the DataFile */
private String m_contentType;
/** filename */
private String m_fileName;
/** Id attribute od this DataFile */
private String m_id;
/** mime type of the file */
private String m_mimeType;
/** file size on bytes */
private long m_size;
/** digest type of detatched file */
private String m_digestType;
/** digest value of detatched file */
private byte[] m_digestValue;
/**
* digest value of the XML form of <DataFile> 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_DETATCHED = "DETATCHED";
public static final String CONTENT_EMBEDDED = "EMBEDDED";
public static final String CONTENT_EMBEDDED_BASE64 = "EMBEDDED_BASE64";
/** the only allowed value for digest type */
public static final String DIGEST_TYPE_SHA1 = "sha1";
private static int block_size = 2048;
/** log4j logger */
private Logger m_logger = 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
{
setId(id);
setContentType(contentType);
setFileName(fileName);
setMimeType(mimeType);
m_sigDoc = sdoc;
m_size = 0;
m_digestType = null;
m_digestValue = null;
m_attributes = null;
m_body = null;
m_codepage = "UTF-8";
m_origDigestValue = null;
m_logger = Logger.getLogger(DataFile.class);
}
/**
* 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()
{
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)
{
m_body = data;
}
/**
* Accessor for body attribute. Returns the body as a string. Takes in account the initial
* codepage. usable only for EMBEDDED type of documents.
*
* @return body as string
*/
public String getBodyAsString() throws DigiDocException
{
String str = null;
if (m_contentType.equals(CONTENT_EMBEDDED))
str = ConvertUtils.data2str(m_body, m_codepage);
if (m_contentType.equals(CONTENT_EMBEDDED_BASE64))
str = ConvertUtils.data2str(Base64.decode(m_body), m_codepage);
return str;
}
/**
* 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
*/
public void setBody(byte[] data, String codepage)
{
m_body = data;
m_codepage = codepage;
m_size = m_body.length;
}
/**
* 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
*/
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;
// System.out.println("BODY: \'" + getBodyAsString() + "\'");
}
catch (Exception ex)
{
DigiDocException.handleException(ex, DigiDocException.ERR_XML_CONVERT);
}
}
/**
* Accessor for initialCodepage attribute.
*
* @return value of initialCodepage attribute
*/
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
*/
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;
if (str == null
|| (!str.equals(CONTENT_DETATCHED) && !str.equals(CONTENT_EMBEDDED) && !str
.equals(CONTENT_EMBEDDED_BASE64)))
ex = new DigiDocException(
DigiDocException.ERR_DATA_FILE_CONTENT_TYPE,
"Currently supports only content types: DETATCHED, EMBEDDED and EMBEDDED_BASE64",
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 && (str.charAt(0) != 'D' || !Character.isDigit(str.charAt(1))))
ex = new DigiDocException(DigiDocException.ERR_DATA_FILE_ID,
"Id attribute value has to be in form D<number>", 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 than zero", null);
return ex;
}
/**
* Accessor for digestType attribute
*
* @return value of digestType attribute
*/
public String getDigestType()
{
return m_digestType;
}
/**
* Mutator for digestType attribute
*
* @param str
* new value for digestType attribute
* @throws DigiDocException
* for validation errors
*/
public void setDigestType(String str) throws DigiDocException
{
DigiDocException ex = validateDigestType(str);
if (ex != null)
throw ex;
m_digestType = str;
}
/**
* 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(DIGEST_TYPE_SHA1))
ex = new DigiDocException(DigiDocException.ERR_DATA_FILE_DIGEST_TYPE,
"The only supported digest type is sha1", null);
return ex;
}
/**
* Accessor for digestValue attribute
*
* @return value of digestValue attribute
*/
public byte[] getDigestValue() throws DigiDocException
{
return m_digestValue;
}
/**
* 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;
m_digestValue = 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;
}
/**
* 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", 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);
}
/**
* 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);
ex = validateDigestType(m_digestType);
if (ex != null)
errs.add(ex);
ex = validateDigestValue(m_digestValue);
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;
}
/*
* private void debugWriteFile(String name, String data) { try { String str =
* "C:\\veiko\\work\\sk\\JDigiDoc\\" + name; System.out.println("Writing debug file: " + str);
* FileOutputStream fos = new FileOutputStream(str); fos.write(data.getBytes()); fos.close(); }
* catch(Exception ex) { System.out.println("Error: " + ex); ex.printStackTrace(System.out); } }
*/
/**
* Helper method to calculate original digest for base64 encoded content. Since such content is
* decoded the whitespace around it is thrown away. So we must calculate in beforehand
*
* @param origBody
* original base64 body with any whitespace
*/
/*
* public void calcOrigDigest(String origBody) throws DigiDocException {
* //System.out.println("calculateFileSizeAndDigest(" + getId() + ")"); try {
*
* ByteArrayOutputStream sbDig = new ByteArrayOutputStream(); byte[] tmp = null; tmp =
* xmlHeader(); sbDig.write(tmp); tmp = origBody.getBytes(); sbDig.write(tmp); tmp =
* xmlTrailer(); sbDig.write(tmp); //debugWriteFile(getId() + "-body1.xml", sbDig.toString());
* CanonicalizationFactory canFac = ConfigManager. instance().getCanonicalizationFactory(); tmp
* = canFac.canonicalize(sbDig.toByteArray(), SignedDoc.CANONICALIZATION_METHOD_20010315);
* //debugWriteFile(getId() + "-body2.xml", new String(tmp)); MessageDigest sha =
* MessageDigest.getInstance("SHA-1"); sha.update(tmp); byte[] digest = sha.digest();
* setDigest(digest); //System.out.println("DataFile: \'" + getId() + "\' length: " + //
* tmp.length + " digest: " + Base64Util.encode(digest)); } catch(Exception ex) {
* DigiDocException.handleException(ex, DigiDocException.ERR_READ_FILE); } }
*/
/**
* 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 = FactoryManager.getCanonicalizationFactory();
byte[] tmp = canFac.canonicalize(data, SignedDoc.CANONICALIZATION_METHOD_20010315);
return tmp;
}
catch (Exception ex)
{
System.out.println("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
{
ConfigManager conf = ConfigManager.getInstance();
if (m_logger.isDebugEnabled())
m_logger.debug("calculateFileSizeAndDigest(" + getId() + ")");
boolean bUse64ByteLines = true;
String use64Flag = conf.getProperty("DATAFILE_USE_64BYTE_LINES");
if (use64Flag != null && use64Flag.equalsIgnoreCase("FALSE"))
bUse64ByteLines = false;
try
{
// 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.write(xmlHeader());
os.write(m_body);
os.write(xmlTrailer());
return;
}
// else calculate again
if (m_contentType.equals(CONTENT_DETATCHED) && m_digestValue == null)
{
setDigestType(DIGEST_TYPE_SHA1);
setDigestValue(calculateDetatchedFileDigest());
}
FileInputStream is = null;
if (m_body == null && !m_contentType.equals(CONTENT_DETATCHED))
{
is = new FileInputStream(m_fileName);
long fSize = new File(m_fileName).length();
setSize(fSize);
}
String longFileName = m_fileName;
m_fileName = new File(m_fileName).getName();
MessageDigest sha = MessageDigest.getInstance("SHA-1");
ByteArrayOutputStream sbDig = new ByteArrayOutputStream();
sbDig.write(xmlHeader());
// add trailer and canonicalize
byte[] tmp3 = xmlTrailer();
sbDig.write(tmp3);
byte[] tmp1 = canonicalizeXml(sbDig.toByteArray());
// now remove the end tag again and calculate digest of the start tag only
byte[] tmp2 = new byte[tmp1.length - tmp3.length];
System.arraycopy(tmp1, 0, tmp2, 0, tmp2.length);
sha.update(tmp2);
if (os != null)
os.write(tmp2);
// 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 && !m_contentType.equals(CONTENT_DETATCHED))
{
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 conserver memory
if (bUse64ByteLines)
b64leftover = new byte[65];
else
content = new ByteArrayOutputStream();
}
while ((fRead = is.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))
{
// 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);
}
} // end reading input file
if (m_contentType.equals(CONTENT_EMBEDDED_BASE64))
{
if (!bUse64ByteLines)
sbDig.write(Base64.encodeBytesToBytes(content.toByteArray()));
content = null;
}
}
else
{ // content allready in memory
if (m_body != null)
{
// if (bUse64ByteLines && m_contentType.equals(CONTENT_EMBEDDED_BASE64))
// {
// calculateAndWriteBase64Block(os, sha, null, 0, m_body, m_body.length, true);
// m_body = Base64Util.encode(m_body).getBytes();
// }
// else
// {
if (m_contentType.equals(CONTENT_EMBEDDED_BASE64)){
tmp1 = Base64.encodeBytesToBytes(m_body);
m_body= tmp1;
sha.update(tmp1);
}
else{
tmp1 = ConvertUtils.data2utf8(m_body, m_codepage);
}
sbDig.write(tmp1);
// }
}
}
tmp1 = null;
if (is != null)
is.close();
// don't need to canonicalize base64 content !
if (m_contentType.equals(CONTENT_EMBEDDED_BASE64))
{
if (!bUse64ByteLines)
{
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)
{
// System.out.println("Body: \"" + tmp2 + "\"");
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: "
+ Base64.encodeBytes(digest));
m_fileName = longFileName;
}
catch (Exception ex)
{
DigiDocException.handleException(ex, DigiDocException.ERR_READ_FILE);
}
}
public byte[] calculateDetatchedFileDigest() throws DigiDocException
{
byte[] digest = null;
try
{
// System.out.println("calculateDetatchedFileDigest(" + getId() + ")");
FileInputStream is = new FileInputStream(m_fileName);
setSize(is.available());
MessageDigest sha = MessageDigest.getInstance("SHA-1");
byte[] buf = new byte[block_size]; // use 2KB bytes to avoid base64 problems
int fRead = 0;
while ((fRead = is.read(buf)) == block_size)
{
sha.update(buf);
}
if (fRead > 0)
{
byte[] buf2 = new byte[fRead];
System.arraycopy(buf, 0, buf2, 0, fRead);
sha.update(buf2);
}
is.close();
digest = sha.digest();
// System.out.println("DataFile: \'" + getId() +
// "\' digest: " + Base64Util.encode(digest));
}
catch (Exception ex)
{
DigiDocException.handleException(ex, DigiDocException.ERR_READ_FILE);
}
return digest;
}
/**
* Writes the DataFile to an outout file
*
* @param fos
* output stream
* @throws DigiDocException
* for all errors
*/
public void writeToFile(OutputStream fos) throws DigiDocException
{
// System.out.println("writeToFile(" + getId() + ")");
// 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 replace '&' by '&' in file names
*
* @param filename
* original file name
* @return fixed file name
*/
private String fixFileName(String fileName)
{
StringBuffer sb = new StringBuffer();
for (int i = 0; (fileName != null) && (i < fileName.length()); i++)
{
char ch = fileName.charAt(i);
if (ch == '&')
sb.append("&");
else
sb.append(ch);
}
return sb.toString();
}
/**
* Helper method to create the xml header
*
* @return xml header
*/
private byte[] xmlHeader() throws DigiDocException
{
StringBuffer sb = new StringBuffer("<DataFile");
if (m_codepage != null && !m_codepage.equals("UTF-8"))
{
sb.append(" Codepage=\"");
sb.append(m_codepage);
sb.append("\"");
}
sb.append(" ContentType=\"");
sb.append(m_contentType);
sb.append("\" Filename=\"");
// we write only file name not path to file
String fileName = new File(m_fileName).getName();
sb.append(fixFileName(fileName));
sb.append("\" Id=\"");
sb.append(m_id);
sb.append("\" MimeType=\"");
sb.append(m_mimeType);
sb.append("\" Size=\"");
sb.append(new Long(m_size).toString());
sb.append("\"");
if (m_digestType != null && m_digestValue != null)
{
sb.append(" DigestType=\"");
sb.append(m_digestType);
sb.append("\" DigestValue=\"");
sb.append(Base64.encodeBytes(m_digestValue));
sb.append("\"");
}
for (int i = 0; i < countAttributes(); i++)
{
DataFileAttribute attr = getAttribute(i);
sb.append(" ");
sb.append(attr.toXML());
}
// namespace
if (m_sigDoc != null && m_sigDoc.getVersion().equals(SignedDoc.VERSION_1_3))
{
sb.append(" xmlns=\"");
sb.append(SignedDoc.xmlns_digidoc);
sb.append("\"");
}
sb.append(">");
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("</DataFile>", "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)
|| 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;
}
}