summaryrefslogtreecommitdiff
path: root/XMPFiles/source/XMPFiles_Impl.cpp
blob: 47640b067b3c509288f4d3655a30e2316b572012 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
// =================================================================================================
// Copyright Adobe
// Copyright 2020 Adobe
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it. 
// =================================================================================================

#include "public/include/XMP_Environment.h"	// ! XMP_Environment.h must be the first included header.

#include "public/include/XMP_Const.h"
#include "public/include/XMP_IO.hpp"

#include "XMPFiles/source/XMPFiles_Impl.hpp"
#include "source/XIO.hpp"

#include "source/UnicodeConversions.hpp"

using namespace std;

// Internal code should be using #if with XMP_MacBuild, XMP_WinBuild, XMP_AndroidBuild, or XMP_UNIXBuild.
// This is a sanity check in case of accidental use of *_ENV. Some clients use the poor
// practice of defining the *_ENV macro with an empty value.
#if defined ( MAC_ENV )
	#if ! MAC_ENV
		#error "MAC_ENV must be defined so that \"#if MAC_ENV\" is true"
	#endif
#elif defined ( WIN_ENV )
	#if ! WIN_ENV
		#error "WIN_ENV must be defined so that \"#if WIN_ENV\" is true"
	#endif
#elif defined ( UNIX_ENV )
	#if ! UNIX_ENV
		#error "UNIX_ENV must be defined so that \"#if UNIX_ENV\" is true"
	#endif
#elif defined ( ANDROID_ENV )
	#if ! ANDROID_ENV
		#error "ANDROID_ENV must be defined so that \"#if ANDROID_ENV\" is true"
	#endif
#endif

// =================================================================================================
/// \file XMPFiles_Impl.cpp
/// \brief ...
///
/// This file ...
///
// =================================================================================================
#include "public/include/XMP.incl_cpp"

#if XMP_WinBuild
	#pragma warning ( disable : 4290 )	// C++ exception specification ignored except to indicate a function is not __declspec(nothrow)
	#pragma warning ( disable : 4800 )	// forcing value to bool 'true' or 'false' (performance warning)
#endif

bool ignoreLocalText = false;

XMP_FileFormat voidFileFormat = 0;	// Used as sink for unwanted output parameters.

//***** see CTECHXMP-4169947 *****

//#if ! XMP_StaticBuild
//	XMP_PacketInfo voidPacketInfo;
//	void *         voidVoidPtr    = 0;
//	XMP_StringPtr  voidStringPtr  = 0;
//	XMP_StringLen  voidStringLen  = 0;
//	XMP_OptionBits voidOptionBits = 0;
//#endif

// =================================================================================================

// Add all known mappings, multiple mappings (tif, tiff) are OK.
const FileExtMapping kFileExtMap[] =
	{ { "pdf",  kXMP_PDFFile },
	  { "ps",   kXMP_PostScriptFile },
	  { "eps",  kXMP_EPSFile },

	  { "jpg",  kXMP_JPEGFile },
	  { "jpeg", kXMP_JPEGFile },
	  { "jpx",  kXMP_JPEG2KFile },
	  { "tif",  kXMP_TIFFFile },
	  { "tiff", kXMP_TIFFFile },
	  { "dng",  kXMP_TIFFFile },	// DNG files are well behaved TIFF.
	  { "gif",  kXMP_GIFFile },
	  { "giff", kXMP_GIFFile },
	  { "png",  kXMP_PNGFile },

	  { "swf",  kXMP_SWFFile },
	  { "flv",  kXMP_FLVFile },

	  { "aif",  kXMP_AIFFFile },

	  { "mov",  kXMP_MOVFile },
	  { "avi",  kXMP_AVIFile },
	  { "cin",  kXMP_CINFile },
	  { "wav",  kXMP_WAVFile },
	  { "mp3",  kXMP_MP3File },
	  { "mp4",  kXMP_MPEG4File },
	  { "m4v",  kXMP_MPEG4File },
	  { "m4a",  kXMP_MPEG4File },
	  { "f4v",  kXMP_MPEG4File },
	  { "3gp",  kXMP_MPEG4File },
	  { "3g2",  kXMP_MPEG4File },
	  { "crm",  kXMP_MPEG4File },
	  { "ses",  kXMP_SESFile },
	  { "cel",  kXMP_CELFile },
	  { "wma",  kXMP_WMAVFile },
	  { "wmv",  kXMP_WMAVFile },
	  { "mxf",  kXMP_MXFFile },
	  { "r3d",  kXMP_REDFile },

	  { "mpg",  kXMP_MPEGFile },
	  { "mpeg", kXMP_MPEGFile },
	  { "mp2",  kXMP_MPEGFile },
	  { "mod",  kXMP_MPEGFile },
	  { "m2v",  kXMP_MPEGFile },
	  { "mpa",  kXMP_MPEGFile },
	  { "mpv",  kXMP_MPEGFile },
	  { "m2p",  kXMP_MPEGFile },
	  { "m2a",  kXMP_MPEGFile },
	  { "m2t",  kXMP_MPEGFile },
	  { "mpe",  kXMP_MPEGFile },
	  { "vob",  kXMP_MPEGFile },
	  { "ms-pvr", kXMP_MPEGFile },
	  { "dvr-ms", kXMP_MPEGFile },
	  { "heic", kXMP_HEIFFile }, 
	  { "heif", kXMP_HEIFFile },
	  { "avc1", kXMP_HEIFFile },

	  { "html", kXMP_HTMLFile },
	  { "xml",  kXMP_XMLFile },
	  { "txt",  kXMP_TextFile },
	  { "text", kXMP_TextFile },
	  { "svg",  kXMP_SVGFile },

	  { "psd",  kXMP_PhotoshopFile },
	  { "ai",   kXMP_IllustratorFile },
	  { "indd", kXMP_InDesignFile },
	  { "indt", kXMP_InDesignFile },
	  { "aep",  kXMP_AEProjectFile },
	  { "aepx", kXMP_AEProjectFile },
	  { "aet",  kXMP_AEProjTemplateFile },
	  { "ffx",  kXMP_AEFilterPresetFile },
	  { "ncor", kXMP_EncoreProjectFile },
	  { "prproj", kXMP_PremiereProjectFile },
	  { "prtl", kXMP_PremiereTitleFile },
	  { "ucf", kXMP_UCFFile },
	  { "xfl", kXMP_UCFFile },
	  { "pdfxml", kXMP_UCFFile },
	  { "mars", kXMP_UCFFile },
	  { "idml", kXMP_UCFFile },
	  { "idap", kXMP_UCFFile },
	  { "icap", kXMP_UCFFile },
	  { "", 0 } };	// ! Must be last as a sentinel.

// Files known to contain XMP but have no smart handling, here or elsewhere.
const char * kKnownScannedFiles[] =
	{ "ai",		// Illustrator, actually a PDF file.
	  "ait",	// Illustrator template, actually a PDF file.
	  "aet",	// After Effects template project file.
	  "ffx",	// After Effects filter preset file.
	  "aep",	// After Effects project file in proprietary format
	  "aepx",	// After Effects project file in XML format
	  "inx",	// InDesign interchange, an XML file.
	  "inds",	// InDesign snippet, an XML file.
	  "inpk",	// InDesign package for GoLive, a text file (not XML).
	  "incd",	// InCopy story, an XML file.
	  "inct",	// InCopy template, an XML file.
	  "incx",	// InCopy interchange, an XML file.
	  "fm",		// FrameMaker file, proprietary format.
	  "book",	// FrameMaker book, proprietary format.
	  "icml",	// an inCopy (inDesign) format
	  "icmt",	// an inCopy (inDesign) format
	  "idms",	// an inCopy (inDesign) format
	  0 };		// ! Keep a 0 sentinel at the end.


// Extensions that XMPFiles never handles.
const char * kKnownRejectedFiles[] =
	{
		// RAW files
		"cr2", "erf", "fff", "dcr", "kdc", "mos", "mfw", "mef",
		"raw", "nef", "orf", "pef", "arw", "sr2", "srf", "sti",
		"3fr", "rwl", "crw", "sraw", "mos", "mrw", "nrw", "rw2",
		"c3f",
		// UCF subformats
		"air",
		// Others
	  0 };	// ! Keep a 0 sentinel at the end.

// =================================================================================================

// =================================================================================================

// =================================================================================================
// GetPacketCharForm
// =================
//
// The first character must be U+FEFF or ASCII, typically '<' for an outermost element, initial
// processing instruction, or XML declaration. The second character can't be U+0000.
// The possible input sequences are:
//   Cases with U+FEFF
//      EF BB BF -- - UTF-8
//      FE FF -- -- - Big endian UTF-16
//      00 00 FE FF - Big endian UTF 32
//      FF FE 00 00 - Little endian UTF-32
//      FF FE -- -- - Little endian UTF-16
//   Cases with ASCII
//      nn mm -- -- - UTF-8 -
//      00 00 00 nn - Big endian UTF-32
//      00 nn -- -- - Big endian UTF-16
//      nn 00 00 00 - Little endian UTF-32
//      nn 00 -- -- - Little endian UTF-16

static XMP_Uns8 GetPacketCharForm ( XMP_StringPtr packetStr, XMP_StringLen packetLen )
{
	XMP_Uns8   charForm = kXMP_CharUnknown;
	XMP_Uns8 * unsBytes = (XMP_Uns8*)packetStr;	// ! Make sure comparisons are unsigned.

	if ( packetLen < 2 ) return kXMP_Char8Bit;

	if ( packetLen < 4 ) {

		// These cases are based on the first 2 bytes:
		//   00 nn Big endian UTF-16
		//   nn 00 Little endian UTF-16
		//   FE FF Big endian UTF-16
		//   FF FE Little endian UTF-16
		//   Otherwise UTF-8

		if ( packetStr[0] == 0 ) return kXMP_Char16BitBig;
		if ( packetStr[1] == 0 ) return kXMP_Char16BitLittle;
		if ( CheckBytes ( packetStr, "\xFE\xFF", 2 ) ) return kXMP_Char16BitBig;
		if ( CheckBytes ( packetStr, "\xFF\xFE", 2 ) ) return kXMP_Char16BitLittle;
		return kXMP_Char8Bit;

	}

	// If we get here the packet is at least 4 bytes, could be any form.

	if ( unsBytes[0] == 0 ) {

		// These cases are:
		//   00 nn -- -- - Big endian UTF-16
		//   00 00 00 nn - Big endian UTF-32
		//   00 00 FE FF - Big endian UTF 32

		if ( unsBytes[1] != 0 ) {
			charForm = kXMP_Char16BitBig;			// 00 nn
		} else {
			if ( (unsBytes[2] == 0) && (unsBytes[3] != 0) ) {
				charForm = kXMP_Char32BitBig;		// 00 00 00 nn
			} else if ( (unsBytes[2] == 0xFE) && (unsBytes[3] == 0xFF) ) {
				charForm = kXMP_Char32BitBig;		// 00 00 FE FF
			}
		}

	} else {

		// These cases are:
		//   FE FF -- -- - Big endian UTF-16, FE isn't valid UTF-8
		//   FF FE 00 00 - Little endian UTF-32, FF isn't valid UTF-8
		//   FF FE -- -- - Little endian UTF-16
		//   nn mm -- -- - UTF-8, includes EF BB BF case
		//   nn 00 00 00 - Little endian UTF-32
		//   nn 00 -- -- - Little endian UTF-16

		if ( unsBytes[0] == 0xFE ) {
			if ( unsBytes[1] == 0xFF ) charForm = kXMP_Char16BitBig;	// FE FF
		} else if ( unsBytes[0] == 0xFF ) {
			if ( unsBytes[1] == 0xFE ) {
				if ( (unsBytes[2] == 0) && (unsBytes[3] == 0) ) {
					charForm = kXMP_Char32BitLittle;	// FF FE 00 00
				} else {
					charForm = kXMP_Char16BitLittle;	// FF FE
				}
			}
		} else if ( unsBytes[1] != 0 ) {
			charForm = kXMP_Char8Bit;					// nn mm
		} else {
			if ( (unsBytes[2] == 0) && (unsBytes[3] == 0) ) {
				charForm = kXMP_Char32BitLittle;		// nn 00 00 00
			} else {
				charForm = kXMP_Char16BitLittle;		// nn 00
			}
		}

	}

	//	XMP_Assert ( charForm != kXMP_CharUnknown );
	return charForm;

}	// GetPacketCharForm

// =================================================================================================
// FillPacketInfo
// ==============
//
// If a packet wrapper is present, the the packet string is roughly:
//   <?xpacket begin= ...?>
//   <outer-XML-element>
//     ... more XML ...
//   </outer-XML-element>
//   ... whitespace padding ...
//   <?xpacket end='.'?>

// The 8-bit form is 14 bytes, the 16-bit form is 28 bytes, the 32-bit form is 56 bytes.
#define k8BitTrailer  "<?xpacket end="
#define k16BitTrailer "<\0?\0x\0p\0a\0c\0k\0e\0t\0 \0e\0n\0d\0=\0"
#define k32BitTrailer "<\0\0\0?\0\0\0x\0\0\0p\0\0\0a\0\0\0c\0\0\0k\0\0\0e\0\0\0t\0\0\0 \0\0\0e\0\0\0n\0\0\0d\0\0\0=\0\0\0"
static XMP_StringPtr kPacketTrailiers[3] = { k8BitTrailer, k16BitTrailer, k32BitTrailer };

void FillPacketInfo ( const std::string & packet, XMP_PacketInfo * info )
{
	XMP_StringPtr packetStr = packet.c_str();
	XMP_StringLen packetLen = (XMP_StringLen) packet.size();
	if ( packetLen == 0 ) return;

	info->charForm = GetPacketCharForm ( packetStr, packetLen );
	XMP_StringLen charSize = XMP_GetCharSize ( info->charForm );

	// Look for a packet wrapper. For our purposes, we can be lazy and just look for the trailer PI.
	// If that is present we'll assume that a recognizable header is present. First do a bytewise
	// search for '<', then a char sized comparison for the start of the trailer. We don't really
	// care about big or little endian here. We're looking for ASCII bytes with zeroes between.
	// Shorten the range comparisons (n*charSize) by 1 to easily tolerate both big and little endian.

	XMP_StringLen padStart, padEnd;
	XMP_StringPtr packetTrailer = kPacketTrailiers [ charSize>>1 ];

	padEnd = packetLen - 1;
	for ( ; padEnd > 0; --padEnd ) if ( packetStr[padEnd] == '<' ) break;
	if ( (packetStr[padEnd] != '<') || ((packetLen - padEnd) < (18*charSize)) ) return;
	if ( ! CheckBytes ( &packetStr[padEnd], packetTrailer, (13*charSize) ) ) return;

	info->hasWrapper = true;

	char rwFlag = packetStr [padEnd + 15*charSize];
	if ( rwFlag == 'w' ) info->writeable = true;

	// Look for the start of the padding, right after the last XML end tag.

	padStart = padEnd;	// Don't do the -charSize here, might wrap below zero.
	for ( ; padStart >= charSize; padStart -= charSize ) if ( packetStr[padStart] == '>' ) break;
	if ( padStart < charSize ) return;
	padStart += charSize;	// The padding starts after the '>'.

	info->padSize = padEnd - padStart;	// We want bytes of padding, not character units.

}	// FillPacketInfo

// =================================================================================================
// ReadXMPPacket
// =============

void ReadXMPPacket ( XMPFileHandler * handler )
{
	XMP_IO* fileRef   = handler->parent->ioRef;
	std::string & xmpPacket = handler->xmpPacket;
	XMP_StringLen packetLen = handler->packetInfo.length;

	if ( packetLen == 0 ) XMP_Throw ( "ReadXMPPacket - No XMP packet", kXMPErr_BadXMP );

	xmpPacket.erase();
	xmpPacket.reserve ( packetLen );
	xmpPacket.append ( packetLen, ' ' );

	XMP_StringPtr packetStr = XMP_StringPtr ( xmpPacket.c_str() );	// Don't set until after reserving the space!

	fileRef->Seek ( handler->packetInfo.offset, kXMP_SeekFromStart );
	fileRef->ReadAll ( (char*)packetStr, packetLen );

}	// ReadXMPPacket

// =================================================================================================
// XMPFileHandler::GetFileModDate
// ==============================
//
// The base implementation is only for typical embedding handlers. It returns the modification date
// of the named file.

bool XMPFileHandler::GetFileModDate ( XMP_DateTime * modDate )
{

	XMP_OptionBits flags = this->handlerFlags;
	if ( (flags & kXMPFiles_HandlerOwnsFile) ||
		 (flags & kXMPFiles_UsesSidecarXMP) ||
		 (flags & kXMPFiles_FolderBasedFormat) ) {
		XMP_Throw ( "Base implementation of GetFileModDate only for typical embedding handlers", kXMPErr_InternalFailure );
	}
	
	if ( this->parent->GetFilePath().empty() ) {
		XMP_Throw ( "GetFileModDate cannot be used with client-provided I/O", kXMPErr_InternalFailure );
	}
	
	return Host_IO::GetModifyDate ( this->parent->GetFilePath().c_str(), modDate );

}	// XMPFileHandler::GetFileModDate

// =================================================================================================
// XMPFileHandler::FillMetadataFiles
// ==============================
//
// The base implementation is only for files having embedded metadata for which the same file will
// be returned.

void XMPFileHandler::FillMetadataFiles ( std::vector<std::string> * metadataFiles )
{
	XMP_OptionBits flags = this->handlerFlags;
	if ( (flags & kXMPFiles_HandlerOwnsFile) ||
		 (flags & kXMPFiles_UsesSidecarXMP) ||
		 (flags & kXMPFiles_FolderBasedFormat) ) {
		XMP_Throw ( "Base implementation of FillMetadataFiles only for typical embedding handlers", kXMPErr_InternalFailure );
	}
	
	if ( this->parent->GetFilePath().empty() ) {
		XMP_Throw ( "FillMetadataFiles cannot be used with client-provided I/O", kXMPErr_InternalFailure );
	}
	
	metadataFiles->push_back ( std::string ( this->parent->GetFilePath().c_str() ) );
}	// XMPFileHandler::FillMetadataFiles

// =================================================================================================
// XMPFileHandler::FillAssociatedResources
// =======================================
//
// The base implementation is only for files having embedded metadata for which the same file will
// be returned as the associated resource.
// 

void XMPFileHandler::FillAssociatedResources ( std::vector<std::string> * metadataFiles )
{
	XMP_OptionBits flags = this->handlerFlags;
	if ( (flags & kXMPFiles_HandlerOwnsFile) ||
		 (flags & kXMPFiles_UsesSidecarXMP) ||
		 (flags & kXMPFiles_FolderBasedFormat) ) {
		XMP_Throw ( "GetAssociatedResources is not implemented for this file format", kXMPErr_InternalFailure );
	}
	
	if ( this->parent->GetFilePath().empty() ) {
		XMP_Throw ( "GetAssociatedResources cannot be used with client-provided I/O", kXMPErr_InternalFailure );
	}
	
	metadataFiles->push_back ( std::string ( this->parent->GetFilePath().c_str() ) );
}	// XMPFileHandler::FillAssociatedResources

// =================================================================================================
// XMPFileHandler::IsMetadataWritable
// =======================================
//
// The base implementation is only for files having embedded metadata and it checks whether that 
// file is writable or no.Returns true if the file is writable. 
// 
bool XMPFileHandler::IsMetadataWritable ( )
{
	XMP_OptionBits flags = this->handlerFlags;
	if ( (flags & kXMPFiles_HandlerOwnsFile) ||
		 (flags & kXMPFiles_UsesSidecarXMP)  ||
		 (flags & kXMPFiles_FolderBasedFormat) ) {
		XMP_Throw ( "IsMetadataWritable is not implemented for this file format", kXMPErr_InternalFailure );
	}

	if ( this->parent->GetFilePath().empty() ) {
		XMP_Throw ( "IsMetadataWritable cannot be used with client-provided I/O", kXMPErr_InternalFailure );
	}

	try {
		return Host_IO::Writable( this->parent->GetFilePath().c_str() );
	} catch ( ... ) {

	}
	return false;
}

// =================================================================================================
// XMPFileHandler::ProcessXMP
// ==========================
//
// This base implementation just parses the XMP. If the derived handler does reconciliation then it
// must have its own implementation of ProcessXMP.

void XMPFileHandler::ProcessXMP()
{

	if ( (!this->containsXMP) || this->processedXMP ) return;

	if ( this->handlerFlags & kXMPFiles_CanReconcile ) {
		XMP_Throw ( "Reconciling file handlers must implement ProcessXMP", kXMPErr_InternalFailure );
	}

	SXMPUtils::RemoveProperties ( &this->xmpObj, 0, 0, kXMPUtil_DoAllProperties );
	this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
	this->processedXMP = true;

}	// XMPFileHandler::ProcessXMP

// =================================================================================================
// XMPFileHandler::GetSerializeOptions
// ===================================
//
// This base implementation just selects compact serialization. The character form and padding/in-place
// settings are added in the common code before calling SerializeToBuffer.

XMP_OptionBits XMPFileHandler::GetSerializeOptions()
{

	return kXMP_UseCompactFormat;

}	// XMPFileHandler::GetSerializeOptions

// =================================================================================================
// XMPFileHandler::NotifyClient
// ===================================
//
// Generic function for all the file handlers to replace existing exception throws
//
void XMPFileHandler::NotifyClient(GenericErrorCallback * errCBptr, XMP_ErrorSeverity severity, XMP_Error & error)
{	
	if (errCBptr)
		errCBptr->NotifyClient( severity, error );
	else {
		if ( severity != kXMPErrSev_Recoverable )
			throw error;
	}
}
// =================================================================================================