1 // =================================================================================================
2 // ADOBE SYSTEMS INCORPORATED
3 // Copyright 2002-2008 Adobe Systems Incorporated
4 // All Rights Reserved
5 //
6 // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
7 // of the Adobe license agreement accompanying it.
8 // =================================================================================================
9
10 #include "InDesign_Handler.hpp"
11
12 using namespace std;
13
14 // =================================================================================================
15 /// \file InDesign_Handler.cpp
16 /// \brief File format handler for InDesign files.
17 ///
18 /// This header ...
19 ///
20 /// The layout of an InDesign file in terms of the Basic_MetaHandler model is:
21 ///
22 /// \li The front of the file. This is everything up to the XMP contiguous object section. The file
23 /// starts with a pair of master pages, followed by the data pages, followed by contiguous object
24 /// sections, finished with padding to a page boundary.
25 ///
26 /// \li A prefix for the XMP section. This is the contiguous object header. The offset is
27 /// (this->packetInfo.offset - this->xmpPrefixSize).
28 ///
29 /// \li The XMP packet. The offset is this->packetInfo.offset.
30 ///
31 /// \li A suffix for the XMP section. This is the contiguous object header. The offset is
32 /// (this->packetInfo.offset + this->packetInfo.length).
33 ///
34 /// \li Trailing file content. This is the contiguous objects that follow the XMP. The offset is
35 /// (this->packetInfo.offset + this->packetInfo.length + this->xmpSuffixSize).
36 ///
37 /// \li The back of the file. This is the final padding to a page boundary. The offset is
38 /// (this->packetInfo.offset + this->packetInfo.length + this->xmpSuffixSize + this->trailingContentSize).
39 ///
40 // =================================================================================================
41
42 // *** Add PutXMP overrides that throw if the file does not contain XMP.
43
44 #ifndef TraceInDesignHandler
45 #define TraceInDesignHandler 0
46 #endif
47
48 enum { kInDesignGUIDSize = 16 };
49
50 struct InDesignMasterPage {
51 XMP_Uns8 fGUID [kInDesignGUIDSize];
52 XMP_Uns8 fMagicBytes [8];
53 XMP_Uns8 fObjectStreamEndian;
54 XMP_Uns8 fIrrelevant1 [239];
55 XMP_Uns64 fSequenceNumber;
56 XMP_Uns8 fIrrelevant2 [8];
57 XMP_Uns32 fFilePages;
58 XMP_Uns8 fIrrelevant3 [3812];
59 };
60
61 enum {
62 kINDD_PageSize = 4096,
63 kINDD_PageMask = (kINDD_PageSize - 1),
64 kINDD_LittleEndian = 1,
65 kINDD_BigEndian = 2 };
66
67 struct InDesignContigObjMarker {
68 XMP_Uns8 fGUID [kInDesignGUIDSize];
69 XMP_Uns32 fObjectUID;
70 XMP_Uns32 fObjectClassID;
71 XMP_Uns32 fStreamLength;
72 XMP_Uns32 fChecksum;
73 };
74
75 static const XMP_Uns8 * kINDD_MasterPageGUID =
76 (const XMP_Uns8 *) "\x06\x06\xED\xF5\xD8\x1D\x46\xE5\xBD\x31\xEF\xE7\xFE\x74\xB7\x1D";
77
78 static const XMP_Uns8 * kINDDContigObjHeaderGUID =
79 (const XMP_Uns8 *) "\xDE\x39\x39\x79\x51\x88\x4B\x6C\x8E\x63\xEE\xF8\xAE\xE0\xDD\x38";
80
81 static const XMP_Uns8 * kINDDContigObjTrailerGUID =
82 (const XMP_Uns8 *) "\xFD\xCE\xDB\x70\xF7\x86\x4B\x4F\xA4\xD3\xC7\x28\xB3\x41\x71\x06";
83
84 // =================================================================================================
85 // InDesign_MetaHandlerCTor
86 // ========================
87
InDesign_MetaHandlerCTor(XMPFiles * parent)88 XMPFileHandler * InDesign_MetaHandlerCTor ( XMPFiles * parent )
89 {
90 return new InDesign_MetaHandler ( parent );
91
92 } // InDesign_MetaHandlerCTor
93
94 // =================================================================================================
95 // InDesign_CheckFormat
96 // ====================
97 //
98 // For InDesign we check that the pair of master pages begin with the 16 byte GUID.
99
InDesign_CheckFormat(XMP_FileFormat format,XMP_StringPtr filePath,LFA_FileRef fileRef,XMPFiles * parent)100 bool InDesign_CheckFormat ( XMP_FileFormat format,
101 XMP_StringPtr filePath,
102 LFA_FileRef fileRef,
103 XMPFiles * parent )
104 {
105 IgnoreParam(format); IgnoreParam(filePath); IgnoreParam(parent);
106 XMP_Assert ( format == kXMP_InDesignFile );
107 XMP_Assert ( strlen ( (const char *) kINDD_MasterPageGUID ) == kInDesignGUIDSize );
108
109 enum { kBufferSize = 2*kINDD_PageSize };
110 XMP_Uns8 buffer [kBufferSize];
111
112 XMP_Int64 filePos = 0;
113 XMP_Uns8 * bufPtr = buffer;
114 XMP_Uns8 * bufLimit = bufPtr + kBufferSize;
115
116 LFA_Seek ( fileRef, 0, SEEK_SET );
117 size_t bufLen = LFA_Read ( fileRef, buffer, kBufferSize );
118 if ( bufLen != kBufferSize ) return false;
119
120 if ( ! CheckBytes ( bufPtr, kINDD_MasterPageGUID, kInDesignGUIDSize ) ) return false;
121 if ( ! CheckBytes ( bufPtr+kINDD_PageSize, kINDD_MasterPageGUID, kInDesignGUIDSize ) ) return false;
122
123 return true;
124
125 } // InDesign_CheckFormat
126
127 // =================================================================================================
128 // InDesign_MetaHandler::InDesign_MetaHandler
129 // ==========================================
130
InDesign_MetaHandler(XMPFiles * _parent)131 InDesign_MetaHandler::InDesign_MetaHandler ( XMPFiles * _parent ) : streamBigEndian(0), xmpObjID(0), xmpClassID(0)
132 {
133 this->parent = _parent;
134 this->handlerFlags = kInDesign_HandlerFlags;
135 this->stdCharForm = kXMP_Char8Bit;
136
137 } // InDesign_MetaHandler::InDesign_MetaHandler
138
139 // =================================================================================================
140 // InDesign_MetaHandler::~InDesign_MetaHandler
141 // ===========================================
142
~InDesign_MetaHandler()143 InDesign_MetaHandler::~InDesign_MetaHandler()
144 {
145 // Nothing to do here.
146
147 } // InDesign_MetaHandler::~InDesign_MetaHandler
148
149 // =================================================================================================
150 // InDesign_MetaHandler::CacheFileData
151 // ===================================
152 //
153 // Look for the XMP in an InDesign database file. This is a paged database using 4K byte pages,
154 // followed by redundant "contiguous object streams". Each contiguous object stream is a copy of a
155 // database object stored as a contiguous byte stream. The XMP that we want is one of these.
156 //
157 // The first 2 pages of the database are alternating master pages. A generation number is used to
158 // select the active master page. The master page contains an offset to the start of the contiguous
159 // object streams. Each of the contiguous object streams contains a header and trailer, allowing
160 // fast motion from one stream to the next.
161 //
162 // There is no unique "what am I" tagging to the contiguous object streams, so we simply pick the
163 // first one that looks right. At present this is a 4 byte little endian packet size followed by the
164 // packet.
165
166 // ! Note that insertion of XMP is not allowed for InDesign, the XMP must be a contiguous copy of an
167 // ! internal database object. So we don't set the packet offset to an insertion point if not found.
168
CacheFileData()169 void InDesign_MetaHandler::CacheFileData()
170 {
171 LFA_FileRef fileRef = this->parent->fileRef;
172 XMP_PacketInfo & packetInfo = this->packetInfo;
173
174 IOBuffer ioBuf;
175 size_t dbPages;
176 XMP_Uns8 cobjEndian;
177
178 XMP_AbortProc abortProc = this->parent->abortProc;
179 void * abortArg = this->parent->abortArg;
180 const bool checkAbort = (abortProc != 0);
181
182 XMP_Assert ( kINDD_PageSize == sizeof(InDesignMasterPage) );
183 XMP_Assert ( kIOBufferSize >= (2 * kINDD_PageSize) );
184
185 this->containsXMP = false;
186
187 // ---------------------------------------------------------------------------------
188 // Figure out which master page is active and seek to the contiguous object portion.
189
190 {
191 ioBuf.filePos = 0;
192 ioBuf.ptr = ioBuf.limit; // Make sure RefillBuffer does a simple read.
193 LFA_Seek ( fileRef, ioBuf.filePos, SEEK_SET );
194 RefillBuffer ( fileRef, &ioBuf );
195 if ( ioBuf.len < (2 * kINDD_PageSize) ) XMP_Throw ( "GetMainPacket/ScanInDesignFile: Read failure", kXMPErr_ExternalFailure );
196
197 InDesignMasterPage * masters = (InDesignMasterPage *) ioBuf.ptr;
198 XMP_Uns64 seq0 = GetUns64LE ( (XMP_Uns8 *) &masters[0].fSequenceNumber );
199 XMP_Uns64 seq1 = GetUns64LE ( (XMP_Uns8 *) &masters[1].fSequenceNumber );
200
201 dbPages = GetUns32LE ( (XMP_Uns8 *) &masters[0].fFilePages );
202 cobjEndian = masters[0].fObjectStreamEndian;
203 if ( seq1 > seq0 ) {
204 dbPages = GetUns32LE ( (XMP_Uns8 *) &masters[1].fFilePages );
205 cobjEndian = masters[1].fObjectStreamEndian;
206 }
207 }
208
209 XMP_Assert ( ! this->streamBigEndian );
210 if ( cobjEndian == kINDD_BigEndian ) this->streamBigEndian = true;
211
212 // ---------------------------------------------------------------------------------------------
213 // Look for the XMP contiguous object stream. Most of the time there will be just one stream and
214 // it will be the XMP. So we might as well fill the whole buffer and not worry about reading too
215 // much and seeking back to the start of the following stream.
216
217 XMP_Int64 cobjPos = (XMP_Int64)dbPages * kINDD_PageSize; // ! Use a 64 bit multiply!
218 cobjPos -= (2 * sizeof(InDesignContigObjMarker)); // ! For the first pass in the loop.
219 XMP_Uns32 streamLength = 0; // ! For the first pass in the loop.
220
221 while ( true ) {
222
223 if ( checkAbort && abortProc(abortArg) ) {
224 XMP_Throw ( "InDesign_MetaHandler::LocateXMP - User abort", kXMPErr_UserAbort );
225 }
226
227 // Fetch the start of the next stream and check the contiguous object header.
228 // ! The writeable bit of fObjectClassID is ignored, we use the packet trailer flag.
229
230 cobjPos += streamLength + (2 * sizeof(InDesignContigObjMarker));
231 ioBuf.filePos = cobjPos;
232 ioBuf.ptr = ioBuf.limit; // Make sure RefillBuffer does a simple read.
233 LFA_Seek ( fileRef, ioBuf.filePos, SEEK_SET );
234 RefillBuffer ( fileRef, &ioBuf );
235 if ( ioBuf.len < (2 * sizeof(InDesignContigObjMarker)) ) break; // Too small, must be end of file.
236
237 const InDesignContigObjMarker * cobjHeader = (const InDesignContigObjMarker *) ioBuf.ptr;
238 if ( ! CheckBytes ( Uns8Ptr(&cobjHeader->fGUID), kINDDContigObjHeaderGUID, kInDesignGUIDSize ) ) break; // Not a contiguous object header.
239 this->xmpObjID = cobjHeader->fObjectUID; // Save these now while the buffer is good.
240 this->xmpClassID = cobjHeader->fObjectClassID;
241 streamLength = GetUns32LE ( (XMP_Uns8 *) &cobjHeader->fStreamLength );
242 ioBuf.ptr += sizeof ( InDesignContigObjMarker );
243
244 // See if this is the XMP stream. Only check for UTF-8, others get caught in fallback scanning.
245
246 if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) continue; // Too small, can't possibly be XMP.
247
248 XMP_Uns32 innerLength = GetUns32LE ( ioBuf.ptr );
249 if ( this->streamBigEndian ) innerLength = GetUns32BE ( ioBuf.ptr );
250 if ( innerLength != (streamLength - 4) ) continue; // Not legit XMP.
251 ioBuf.ptr += 4;
252
253 if ( ! CheckFileSpace ( fileRef, &ioBuf, kUTF8_PacketHeaderLen ) ) continue; // Too small, can't possibly be XMP.
254
255 if ( ! CheckBytes ( ioBuf.ptr, kUTF8_PacketStart, strlen((char*)kUTF8_PacketStart) ) ) continue;
256 ioBuf.ptr += strlen((char*)kUTF8_PacketStart);
257
258 XMP_Uns8 quote = *ioBuf.ptr;
259 if ( (quote != '\'') && (quote != '"') ) continue;
260 ioBuf.ptr += 1;
261 if ( *ioBuf.ptr != quote ) {
262 if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr("\xEF\xBB\xBF"), 3 ) ) continue;
263 ioBuf.ptr += 3;
264 }
265 if ( *ioBuf.ptr != quote ) continue;
266 ioBuf.ptr += 1;
267
268 if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr(" id="), 4 ) ) continue;
269 ioBuf.ptr += 4;
270 quote = *ioBuf.ptr;
271 if ( (quote != '\'') && (quote != '"') ) continue;
272 ioBuf.ptr += 1;
273 if ( ! CheckBytes ( ioBuf.ptr, kUTF8_PacketID, strlen((char*)kUTF8_PacketID) ) ) continue;
274 ioBuf.ptr += strlen((char*)kUTF8_PacketID);
275 if ( *ioBuf.ptr != quote ) continue;
276 ioBuf.ptr += 1;
277
278 // We've seen enough, it is the XMP. To fit the Basic_Handler model we need to compute the
279 // total size of remaining contiguous objects, the trailingContentSize.
280
281 this->xmpPrefixSize = sizeof(InDesignContigObjMarker) + 4;
282 this->xmpSuffixSize = sizeof(InDesignContigObjMarker);
283 packetInfo.offset = cobjPos + this->xmpPrefixSize;
284 packetInfo.length = innerLength;
285
286
287 XMP_Int64 tcStart = cobjPos + streamLength + (2 * sizeof(InDesignContigObjMarker));
288 while ( true ) {
289 if ( checkAbort && abortProc(abortArg) ) {
290 XMP_Throw ( "InDesign_MetaHandler::LocateXMP - User abort", kXMPErr_UserAbort );
291 }
292 cobjPos += streamLength + (2 * sizeof(InDesignContigObjMarker));
293 ioBuf.filePos = cobjPos;
294 ioBuf.ptr = ioBuf.limit; // Make sure RefillBuffer does a simple read.
295 LFA_Seek ( fileRef, ioBuf.filePos, SEEK_SET );
296 RefillBuffer ( fileRef, &ioBuf );
297 if ( ioBuf.len < sizeof(InDesignContigObjMarker) ) break; // Too small, must be end of file.
298 cobjHeader = (const InDesignContigObjMarker *) ioBuf.ptr;
299 if ( ! CheckBytes ( Uns8Ptr(&cobjHeader->fGUID), kINDDContigObjHeaderGUID, kInDesignGUIDSize ) ) break; // Not a contiguous object header.
300 streamLength = GetUns32LE ( (XMP_Uns8 *) &cobjHeader->fStreamLength );
301 }
302 this->trailingContentSize = cobjPos - tcStart;
303
304 #if TraceInDesignHandler
305 XMP_Uns32 pktOffset = (XMP_Uns32)this->packetInfo.offset;
306 printf ( "Found XMP in InDesign file, offsets:\n" );
307 printf ( " CObj head %X, XMP %X, CObj tail %X, file tail %X, padding %X\n",
308 (pktOffset - this->xmpPrefixSize), pktOffset, (pktOffset + this->packetInfo.length),
309 (pktOffset + this->packetInfo.length + this->xmpSuffixSize),
310 (pktOffset + this->packetInfo.length + this->xmpSuffixSize + (XMP_Uns32)this->trailingContentSize) );
311 #endif
312
313 this->containsXMP = true;
314 break;
315
316 }
317
318 if ( this->containsXMP ) {
319 this->xmpFileOffset = packetInfo.offset;
320 this->xmpFileSize = packetInfo.length;
321 ReadXMPPacket ( this );
322 }
323
324 } // InDesign_MetaHandler::CacheFileData
325
326 // =================================================================================================
327 // InDesign_MetaHandler::WriteXMPPrefix
328 // ====================================
329
WriteXMPPrefix()330 void InDesign_MetaHandler::WriteXMPPrefix()
331 {
332 // Write the contiguous object header and the 4 byte length of the XMP packet.
333
334 LFA_FileRef fileRef = this->parent->fileRef;
335 XMP_Uns32 packetSize = (XMP_Uns32)this->xmpPacket.size();
336
337 InDesignContigObjMarker header;
338 memcpy ( header.fGUID, kINDDContigObjHeaderGUID, sizeof(header.fGUID) ); // AUDIT: Use of dest sizeof for length is safe.
339 header.fObjectUID = this->xmpObjID;
340 header.fObjectClassID = this->xmpClassID;
341 header.fStreamLength = MakeUns32LE ( 4 + packetSize );
342 header.fChecksum = (XMP_Uns32)(-1);
343 LFA_Write ( fileRef, &header, sizeof(header) );
344
345 XMP_Uns32 pktLength = MakeUns32LE ( packetSize );
346 if ( this->streamBigEndian ) pktLength = MakeUns32BE ( packetSize );
347 LFA_Write ( fileRef, &pktLength, sizeof(pktLength) );
348
349 } // InDesign_MetaHandler::WriteXMPPrefix
350
351 // =================================================================================================
352 // InDesign_MetaHandler::WriteXMPSuffix
353 // ====================================
354
WriteXMPSuffix()355 void InDesign_MetaHandler::WriteXMPSuffix()
356 {
357 // Write the contiguous object trailer.
358
359 LFA_FileRef fileRef = this->parent->fileRef;
360 XMP_Uns32 packetSize = (XMP_Uns32)this->xmpPacket.size();
361
362 InDesignContigObjMarker trailer;
363
364 memcpy ( trailer.fGUID, kINDDContigObjTrailerGUID, sizeof(trailer.fGUID) ); // AUDIT: Use of dest sizeof for length is safe.
365 trailer.fObjectUID = this->xmpObjID;
366 trailer.fObjectClassID = this->xmpClassID;
367 trailer.fStreamLength = MakeUns32LE ( 4 + packetSize );
368 trailer.fChecksum = (XMP_Uns32)(-1);
369
370 LFA_Write ( fileRef, &trailer, sizeof(trailer) );
371
372 } // InDesign_MetaHandler::WriteXMPSuffix
373
374 // =================================================================================================
375 // InDesign_MetaHandler::NoteXMPRemoval
376 // ====================================
377
NoteXMPRemoval()378 void InDesign_MetaHandler::NoteXMPRemoval()
379 {
380 // Nothing to do.
381
382 } // InDesign_MetaHandler::NoteXMPRemoval
383
384 // =================================================================================================
385 // InDesign_MetaHandler::NoteXMPInsertion
386 // ======================================
387
NoteXMPInsertion()388 void InDesign_MetaHandler::NoteXMPInsertion()
389 {
390 // Nothing to do.
391
392 } // InDesign_MetaHandler::NoteXMPInsertion
393
394 // =================================================================================================
395 // InDesign_MetaHandler::CaptureFileEnding
396 // =======================================
397
CaptureFileEnding()398 void InDesign_MetaHandler::CaptureFileEnding()
399 {
400 // Nothing to do. The back of an InDesign file is the final zero padding.
401
402 } // InDesign_MetaHandler::CaptureFileEnding
403
404 // =================================================================================================
405 // InDesign_MetaHandler::RestoreFileEnding
406 // =======================================
407
RestoreFileEnding()408 void InDesign_MetaHandler::RestoreFileEnding()
409 {
410 // Pad the file with zeros to a page boundary.
411
412 LFA_FileRef fileRef = this->parent->fileRef;
413
414 XMP_Int64 dataLength = LFA_Measure ( fileRef );
415 XMP_Int32 padLength = (kINDD_PageSize - ((XMP_Int32)dataLength & kINDD_PageMask)) & kINDD_PageMask;
416
417 XMP_Uns8 buffer [kINDD_PageSize];
418 memset ( buffer, 0, kINDD_PageSize );
419 LFA_Write ( fileRef, buffer, padLength );
420
421 } // InDesign_MetaHandler::RestoreFileEnding
422
423 // =================================================================================================
424