aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Ahlborn <jtahlborn@yahoo.com>2013-06-02 02:23:36 +0000
committerJames Ahlborn <jtahlborn@yahoo.com>2013-06-02 02:23:36 +0000
commit52df2373c7bb61b784b3d983c6ee42ae4b1cb030 (patch)
tree12f0679c793fe97f8ca9e44e9c21fa10db205890
parent99f648a340dd45967550a511f8a27a16c2c5b5f3 (diff)
downloadjackcess-52df2373c7bb61b784b3d983c6ee42ae4b1cb030.tar.gz
jackcess-52df2373c7bb61b784b3d983c6ee42ae4b1cb030.zip
add methods to get the actual attachment content
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@733 f203690c-595d-4dc9-a70b-905162fa7fd2
-rw-r--r--pom.xml6
-rw-r--r--src/changes/changes.xml4
-rw-r--r--src/java/com/healthmarketscience/jackcess/Column.java3
-rw-r--r--src/java/com/healthmarketscience/jackcess/Database.java3
-rw-r--r--src/java/com/healthmarketscience/jackcess/PageChannel.java8
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/Attachment.java5
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/AttachmentColumnInfo.java127
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java18
-rw-r--r--test/src/java/com/healthmarketscience/jackcess/ComplexColumnTest.java29
9 files changed, 193 insertions, 10 deletions
diff --git a/pom.xml b/pom.xml
index 380ffaf..08e90dc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -82,6 +82,12 @@
read-only support</role>
</roles>
</contributor>
+ <contributor>
+ <name>Lorenzo Carrara</name>
+ <roles>
+ <role>Reverse engineered the attachment data encoding.</role>
+ </roles>
+ </contributor>
</contributors>
<issueManagement>
<system>SourceForge2</system>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index c5b9575..d26c22d 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -12,6 +12,10 @@
<action dev="jahlborn" type="update">
Add more methods to Database for retrieving Relationships.
</action>
+ <action dev="jahlborn" type="update">
+ Add methods to get the actual attachment content, thanks to Lorenzo
+ Carrara.
+ </action>
</release>
<release version="1.2.12" date="2013-05-09">
<action dev="jahlborn" type="fix" system="SourceForge2" issue="94">
diff --git a/src/java/com/healthmarketscience/jackcess/Column.java b/src/java/com/healthmarketscience/jackcess/Column.java
index 3c0274f..5f445b1 100644
--- a/src/java/com/healthmarketscience/jackcess/Column.java
+++ b/src/java/com/healthmarketscience/jackcess/Column.java
@@ -899,8 +899,7 @@ public class Column implements Comparable<Column> {
private byte[] readLongValue(byte[] lvalDefinition)
throws IOException
{
- ByteBuffer def = ByteBuffer.wrap(lvalDefinition)
- .order(PageChannel.DEFAULT_BYTE_ORDER);
+ ByteBuffer def = PageChannel.wrap(lvalDefinition);
int lengthWithFlags = def.getInt();
int length = lengthWithFlags & (~LONG_VALUE_TYPE_MASK);
diff --git a/src/java/com/healthmarketscience/jackcess/Database.java b/src/java/com/healthmarketscience/jackcess/Database.java
index 3a720b2..450c9b9 100644
--- a/src/java/com/healthmarketscience/jackcess/Database.java
+++ b/src/java/com/healthmarketscience/jackcess/Database.java
@@ -2409,8 +2409,7 @@ public class Database
double dateVal = Double.longBitsToDouble(buffer.getLong());
byte[] pwdMask = new byte[4];
- ByteBuffer.wrap(pwdMask).order(PageChannel.DEFAULT_BYTE_ORDER)
- .putInt((int)dateVal);
+ PageChannel.wrap(pwdMask).putInt((int)dateVal);
return pwdMask;
}
diff --git a/src/java/com/healthmarketscience/jackcess/PageChannel.java b/src/java/com/healthmarketscience/jackcess/PageChannel.java
index 58b4017..27cb0ab 100644
--- a/src/java/com/healthmarketscience/jackcess/PageChannel.java
+++ b/src/java/com/healthmarketscience/jackcess/PageChannel.java
@@ -380,4 +380,12 @@ public class PageChannel implements Channel, Flushable {
.position(position)
.mark();
}
+
+ /**
+ * Returns a ByteBuffer wrapping the given bytes and configured with the
+ * default byte order.
+ */
+ public static ByteBuffer wrap(byte[] bytes) {
+ return ByteBuffer.wrap(bytes).order(DEFAULT_BYTE_ORDER);
+ }
}
diff --git a/src/java/com/healthmarketscience/jackcess/complex/Attachment.java b/src/java/com/healthmarketscience/jackcess/complex/Attachment.java
index 2f4b046..be7e302 100644
--- a/src/java/com/healthmarketscience/jackcess/complex/Attachment.java
+++ b/src/java/com/healthmarketscience/jackcess/complex/Attachment.java
@@ -19,6 +19,7 @@ USA
package com.healthmarketscience.jackcess.complex;
+import java.io.IOException;
import java.util.Date;
/**
@@ -32,6 +33,10 @@ public interface Attachment extends ComplexValue
public void setFileData(byte[] data);
+ public byte[] getDecodedFileData() throws IOException;
+
+ public void setDecodedFileData(byte[] data);
+
public String getFileName();
public void setFileName(String fileName);
diff --git a/src/java/com/healthmarketscience/jackcess/complex/AttachmentColumnInfo.java b/src/java/com/healthmarketscience/jackcess/complex/AttachmentColumnInfo.java
index 5410e41..eb3ca73 100644
--- a/src/java/com/healthmarketscience/jackcess/complex/AttachmentColumnInfo.java
+++ b/src/java/com/healthmarketscience/jackcess/complex/AttachmentColumnInfo.java
@@ -19,13 +19,19 @@ USA
package com.healthmarketscience.jackcess.complex;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
import java.util.Date;
import java.util.List;
import java.util.Map;
+import java.util.zip.InflaterInputStream;
import com.healthmarketscience.jackcess.ByteUtil;
import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.PageChannel;
import com.healthmarketscience.jackcess.Table;
@@ -39,6 +45,9 @@ public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
private static final String FILE_NAME_COL_NAME = "FileName";
private static final String FILE_TYPE_COL_NAME = "FileType";
+ private static final int DATA_TYPE_RAW = 0;
+ private static final int DATA_TYPE_COMPRESSED = 1;
+
private final Column _fileUrlCol;
private final Column _fileNameCol;
private final Column _fileTypeCol;
@@ -142,7 +151,7 @@ public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
byte[] data = (byte[])getFileDataColumn().getRowValue(rawValue);
return new AttachmentImpl(id, complexValueFk, url, name, type, data,
- ts, flags);
+ ts, flags, null);
}
@Override
@@ -179,7 +188,33 @@ public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
String type, byte[] data, Date timeStamp, Integer flags)
{
return new AttachmentImpl(INVALID_ID, complexValueFk, url, name, type,
- data, timeStamp, flags);
+ data, timeStamp, flags, null);
+ }
+
+ public static Attachment newDecodedAttachment(byte[] decodedData) {
+ return newDecodedAttachment(INVALID_COMPLEX_VALUE_ID, decodedData);
+ }
+
+ public static Attachment newDecodedAttachment(
+ ComplexValueForeignKey complexValueFk, byte[] decodedData) {
+ return newDecodedAttachment(complexValueFk, null, null, null, decodedData,
+ null, null);
+ }
+
+ public static Attachment newDecodedAttachment(
+ String url, String name, String type, byte[] decodedData,
+ Date timeStamp, Integer flags)
+ {
+ return newDecodedAttachment(INVALID_COMPLEX_VALUE_ID, url, name, type,
+ decodedData, timeStamp, flags);
+ }
+
+ public static Attachment newDecodedAttachment(
+ ComplexValueForeignKey complexValueFk, String url, String name,
+ String type, byte[] decodedData, Date timeStamp, Integer flags)
+ {
+ return new AttachmentImpl(INVALID_ID, complexValueFk, url, name, type,
+ null, timeStamp, flags, decodedData);
}
@@ -235,10 +270,11 @@ public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
private byte[] _data;
private Date _timeStamp;
private Integer _flags;
+ private byte[] _decodedData;
private AttachmentImpl(int id, ComplexValueForeignKey complexValueFk,
String url, String name, String type, byte[] data,
- Date timeStamp, Integer flags)
+ Date timeStamp, Integer flags, byte[] decodedData)
{
super(id, complexValueFk);
_url = url;
@@ -247,14 +283,31 @@ public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
_data = data;
_timeStamp = timeStamp;
_flags = flags;
+ _decodedData = decodedData;
}
public byte[] getFileData() {
+ if((_data == null) && (_decodedData != null)) {
+ _data = encodeData();
+ }
return _data;
}
public void setFileData(byte[] data) {
_data = data;
+ _decodedData = null;
+ }
+
+ public byte[] getDecodedFileData() throws IOException {
+ if((_decodedData == null) && (_data != null)) {
+ _decodedData = decodeData();
+ }
+ return _decodedData;
+ }
+
+ public void setDecodedFileData(byte[] data) {
+ _decodedData = data;
+ _data = null;
}
public String getFileName() {
@@ -313,6 +366,72 @@ public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
+ ", " + getFileTimeStamp() + ", " + getFileFlags() + ", " +
ByteUtil.toHexString(getFileData());
}
+
+ /**
+ * Decodes the raw attachment file data to get the _actual_ content.
+ */
+ private byte[] decodeData() throws IOException {
+
+ if(_data.length < 8) {
+ // nothing we can do
+ throw new IOException("Unknown encoded attachment data format");
+ }
+
+ // read initial header info
+ ByteBuffer bb = PageChannel.wrap(_data);
+ int typeFlag = bb.getInt();
+ int dataLen = bb.getInt();
+
+ DataInputStream contentStream = null;
+ try {
+ InputStream bin = new ByteArrayInputStream(
+ _data, 8, _data.length - 8);
+
+ if(typeFlag == DATA_TYPE_RAW) {
+ // nothing else to do
+ } else if(typeFlag == DATA_TYPE_COMPRESSED) {
+ // actual content is deflate compressed
+ bin = new InflaterInputStream(bin);
+ } else {
+ throw new IOException(
+ "Unknown encoded attachment data type " + typeFlag);
+ }
+
+ contentStream = new DataInputStream(bin);
+
+ // header is the "file extension" of the data. no clue why we need
+ // that again since it's already a separate field in the attachment
+ // table. just skip it
+ byte[] tmpBytes = new byte[4];
+ contentStream.readFully(tmpBytes);
+ int headerLen = PageChannel.wrap(tmpBytes).getInt();
+ contentStream.skipBytes(headerLen - 4);
+
+ // calculate actual data length and read it (note, header length
+ // includes the bytes for the length)
+ tmpBytes = new byte[dataLen - headerLen];
+ contentStream.readFully(tmpBytes);
+
+ return tmpBytes;
+
+ } finally {
+ if(contentStream != null) {
+ try {
+ contentStream.close();
+ } catch(IOException e) {
+ // ignored
+ }
+ }
+ }
+ }
+
+ /**
+ * Encodes the actual attachment file data to get the raw, stored format.
+ */
+ private byte[] encodeData() {
+ // FIXME, writeme
+ throw new UnsupportedOperationException();
+ }
}
-
+
}
diff --git a/src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java b/src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java
index 80735e3..170a92f 100644
--- a/src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java
+++ b/src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java
@@ -215,6 +215,24 @@ public class ComplexValueForeignKey extends Number
return a;
}
+ public Attachment addDecodedAttachment(byte[] decodedData)
+ throws IOException
+ {
+ return addDecodedAttachment(null, null, null, decodedData, null, null);
+ }
+
+ public Attachment addDecodedAttachment(
+ String url, String name, String type, byte[] decodedData,
+ Date timeStamp, Integer flags)
+ throws IOException
+ {
+ reset();
+ Attachment a = AttachmentColumnInfo.newDecodedAttachment(
+ this, url, name, type, decodedData, timeStamp, flags);
+ getAttachmentInfo().addValue(a);
+ return a;
+ }
+
public Attachment updateAttachment(Attachment attachment)
throws IOException
{
diff --git a/test/src/java/com/healthmarketscience/jackcess/ComplexColumnTest.java b/test/src/java/com/healthmarketscience/jackcess/ComplexColumnTest.java
index 019dd74..6dbcbed 100644
--- a/test/src/java/com/healthmarketscience/jackcess/ComplexColumnTest.java
+++ b/test/src/java/com/healthmarketscience/jackcess/ComplexColumnTest.java
@@ -367,11 +367,12 @@ public class ComplexColumnTest extends TestCase
assertEquals(fileNames.length, attachments.size());
for(int i = 0; i < fileNames.length; ++i) {
String fname = fileNames[i];
- byte[] dataBytes = getFileBytes(fname);
Attachment a = attachments.get(i);
assertEquals(fname, a.getFileName());
assertEquals("txt", a.getFileType());
- assertTrue(Arrays.equals(dataBytes, a.getFileData()));
+ assertTrue(Arrays.equals(getFileBytes(fname), a.getFileData()));
+ assertTrue(Arrays.equals(getDecodedFileBytes(fname),
+ a.getDecodedFileData()));
}
}
}
@@ -430,17 +431,41 @@ public class ComplexColumnTest extends TestCase
throw new RuntimeException("unexpected bytes");
}
+ private static byte[] getDecodedFileBytes(String fname) throws Exception
+ {
+ if("test_data.txt".equals(fname)) {
+ return TEST_DEC_BYTES;
+ }
+ if("test_data2.txt".equals(fname)) {
+ return TEST2_DEC_BYTES;
+ }
+ throw new RuntimeException("unexpected bytes");
+ }
+
private static byte b(int i) { return (byte)i; }
+ private static byte[] getAsciiBytes(String str) {
+ try {
+ return str.getBytes("US-ASCII");
+ } catch(Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
private static final byte[] TEST_BYTES = new byte[] {
b(0x01),b(0x00),b(0x00),b(0x00),b(0x3A),b(0x00),b(0x00),b(0x00),b(0x78),b(0x5E),b(0x13),b(0x61),b(0x60),b(0x60),b(0x60),b(0x04),b(0x62),b(0x16),b(0x20),b(0x2E),b(0x61),b(0xA8),b(0x00),b(0x62),
b(0x20),b(0x9D),b(0x91),b(0x59),b(0xAC),b(0x00),b(0x44),b(0xC5),b(0xF9),b(0xB9),b(0xA9),b(0x0A),b(0x25),b(0xA9),b(0xC5),b(0x25),b(0x0A),b(0x29),b(0x89),b(0x25),b(0x89),b(0x0A),b(0x69),b(0xF9),
b(0x45),b(0x0A),b(0x89),b(0x25),b(0x25),b(0x89),b(0xC9),b(0x19),b(0xB9),b(0xA9),b(0x79),b(0x25),b(0x7A),b(0x00),b(0x52),b(0xA9),b(0x0F),b(0x7A)
};
+
+ private static final byte[] TEST_DEC_BYTES = getAsciiBytes("this is some test data for attachment.");
private static final byte[] TEST2_BYTES = new byte[] {
b(0x01),b(0x00),b(0x00),b(0x00),b(0x3F),b(0x00),b(0x00),b(0x00),b(0x78),b(0x5E),b(0x13),b(0x61),b(0x60),b(0x60),b(0x60),b(0x04),b(0x62),b(0x16),b(0x20),b(0x2E),b(0x61),b(0xA8),b(0x00),b(0x62),
b(0x20),b(0x9D),b(0x91),b(0x59),b(0xAC),b(0x00),b(0x44),b(0xC5),b(0xF9),b(0xB9),b(0xA9),b(0x0A),b(0xB9),b(0xF9),b(0x45),b(0xA9),b(0x0A),b(0x25),b(0xA9),b(0xC5),b(0x25),b(0x0A),b(0x29),b(0x89),
b(0x25),b(0x89),b(0x0A),b(0x69),b(0xF9),b(0x45),b(0x0A),b(0x89),b(0x25),b(0x25),b(0x89),b(0xC9),b(0x19),b(0xB9),b(0xA9),b(0x79),b(0x25),b(0x7A),b(0x00),b(0xA5),b(0x0B),b(0x11),b(0x4D)
};
+
+ private static final byte[] TEST2_DEC_BYTES = getAsciiBytes("this is some more test data for attachment.");
+
}