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