1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.healthmarketscience.jackcess.impl;
18
19 import java.io.ByteArrayInputStream;
20 import java.io.Closeable;
21 import java.io.FileInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.nio.ByteBuffer;
26 import java.nio.charset.Charset;
27 import java.nio.charset.StandardCharsets;
28 import java.sql.Blob;
29 import java.sql.SQLException;
30 import java.sql.SQLFeatureNotSupportedException;
31 import java.text.Normalizer;
32 import java.util.EnumSet;
33 import java.util.Set;
34 import java.util.regex.Pattern;
35
36 import com.healthmarketscience.jackcess.DataType;
37 import com.healthmarketscience.jackcess.util.OleBlob;
38 import static com.healthmarketscience.jackcess.util.OleBlob.*;
39 import org.apache.commons.lang3.builder.ToStringBuilder;
40
41
42
43
44
45
46
47 public class OleUtil
48 {
49
50
51
52
53 interface CompoundPackageFactory
54 {
55 public ContentImpl createCompoundPackageContent(
56 OleBlobImpl blob, String prettyName, String className, String typeName,
57 ByteBuffer blobBb, int dataBlockLen);
58 }
59
60 private static final int PACKAGE_SIGNATURE = 0x1C15;
61 private static final Charset OLE_CHARSET = StandardCharsets.US_ASCII;
62 private static final Charset OLE_UTF_CHARSET = StandardCharsets.UTF_16LE;
63 private static final byte[] COMPOUND_STORAGE_SIGNATURE =
64 {(byte)0xd0,(byte)0xcf,(byte)0x11,(byte)0xe0,
65 (byte)0xa1,(byte)0xb1,(byte)0x1a,(byte)0xe1};
66 private static final String SIMPLE_PACKAGE_TYPE = "Package";
67 private static final int PACKAGE_OBJECT_TYPE = 0x02;
68 private static final int OLE_VERSION = 0x0501;
69 private static final int OLE_FORMAT = 0x02;
70 private static final int PACKAGE_STREAM_SIGNATURE = 0x02;
71 private static final int PS_EMBEDDED_FILE = 0x030000;
72 private static final int PS_LINKED_FILE = 0x010000;
73 private static final Set<ContentType> WRITEABLE_TYPES = EnumSet.of(
74 ContentType.LINK, ContentType.SIMPLE_PACKAGE, ContentType.OTHER);
75 private static final byte[] NO_DATA = new byte[0];
76 private static final int LINK_HEADER = 0x01;
77 private static final byte[] PACKAGE_FOOTER = {
78 0x01, 0x05, 0x00, 0x00, 0x00, 0x00,
79 0x00, 0x00, 0x01, (byte)0xAD, 0x05, (byte)0xFE
80 };
81
82
83 private static final Pattern UNICODE_ACCENT_PATTERN =
84 Pattern.compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+");
85
86 private static final CompoundPackageFactory COMPOUND_FACTORY;
87
88 static {
89 CompoundPackageFactory compoundFactory = null;
90 try {
91 compoundFactory = (CompoundPackageFactory)
92 Class.forName("com.healthmarketscience.jackcess.impl.CompoundOleUtil")
93 .newInstance();
94 } catch(Throwable t) {
95
96 }
97 COMPOUND_FACTORY = compoundFactory;
98 }
99
100
101
102
103
104 public static OleBlob parseBlob(byte[] bytes) {
105 return new OleBlobImpl(bytes);
106 }
107
108
109
110
111 public static OleBlob createBlob(Builder oleBuilder)
112 throws IOException
113 {
114 try {
115
116 if(!WRITEABLE_TYPES.contains(oleBuilder.getType())) {
117 throw new IllegalArgumentException(
118 "Cannot currently create ole values of type " +
119 oleBuilder.getType());
120 }
121
122 long contentLen = oleBuilder.getContentLength();
123 byte[] contentBytes = oleBuilder.getBytes();
124 InputStream contentStream = oleBuilder.getStream();
125 byte[] packageStreamHeader = NO_DATA;
126 byte[] packageStreamFooter = NO_DATA;
127
128 switch(oleBuilder.getType()) {
129 case LINK:
130 packageStreamHeader = writePackageStreamHeader(oleBuilder);
131
132
133 contentBytes = getZeroTermStrBytes(oleBuilder.getFilePath());
134 contentLen = contentBytes.length;
135 break;
136
137 case SIMPLE_PACKAGE:
138 packageStreamHeader = writePackageStreamHeader(oleBuilder);
139 packageStreamFooter = writePackageStreamFooter(oleBuilder);
140 break;
141
142 case OTHER:
143
144 break;
145 default:
146 throw new RuntimeException("unexpected type " + oleBuilder.getType());
147 }
148
149 long payloadLen = packageStreamHeader.length + packageStreamFooter.length +
150 contentLen;
151 byte[] packageHeader = writePackageHeader(oleBuilder, payloadLen);
152
153 long totalOleLen = packageHeader.length + PACKAGE_FOOTER.length +
154 payloadLen;
155 if(totalOleLen > DataType.OLE.getMaxSize()) {
156 throw new IllegalArgumentException("Content size of " + totalOleLen +
157 " is too large for ole column");
158 }
159
160 byte[] oleBytes = new byte[(int)totalOleLen];
161 ByteBuffer bb = PageChannel.wrap(oleBytes);
162 bb.put(packageHeader);
163 bb.put(packageStreamHeader);
164
165 if(contentLen > 0L) {
166 if(contentBytes != null) {
167 bb.put(contentBytes);
168 } else {
169 byte[] buf = new byte[8192];
170 int numBytes = 0;
171 while((numBytes = contentStream.read(buf)) >= 0) {
172 bb.put(buf, 0, numBytes);
173 }
174 }
175 }
176
177 bb.put(packageStreamFooter);
178 bb.put(PACKAGE_FOOTER);
179
180 return parseBlob(oleBytes);
181
182 } finally {
183 ByteUtil.closeQuietly(oleBuilder.getStream());
184 }
185 }
186
187 private static byte[] writePackageHeader(Builder oleBuilder,
188 long contentLen) {
189
190 byte[] prettyNameBytes = getZeroTermStrBytes(oleBuilder.getPrettyName());
191 String className = oleBuilder.getClassName();
192 String typeName = oleBuilder.getTypeName();
193 if(className == null) {
194 className = typeName;
195 } else if(typeName == null) {
196 typeName = className;
197 }
198 byte[] classNameBytes = getZeroTermStrBytes(className);
199 byte[] typeNameBytes = getZeroTermStrBytes(typeName);
200
201 int packageHeaderLen = 20 + prettyNameBytes.length + classNameBytes.length;
202
203 int oleHeaderLen = 24 + typeNameBytes.length;
204
205 byte[] headerBytes = new byte[packageHeaderLen + oleHeaderLen];
206
207 ByteBuffer bb = PageChannel.wrap(headerBytes);
208
209
210 bb.putShort((short)PACKAGE_SIGNATURE);
211 bb.putShort((short)packageHeaderLen);
212 bb.putInt(PACKAGE_OBJECT_TYPE);
213 bb.putShort((short)prettyNameBytes.length);
214 bb.putShort((short)classNameBytes.length);
215 int prettyNameOff = bb.position() + 8;
216 bb.putShort((short)prettyNameOff);
217 bb.putShort((short)(prettyNameOff + prettyNameBytes.length));
218 bb.putInt(-1);
219 bb.put(prettyNameBytes);
220 bb.put(classNameBytes);
221
222
223 bb.putInt(OLE_VERSION);
224 bb.putInt(OLE_FORMAT);
225 bb.putInt(typeNameBytes.length);
226 bb.put(typeNameBytes);
227 bb.putLong(0L);
228 bb.putInt((int)contentLen);
229
230 return headerBytes;
231 }
232
233 private static byte[] writePackageStreamHeader(Builder oleBuilder) {
234
235 byte[] fileNameBytes = getZeroTermStrBytes(oleBuilder.getFileName());
236 byte[] filePathBytes = getZeroTermStrBytes(oleBuilder.getFilePath());
237
238 int headerLen = 6 + fileNameBytes.length + filePathBytes.length;
239
240 if(oleBuilder.getType() == ContentType.SIMPLE_PACKAGE) {
241
242 headerLen += 8 + filePathBytes.length;
243
244 } else {
245
246 headerLen += 2;
247 }
248
249 byte[] headerBytes = new byte[headerLen];
250 ByteBuffer bb = PageChannel.wrap(headerBytes);
251 bb.putShort((short)PACKAGE_STREAM_SIGNATURE);
252 bb.put(fileNameBytes);
253 bb.put(filePathBytes);
254
255 if(oleBuilder.getType() == ContentType.SIMPLE_PACKAGE) {
256 bb.putInt(PS_EMBEDDED_FILE);
257 bb.putInt(filePathBytes.length);
258 bb.put(filePathBytes, 0, filePathBytes.length);
259 bb.putInt((int) oleBuilder.getContentLength());
260 } else {
261 bb.putInt(PS_LINKED_FILE);
262 bb.putShort((short)LINK_HEADER);
263 }
264
265 return headerBytes;
266 }
267
268 private static byte[] writePackageStreamFooter(Builder oleBuilder) {
269
270
271 byte[] fileNameBytes = oleBuilder.getFileName().getBytes(OLE_UTF_CHARSET);
272 byte[] filePathBytes = oleBuilder.getFilePath().getBytes(OLE_UTF_CHARSET);
273
274 int footerLen = 12 + (filePathBytes.length * 2) + fileNameBytes.length;
275
276 byte[] footerBytes = new byte[footerLen];
277 ByteBuffer bb = PageChannel.wrap(footerBytes);
278
279 bb.putInt(filePathBytes.length/2);
280 bb.put(filePathBytes);
281 bb.putInt(fileNameBytes.length/2);
282 bb.put(fileNameBytes);
283 bb.putInt(filePathBytes.length/2);
284 bb.put(filePathBytes);
285
286 return footerBytes;
287 }
288
289
290
291
292 private static ContentImpl parseContent(OleBlobImpl blob)
293 throws IOException
294 {
295 ByteBuffer bb = PageChannel.wrap(blob.getBytes());
296
297 if((bb.remaining() < 2) || (bb.getShort() != PACKAGE_SIGNATURE)) {
298 return new UnknownContentImpl(blob);
299 }
300
301
302 int headerSize = bb.getShort();
303 bb.getInt();
304 int prettyNameLen = bb.getShort();
305 int classNameLen = bb.getShort();
306 int prettyNameOff = bb.getShort();
307 int classNameOff = bb.getShort();
308 bb.getInt();
309 String prettyName = readStr(bb, prettyNameOff, prettyNameLen);
310 String className = readStr(bb, classNameOff, classNameLen);
311 bb.position(headerSize);
312
313
314 int oleVer = bb.getInt();
315 bb.getInt();
316
317 if(oleVer != OLE_VERSION) {
318 return new UnknownContentImpl(blob);
319 }
320
321 int typeNameLen = bb.getInt();
322 String typeName = readStr(bb, bb.position(), typeNameLen);
323 bb.getLong();
324 int dataBlockLen = bb.getInt();
325 int dataBlockPos = bb.position();
326
327
328 if(SIMPLE_PACKAGE_TYPE.equalsIgnoreCase(typeName)) {
329 return createSimplePackageContent(
330 blob, prettyName, className, typeName, bb, dataBlockLen);
331 }
332
333
334
335 if((COMPOUND_FACTORY != null) &&
336 (bb.remaining() >= COMPOUND_STORAGE_SIGNATURE.length) &&
337 ByteUtil.matchesRange(bb, bb.position(), COMPOUND_STORAGE_SIGNATURE)) {
338 return COMPOUND_FACTORY.createCompoundPackageContent(
339 blob, prettyName, className, typeName, bb, dataBlockLen);
340 }
341
342
343
344 return new OtherContentImpl(blob, prettyName, className,
345 typeName, dataBlockPos, dataBlockLen);
346 }
347
348 private static ContentImpl createSimplePackageContent(
349 OleBlobImpl blob, String prettyName, String className, String typeName,
350 ByteBuffer blobBb, int dataBlockLen) {
351
352 int dataBlockPos = blobBb.position();
353 ByteBuffer bb = PageChannel.narrowBuffer(blobBb, dataBlockPos,
354 dataBlockPos + dataBlockLen);
355
356 int packageSig = bb.getShort();
357 if(packageSig != PACKAGE_STREAM_SIGNATURE) {
358 return new OtherContentImpl(blob, prettyName, className,
359 typeName, dataBlockPos, dataBlockLen);
360 }
361
362 String fileName = readZeroTermStr(bb);
363 String filePath = readZeroTermStr(bb);
364 int packageType = bb.getInt();
365
366 if(packageType == PS_EMBEDDED_FILE) {
367
368 int localFilePathLen = bb.getInt();
369 String localFilePath = readStr(bb, bb.position(), localFilePathLen);
370 int dataLen = bb.getInt();
371 int dataPos = bb.position();
372 bb.position(dataLen + dataPos);
373
374
375
376
377
378 int strNum = 0;
379 while(true) {
380
381 int rem = bb.remaining();
382 if(rem < 4) {
383 break;
384 }
385
386 int strLen = bb.getInt();
387 String remStr = readStr(bb, bb.position(), strLen * 2, OLE_UTF_CHARSET);
388
389 switch(strNum) {
390 case 0:
391 localFilePath = remStr;
392 break;
393 case 1:
394 fileName = remStr;
395 break;
396 case 2:
397 filePath = remStr;
398 break;
399 default:
400
401 }
402
403 ++strNum;
404 }
405
406 return new SimplePackageContentImpl(
407 blob, prettyName, className, typeName, dataPos, dataLen,
408 fileName, filePath, localFilePath);
409 }
410
411 if(packageType == PS_LINKED_FILE) {
412
413 bb.getShort();
414 String linkStr = readZeroTermStr(bb);
415
416 return new LinkContentImpl(blob, prettyName, className, typeName,
417 fileName, linkStr, filePath);
418 }
419
420 return new OtherContentImpl(blob, prettyName, className,
421 typeName, dataBlockPos, dataBlockLen);
422 }
423
424 private static String readStr(ByteBuffer bb, int off, int len) {
425 return readStr(bb, off, len, OLE_CHARSET);
426 }
427
428 private static String readZeroTermStr(ByteBuffer bb) {
429 int off = bb.position();
430 while(bb.hasRemaining()) {
431 byte b = bb.get();
432 if(b == 0) {
433 break;
434 }
435 }
436 int len = bb.position() - off;
437 return readStr(bb, off, len);
438 }
439
440 private static String readStr(ByteBuffer bb, int off, int len,
441 Charset charset) {
442 String str = new String(bb.array(), off, len, charset);
443 bb.position(off + len);
444 if(str.charAt(str.length() - 1) == '\0') {
445 str = str.substring(0, str.length() - 1);
446 }
447 return str;
448 }
449
450 private static byte[] getZeroTermStrBytes(String str) {
451
452
453
454
455
456 str = Normalizer.normalize(str, Normalizer.Form.NFD);
457
458 str = UNICODE_ACCENT_PATTERN.matcher(str).replaceAll("");
459
460 str = Normalizer.normalize(str, Normalizer.Form.NFC);
461
462 return (str + '\0').getBytes(OLE_CHARSET);
463 }
464
465
466 static final class OleBlobImpl implements OleBlob, ColumnImpl.InMemoryBlob
467 {
468 private byte[] _bytes;
469 private ContentImpl _content;
470
471 private OleBlobImpl(byte[] bytes) {
472 _bytes = bytes;
473 }
474
475 @Override
476 public void writeTo(OutputStream out) throws IOException {
477 out.write(_bytes);
478 }
479
480 @Override
481 public Content getContent() throws IOException {
482 if(_content == null) {
483 _content = parseContent(this);
484 }
485 return _content;
486 }
487
488 @Override
489 public InputStream getBinaryStream() throws SQLException {
490 return new ByteArrayInputStream(_bytes);
491 }
492
493 @Override
494 public InputStream getBinaryStream(long pos, long len)
495 throws SQLException
496 {
497 return new ByteArrayInputStream(_bytes, fromJdbcOffset(pos), (int)len);
498 }
499
500 @Override
501 public long length() throws SQLException {
502 return _bytes.length;
503 }
504
505 @Override
506 public byte[] getBytes() throws IOException {
507 if(_bytes == null) {
508 throw new IOException("blob is closed");
509 }
510 return _bytes;
511 }
512
513 @Override
514 public byte[] getBytes(long pos, int len) throws SQLException {
515 return ByteUtil.copyOf(_bytes, fromJdbcOffset(pos), len);
516 }
517
518 @Override
519 public long position(byte[] pattern, long start) throws SQLException {
520 int pos = ByteUtil.findRange(PageChannel.wrap(_bytes),
521 fromJdbcOffset(start), pattern);
522 return((pos >= 0) ? toJdbcOffset(pos) : pos);
523 }
524
525 @Override
526 public long position(Blob pattern, long start) throws SQLException {
527 return position(pattern.getBytes(1L, (int)pattern.length()), start);
528 }
529
530 @Override
531 public OutputStream setBinaryStream(long position) throws SQLException {
532 throw new SQLFeatureNotSupportedException();
533 }
534
535 @Override
536 public void truncate(long len) throws SQLException {
537 throw new SQLFeatureNotSupportedException();
538 }
539
540 @Override
541 public int setBytes(long pos, byte[] bytes) throws SQLException {
542 throw new SQLFeatureNotSupportedException();
543 }
544
545 @Override
546 public int setBytes(long pos, byte[] bytes, int offset, int lesn)
547 throws SQLException {
548 throw new SQLFeatureNotSupportedException();
549 }
550
551 @Override
552 public void free() {
553 close();
554 }
555
556 @Override
557 public void close() {
558 _bytes = null;
559 ByteUtil.closeQuietly(_content);
560 _content = null;
561 }
562
563 private static int toJdbcOffset(int off) {
564 return off + 1;
565 }
566
567 private static int fromJdbcOffset(long off) {
568 return (int)off - 1;
569 }
570
571 @Override
572 public String toString() {
573 ToStringBuilder sb = CustomToStringStyle.builder(this);
574 if(_content != null) {
575 sb.append("content", _content);
576 } else {
577 sb.append("bytes", _bytes);
578 sb.append("content", "(uninitialized)");
579 }
580 return sb.toString();
581 }
582 }
583
584 static abstract class ContentImpl implements Content, Closeable
585 {
586 protected final OleBlobImpl _blob;
587
588 protected ContentImpl(OleBlobImpl blob) {
589 _blob = blob;
590 }
591
592 @Override
593 public OleBlobImpl getBlob() {
594 return _blob;
595 }
596
597 protected byte[] getBytes() throws IOException {
598 return getBlob().getBytes();
599 }
600
601 @Override
602 public void close() {
603
604 }
605
606 protected ToStringBuilder toString(ToStringBuilder sb) {
607 sb.append("type", getType());
608 return sb;
609 }
610 }
611
612 static abstract class EmbeddedContentImpl extends ContentImpl
613 implements EmbeddedContent
614 {
615 private final int _position;
616 private final int _length;
617
618 protected EmbeddedContentImpl(OleBlobImpl blob, int position, int length)
619 {
620 super(blob);
621 _position = position;
622 _length = length;
623 }
624
625 @Override
626 public long length() {
627 return _length;
628 }
629
630 @Override
631 public InputStream getStream() throws IOException {
632 return new ByteArrayInputStream(getBytes(), _position, _length);
633 }
634
635 @Override
636 public void writeTo(OutputStream out) throws IOException {
637 out.write(getBytes(), _position, _length);
638 }
639
640 @Override
641 protected ToStringBuilder toString(ToStringBuilder sb) {
642 super.toString(sb);
643 if(_position >= 0) {
644 sb.append("content", ByteBuffer.wrap(_blob._bytes, _position, _length));
645 }
646 return sb;
647 }
648 }
649
650 static abstract class EmbeddedPackageContentImpl
651 extends EmbeddedContentImpl
652 implements PackageContent
653 {
654 private final String _prettyName;
655 private final String _className;
656 private final String _typeName;
657
658 protected EmbeddedPackageContentImpl(
659 OleBlobImpl blob, String prettyName, String className,
660 String typeName, int position, int length)
661 {
662 super(blob, position, length);
663 _prettyName = prettyName;
664 _className = className;
665 _typeName = typeName;
666 }
667
668 @Override
669 public String getPrettyName() {
670 return _prettyName;
671 }
672
673 @Override
674 public String getClassName() {
675 return _className;
676 }
677
678 @Override
679 public String getTypeName() {
680 return _typeName;
681 }
682
683 @Override
684 protected ToStringBuilder toString(ToStringBuilder sb) {
685 sb.append("prettyName", _prettyName)
686 .append("className", _className)
687 .append("typeName", _typeName);
688 super.toString(sb);
689 return sb;
690 }
691 }
692
693 private static final class LinkContentImpl
694 extends EmbeddedPackageContentImpl
695 implements LinkContent
696 {
697 private final String _fileName;
698 private final String _linkPath;
699 private final String _filePath;
700
701 private LinkContentImpl(OleBlobImpl blob, String prettyName,
702 String className, String typeName,
703 String fileName, String linkPath,
704 String filePath)
705 {
706 super(blob, prettyName, className, typeName, -1, -1);
707 _fileName = fileName;
708 _linkPath = linkPath;
709 _filePath = filePath;
710 }
711
712 @Override
713 public ContentType getType() {
714 return ContentType.LINK;
715 }
716
717 @Override
718 public String getFileName() {
719 return _fileName;
720 }
721
722 @Override
723 public String getLinkPath() {
724 return _linkPath;
725 }
726
727 @Override
728 public String getFilePath() {
729 return _filePath;
730 }
731
732 @Override
733 public InputStream getLinkStream() throws IOException {
734 return new FileInputStream(getLinkPath());
735 }
736
737 @Override
738 public String toString() {
739 return toString(CustomToStringStyle.builder(this))
740 .append("fileName", _fileName)
741 .append("linkPath", _linkPath)
742 .append("filePath", _filePath)
743 .toString();
744 }
745 }
746
747 private static final class SimplePackageContentImpl
748 extends EmbeddedPackageContentImpl
749 implements SimplePackageContent
750 {
751 private final String _fileName;
752 private final String _filePath;
753 private final String _localFilePath;
754
755 private SimplePackageContentImpl(OleBlobImpl blob, String prettyName,
756 String className, String typeName,
757 int position, int length,
758 String fileName, String filePath,
759 String localFilePath)
760 {
761 super(blob, prettyName, className, typeName, position, length);
762 _fileName = fileName;
763 _filePath = filePath;
764 _localFilePath = localFilePath;
765 }
766
767 @Override
768 public ContentType getType() {
769 return ContentType.SIMPLE_PACKAGE;
770 }
771
772 @Override
773 public String getFileName() {
774 return _fileName;
775 }
776
777 @Override
778 public String getFilePath() {
779 return _filePath;
780 }
781
782 @Override
783 public String getLocalFilePath() {
784 return _localFilePath;
785 }
786
787 @Override
788 public String toString() {
789 return toString(CustomToStringStyle.builder(this))
790 .append("fileName", _fileName)
791 .append("filePath", _filePath)
792 .append("localFilePath", _localFilePath)
793 .toString();
794 }
795 }
796
797 private static final class OtherContentImpl
798 extends EmbeddedPackageContentImpl
799 implements OtherContent
800 {
801 private OtherContentImpl(
802 OleBlobImpl blob, String prettyName, String className,
803 String typeName, int position, int length)
804 {
805 super(blob, prettyName, className, typeName, position, length);
806 }
807
808 @Override
809 public ContentType getType() {
810 return ContentType.OTHER;
811 }
812
813 @Override
814 public String toString() {
815 return toString(CustomToStringStyle.builder(this))
816 .toString();
817 }
818 }
819
820 private static final class UnknownContentImpl extends ContentImpl
821 {
822 private UnknownContentImpl(OleBlobImpl blob) {
823 super(blob);
824 }
825
826 @Override
827 public ContentType getType() {
828 return ContentType.UNKNOWN;
829 }
830
831 @Override
832 public String toString() {
833 return toString(CustomToStringStyle.builder(this))
834 .append("content", _blob._bytes)
835 .toString();
836 }
837 }
838
839 }