1 // =================================================================================================
2 // ADOBE SYSTEMS INCORPORATED
3 // Copyright 2002-2007 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 "XMPFiles_Impl.hpp"
11 
12 #include "UnicodeConversions.hpp"
13 
14 using namespace std;
15 
16 // Internal code should be using #if with XMP_MacBuild, XMP_WinBuild, or XMP_UNIXBuild.
17 // This is a sanity check in case of accidental use of *_ENV. Some clients use the poor
18 // practice of defining the *_ENV macro with an empty value.
19 #if defined ( MAC_ENV )
20 	#if ! MAC_ENV
21 		#error "MAC_ENV must be defined so that \"#if MAC_ENV\" is true"
22 	#endif
23 #elif defined ( WIN_ENV )
24 	#if ! WIN_ENV
25 		#error "WIN_ENV must be defined so that \"#if WIN_ENV\" is true"
26 	#endif
27 #elif defined ( UNIX_ENV )
28 	#if ! UNIX_ENV
29 		#error "UNIX_ENV must be defined so that \"#if UNIX_ENV\" is true"
30 	#endif
31 #endif
32 
33 // =================================================================================================
34 /// \file XMPFiles_Impl.cpp
35 /// \brief ...
36 ///
37 /// This file ...
38 ///
39 // =================================================================================================
40 
41 #if XMP_WinBuild
42 	#pragma warning ( disable : 4290 )	// C++ exception specification ignored except to indicate a function is not __declspec(nothrow)
43 	#pragma warning ( disable : 4800 )	// forcing value to bool 'true' or 'false' (performance warning)
44 #endif
45 
46 XMP_FileFormat voidFileFormat = 0;	// Used as sink for unwanted output parameters.
47 XMP_Mutex sXMPFilesLock;
48 int sXMPFilesLockCount = 0;
49 std::string * sXMPFilesExceptionMessage = 0;
50 
51 #if TraceXMPCalls
52 	FILE * xmpFilesOut = stderr;
53 #endif
54 
55 //only define in one non-public-source, non-header(.cpp) place
LFA_Throw(const char * msg,int id)56 void LFA_Throw ( const char* msg, int id )
57 {
58 	switch(id)
59 	{
60 		case kLFAErr_InternalFailure:
61 			XMP_Throw(msg,kXMPErr_InternalFailure);
62 			break;
63 		case kLFAErr_ExternalFailure:
64 			XMP_Throw(msg,kXMPErr_ExternalFailure);
65 			break;
66 		case kLFAErr_UserAbort:
67 			XMP_Throw(msg,kXMPErr_UserAbort);
68 			break;
69 		default:
70 			XMP_Throw(msg,kXMPErr_UnknownException);
71 			break;
72 	}
73 }
74 
75 // =================================================================================================
76 
77 // Add all known mappings, multiple mappings (tif, tiff) are OK.
78 const FileExtMapping kFileExtMap[] =
79 	{ { "pdf",  kXMP_PDFFile },
80 	  { "ps",   kXMP_PostScriptFile },
81 	  { "eps",  kXMP_EPSFile },
82 
83 	  { "jpg",  kXMP_JPEGFile },
84 	  { "jpeg", kXMP_JPEGFile },
85 	  { "jpx",  kXMP_JPEG2KFile },
86 	  { "tif",  kXMP_TIFFFile },
87 	  { "tiff", kXMP_TIFFFile },
88 	  { "dng",  kXMP_TIFFFile },	// DNG files are well behaved TIFF.
89 	  { "gif",  kXMP_GIFFile },
90 	  { "giff", kXMP_GIFFile },
91 	  { "png",  kXMP_PNGFile },
92 
93 	  { "swf",  kXMP_SWFFile },
94 	  { "flv",  kXMP_FLVFile },
95 
96 	  { "aif",  kXMP_AIFFFile },
97 
98 	  //	  { "mov",  kXMP_MOVFile },
99 	  { "avi",  kXMP_AVIFile },
100 	  { "cin",  kXMP_CINFile },
101 	  { "wav",  kXMP_WAVFile },
102 	  { "mp3",  kXMP_MP3File },
103 	  { "mp4",  kXMP_MPEG4File },
104 	  { "m4v",  kXMP_MPEG4File },
105 	  { "m4a",  kXMP_MPEG4File },
106 	  { "f4v",  kXMP_MPEG4File },
107 	  { "ses",  kXMP_SESFile },
108 	  { "cel",  kXMP_CELFile },
109 	  { "wma",  kXMP_WMAVFile },
110 	  { "wmv",  kXMP_WMAVFile },
111 
112 	  { "mpg",  kXMP_MPEGFile },
113 	  { "mpeg", kXMP_MPEGFile },
114 	  { "mp2",  kXMP_MPEGFile },
115 	  { "mod",  kXMP_MPEGFile },
116 	  { "m2v",  kXMP_MPEGFile },
117 	  { "mpa",  kXMP_MPEGFile },
118 	  { "mpv",  kXMP_MPEGFile },
119 	  { "m2p",  kXMP_MPEGFile },
120 	  { "m2a",  kXMP_MPEGFile },
121 	  { "m2t",  kXMP_MPEGFile },
122 	  { "mpe",  kXMP_MPEGFile },
123 	  { "vob",  kXMP_MPEGFile },
124 	  { "ms-pvr", kXMP_MPEGFile },
125 	  { "dvr-ms", kXMP_MPEGFile },
126 
127 	  { "html", kXMP_HTMLFile },
128 	  { "xml",  kXMP_XMLFile },
129 	  { "txt",  kXMP_TextFile },
130 	  { "text", kXMP_TextFile },
131 
132 	  { "psd",  kXMP_PhotoshopFile },
133 	  { "ai",   kXMP_IllustratorFile },
134 	  { "indd", kXMP_InDesignFile },
135 	  { "indt", kXMP_InDesignFile },
136 	  { "aep",  kXMP_AEProjectFile },
137 	  { "aepx", kXMP_AEProjectFile },
138 	  { "aet",  kXMP_AEProjTemplateFile },
139 	  { "ffx",  kXMP_AEFilterPresetFile },
140 	  { "ncor", kXMP_EncoreProjectFile },
141 	  { "prproj", kXMP_PremiereProjectFile },
142 	  { "prtl", kXMP_PremiereTitleFile },
143 	  { "ucf", kXMP_UCFFile },
144 	  { "xfl", kXMP_UCFFile },
145 	  { "pdfxml", kXMP_UCFFile },
146 	  { "mars", kXMP_UCFFile },
147 	  { "idml", kXMP_UCFFile },
148 	  { "", 0 } };	// ! Must be last as a sentinel.
149 
150 // Files known to contain XMP but have no smart handling, here or elsewhere.
151 const char * kKnownScannedFiles[] =
152 	{ "gif",	// GIF, public format but no smart handler.
153 	  "ai",		// Illustrator, actually a PDF file.
154 	  "ait",	// Illustrator template, actually a PDF file.
155 	  "svg",	// SVG, an XML file.
156 	  "aet",	// After Effects template project file.
157 	  "ffx",	// After Effects filter preset file.
158 	  "aep",	// After Effects project file in proprietary format
159 	  "aepx",	// After Effects project file in XML format
160 	  "inx",	// InDesign interchange, an XML file.
161 	  "inds",	// InDesign snippet, an XML file.
162 	  "inpk",	// InDesign package for GoLive, a text file (not XML).
163 	  "incd",	// InCopy story, an XML file.
164 	  "inct",	// InCopy template, an XML file.
165 	  "incx",	// InCopy interchange, an XML file.
166 	  "fm",		// FrameMaker file, proprietary format.
167 	  "book",	// FrameMaker book, proprietary format.
168 	  "icml",	// an inCopy (inDesign) format
169 	  "icmt",	// an inCopy (inDesign) format
170 	  "idms",	// an inCopy (inDesign) format
171 	  0 };		// ! Keep a 0 sentinel at the end.
172 
173 
174 // Extensions that XMPFiles never handles.
175 const char * kKnownRejectedFiles[] =
176 	{
177 		// RAW files
178 		"cr2", "erf", "fff", "dcr", "kdc", "mos", "mfw", "mef",
179 		"raw", "nef", "orf", "pef", "arw", "sr2", "srf", "sti",
180 		"3fr",
181 		// not supported UCF subformats
182 		"air",
183 	  0 };	// ! Keep a 0 sentinel at the end.
184 
185 // =================================================================================================
186 
187 // =================================================================================================
188 
189 // =================================================================================================
190 
191 #if XMP_MacBuild | XMP_UNIXBuild
192 	//copy from LargeFileAccess.cpp
FileExists(const char * filePath)193 	static bool FileExists ( const char * filePath )
194 	{
195 		struct stat info;
196 		int err = stat ( filePath, &info );
197 		return (err == 0);
198 	}
199 #endif
200 
201 
CreateNewFile(const char * newPath,const char * origPath,size_t filePos,bool copyMacRsrc)202 static bool CreateNewFile ( const char * newPath, const char * origPath, size_t filePos, bool copyMacRsrc )
203 {
204 	// Try to create a new file with the same ownership and permissions as some other file.
205 	// *** The ownership and permissions are not handled on all platforms.
206 
207 	#if XMP_MacBuild | XMP_UNIXBuild
208 
209 		if ( FileExists ( newPath ) ) return false;
210 
211 	#elif XMP_WinBuild
212 
213 		{
214 			std::string wideName;
215 			const size_t utf8Len = strlen(newPath);
216 			const size_t maxLen = 2 * (utf8Len+1);
217 			wideName.reserve ( maxLen );
218 			wideName.assign ( maxLen, ' ' );
219 			int wideLen = MultiByteToWideChar ( CP_UTF8, 0, newPath, -1, (LPWSTR)wideName.data(), (int)maxLen );
220 			if ( wideLen == 0 ) return false;
221 			HANDLE temp = CreateFileW ( (LPCWSTR)wideName.data(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING,
222 										(FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS), 0 );
223 			if ( temp != INVALID_HANDLE_VALUE ) {
224 				CloseHandle ( temp );
225 				return false;
226 			}
227 		}
228 
229 	#endif
230 
231 	try {
232 		LFA_FileRef newFile = LFA_Create ( newPath );
233 		LFA_Close ( newFile );
234 	} catch ( ... ) {
235 		// *** Unfortunate that LFA_Create throws for an existing file.
236 		return false;
237 	}
238 
239 	#if XMP_WinBuild
240 
241 		IgnoreParam(origPath); IgnoreParam(filePos); IgnoreParam(copyMacRsrc);
242 
243 		// *** Don't handle Windows specific info yet.
244 
245 	#elif XMP_MacBuild
246 
247 		IgnoreParam(filePos);
248 
249 		OSStatus err;
250 		FSRef newFSRef, origFSRef;	// Copy the "copyable" catalog info, includes the Finder info.
251 
252 		err = FSPathMakeRef ( (XMP_Uns8*)origPath, &origFSRef, 0 );
253 		if ( err != noErr ) XMP_Throw ( "CreateNewFile: FSPathMakeRef failure", kXMPErr_ExternalFailure );
254 		err = FSPathMakeRef ( (XMP_Uns8*)newPath, &newFSRef, 0 );
255 		if ( err != noErr ) XMP_Throw ( "CreateNewFile: FSPathMakeRef failure", kXMPErr_ExternalFailure );
256 
257 		FSCatalogInfo catInfo;	// *** What about the GetInfo comment? The Finder label?
258 		memset ( &catInfo, 0, sizeof(FSCatalogInfo) );
259 
260 		err = FSGetCatalogInfo ( &origFSRef, kFSCatInfoGettableInfo, &catInfo, 0, 0, 0 );
261 		if ( err != noErr ) XMP_Throw ( "CreateNewFile: FSGetCatalogInfo failure", kXMPErr_ExternalFailure );
262 		err = FSSetCatalogInfo ( &newFSRef, kFSCatInfoSettableInfo, &catInfo );
263 
264 		// *** [1841019] tolerate non mac filesystems, i.e. SMB mounts
265 		// this measure helps under 10.5, albeit not reliably under 10.4
266 		if ( err == afpAccessDenied )
267 			copyMacRsrc = false;
268 		else if ( err != noErr ) // all other errors are still an error
269 			XMP_Throw ( "CreateNewFile: FSSetCatalogInfo failure", kXMPErr_ExternalFailure );
270 
271 		// *** [1841019] tolerate non mac filesystems, i.e. SMB mounts
272 		// this measure helps under 10.4 (and besides might be an optimization)
273 		if ( catInfo.rsrcLogicalSize == 0 )
274 			copyMacRsrc = false;
275 
276 		if ( copyMacRsrc ) {	// Copy the resource fork as a byte stream.
277 			LFA_FileRef origRsrcRef = 0;
278 			LFA_FileRef copyRsrcRef = 0;
279 			try {
280 				origRsrcRef = LFA_OpenRsrc ( origPath, 'r' );
281 				XMP_Int64 rsrcSize = LFA_Measure ( origRsrcRef );
282 				if ( rsrcSize > 0 ) {
283 					copyRsrcRef = LFA_OpenRsrc ( newPath, 'w' );
284 					LFA_Copy ( origRsrcRef, copyRsrcRef, rsrcSize, 0, 0 );	// ! Resource fork small enough to not need abort checking.
285 					LFA_Close ( copyRsrcRef );
286 				}
287 				LFA_Close ( origRsrcRef );
288 			} catch ( ... ) {
289 				if ( origRsrcRef != 0 ) LFA_Close ( origRsrcRef );
290 				if ( copyRsrcRef != 0 ) LFA_Close ( copyRsrcRef );
291 				throw;
292 			}
293 		}
294 
295 
296 	#elif XMP_UNIXBuild
297 
298 		IgnoreParam(filePos); IgnoreParam(copyMacRsrc);
299 		// *** Can't use on Mac because of frigging CW POSIX header problems!
300 
301 		// *** Don't handle UNIX specific info yet.
302 
303 		int err, newRef;
304 		struct stat origInfo;
305 		err = stat ( origPath, &origInfo );
306 		if ( err != 0 ) XMP_Throw ( "CreateNewFile: stat failure", kXMPErr_ExternalFailure );
307 
308 		(void) chmod ( newPath, origInfo.st_mode );	// Ignore errors.
309 
310 	#endif
311 
312 	return true;
313 
314 }	// CreateNewFile
315 
316 // =================================================================================================
317 
CreateTempFile(const std::string & origPath,std::string * tempPath,bool copyMacRsrc)318 void CreateTempFile ( const std::string & origPath, std::string * tempPath, bool copyMacRsrc )
319 {
320 	// Create an empty temp file next to the source file with the same ownership and permissions.
321 	// The temp file has "._nn_" added as a prefix to the file name, where "nn" is a unique
322 	// sequence number. The "._" start is important for Bridge, telling it to ignore the file.
323 
324 	// *** The ownership and permissions are not yet handled.
325 
326 	#if XMP_WinBuild
327 		#define kUseBS	true
328 	#else
329 		#define kUseBS	false
330 	#endif
331 
332 	// Break the full path into folder path and file name portions.
333 
334 	size_t namePos;	// The origPath index of the first byte of the file name part.
335 
336 	for ( namePos = origPath.size(); namePos > 0; --namePos ) {
337 		if ( (origPath[namePos] == '/') || (kUseBS && (origPath[namePos] == '\\')) ) {
338 			++namePos;
339 			break;
340 		}
341 	}
342 	if ( (origPath[namePos] == '/') || (kUseBS && (origPath[namePos] == '\\')) ) ++namePos;
343 	if ( namePos == origPath.size() ) XMP_Throw ( "CreateTempFile: Empty file name part", kXMPErr_InternalFailure );
344 
345 	std::string folderPath ( origPath, 0, namePos );
346 	std::string origName ( origPath, namePos );
347 
348 	// First try to create a file with "._nn_" added as a file name prefix.
349 
350 	char tempPrefix[6] = "._nn_";
351 
352 	tempPath->reserve ( origPath.size() + 5 );
353 	tempPath->assign ( origPath, 0, namePos );
354 	tempPath->append ( tempPrefix, 5 );
355 	tempPath->append ( origName );
356 
357 	for ( char n1 = '0'; n1 <= '9'; ++n1 ) {
358 		(*tempPath)[namePos+2] = n1;
359 		for ( char n2 = '0'; n2 <= '9'; ++n2 ) {
360 			(*tempPath)[namePos+3] = n2;
361 			if ( CreateNewFile ( tempPath->c_str(), origPath.c_str(), namePos, copyMacRsrc ) ) return;
362 		}
363 	}
364 
365 	// Now try to create a file with the name "._nn_XMPFilesTemp"
366 
367 	tempPath->assign ( origPath, 0, namePos );
368 	tempPath->append ( tempPrefix, 5 );
369 	tempPath->append ( "XMPFilesTemp" );
370 
371 	for ( char n1 = '0'; n1 <= '9'; ++n1 ) {
372 		(*tempPath)[namePos+2] = n1;
373 		for ( char n2 = '0'; n2 <= '9'; ++n2 ) {
374 			(*tempPath)[namePos+3] = n2;
375 			if ( CreateNewFile ( tempPath->c_str(), origPath.c_str(), namePos, copyMacRsrc ) ) return;
376 		}
377 	}
378 
379 	XMP_Throw ( "CreateTempFile: Can't find unique name", kXMPErr_InternalFailure );
380 
381 }	// CreateTempFile
382 
383 // =================================================================================================
384 // File mode and folder info utilities
385 // -----------------------------------
386 
387 #if XMP_WinBuild
388 
389 	// ---------------------------------------------------------------------------------------------
390 
391 	static DWORD kOtherAttrs = (FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_OFFLINE);
392 
GetFileMode(const char * path)393 	FileMode GetFileMode ( const char * path )
394 	{
395 		std::string utf16;	// GetFileAttributes wants native UTF-16.
396 		ToUTF16Native ( (UTF8Unit*)path, strlen(path), &utf16 );
397 		utf16.append ( 2, '\0' );	// Make sure there are at least 2 final zero bytes.
398 
399 		// ! A shortcut is seen as a file, we would need extra code to recognize it and find the target.
400 
401 		DWORD fileAttrs = GetFileAttributesW ( (LPCWSTR) utf16.c_str() );
402 		if ( fileAttrs == INVALID_FILE_ATTRIBUTES ) return kFMode_DoesNotExist;	// ! Any failure turns into does-not-exist.
403 
404 		if ( fileAttrs & FILE_ATTRIBUTE_DIRECTORY ) return kFMode_IsFolder;
405 		if ( fileAttrs & kOtherAttrs ) return kFMode_IsOther;
406 		return kFMode_IsFile;
407 
408 	}	// GetFileMode
409 
410 	// ---------------------------------------------------------------------------------------------
411 
Open(const char * folderPath)412 	void XMP_FolderInfo::Open ( const char * folderPath )
413 	{
414 
415 		if ( this->dirRef != 0 ) this->Close();
416 
417 		this->folderPath = folderPath;
418 
419 	}	// XMP_FolderInfo::Open
420 
421 	// ---------------------------------------------------------------------------------------------
422 
Close()423 	void XMP_FolderInfo::Close()
424 	{
425 
426 		if ( this->dirRef != 0 ) (void) FindClose ( this->dirRef );
427 		this->dirRef = 0;
428 		this->folderPath.erase();
429 
430 	}	// XMP_FolderInfo::Close
431 
432 	// ---------------------------------------------------------------------------------------------
433 
GetFolderPath(std::string * folderPath)434 	bool XMP_FolderInfo::GetFolderPath ( std::string * folderPath )
435 	{
436 
437 		if ( this->folderPath.empty() ) return false;
438 
439 		*folderPath = this->folderPath;
440 		return true;
441 
442 	}	// XMP_FolderInfo::GetFolderPath
443 
444 	// ---------------------------------------------------------------------------------------------
445 
GetNextChild(std::string * childName)446 	bool XMP_FolderInfo::GetNextChild ( std::string * childName )
447 	{
448 		bool found = false;
449 		WIN32_FIND_DATAW childInfo;
450 
451 		if ( this->dirRef != 0 ) {
452 
453 			found = FindNextFileW ( this->dirRef, &childInfo );
454 			if ( ! found ) return false;
455 
456 		} else {
457 
458 			if ( this->folderPath.empty() ) {
459 				XMP_Throw ( "XMP_FolderInfo::GetNextChild - not open", kXMPErr_InternalFailure );
460 			}
461 
462 			std::string findPath = this->folderPath;
463 			findPath += "\\*";
464 			std::string utf16;	// FindFirstFile wants native UTF-16.
465 			ToUTF16Native ( (UTF8Unit*)findPath.c_str(), findPath.size(), &utf16 );
466 			utf16.append ( 2, '\0' );	// Make sure there are at least 2 final zero bytes.
467 
468 			this->dirRef = FindFirstFileW ( (LPCWSTR) utf16.c_str(), &childInfo );
469 			if ( this->dirRef == 0 ) return false;
470 			found = true;
471 
472 		}
473 
474 		// Ignore all children with names starting in '.'. This covers ., .., .DS_Store, etc.
475 		while ( found && (childInfo.cFileName[0] == '.') ) {
476 			found = FindNextFileW ( this->dirRef, &childInfo );
477 		}
478 		if ( ! found ) return false;
479 
480 		size_t len16 = 0;
481 		while ( childInfo.cFileName[len16] != 0 ) ++len16;
482 		FromUTF16Native ( (UTF16Unit*)childInfo.cFileName, len16, childName );	// The cFileName field is native UTF-16.
483 
484 		return true;
485 
486 	}	// XMP_FolderInfo::GetNextChild
487 
488 	// ---------------------------------------------------------------------------------------------
489 
490 #else	// Mac and UNIX both use POSIX functions.
491 
492 	// ---------------------------------------------------------------------------------------------
493 
GetFileMode(const char * path)494 	FileMode GetFileMode ( const char * path )
495 	{
496 		struct stat fileInfo;
497 
498 		int err = stat ( path, &fileInfo );
499 		if ( err != 0 ) return kFMode_DoesNotExist;	// ! Any failure turns into does-not-exist.
500 
501 		// ! The target of a symlink is properly recognized, not the symlink itself. A Mac alias is
502 		// ! seen as a file, we would need extra code to recognize it and find the target.
503 
504 		if ( S_ISREG ( fileInfo.st_mode ) ) return kFMode_IsFile;
505 		if ( S_ISDIR ( fileInfo.st_mode ) ) return kFMode_IsFolder;
506 		return kFMode_IsOther;
507 
508 	}	// GetFileMode
509 
510 	// ---------------------------------------------------------------------------------------------
511 
Open(const char * folderPath)512 	void XMP_FolderInfo::Open ( const char * folderPath )
513 	{
514 
515 		if ( this->dirRef != 0 ) this->Close();
516 
517 		this->dirRef = opendir ( folderPath );
518 		if ( this->dirRef == 0 ) XMP_Throw ( "XMP_FolderInfo::Open - opendir failed", kXMPErr_ExternalFailure );
519 		this->folderPath = folderPath;
520 
521 	}	// XMP_FolderInfo::Open
522 
523 	// ---------------------------------------------------------------------------------------------
524 
Close()525 	void XMP_FolderInfo::Close()
526 	{
527 
528 		if ( this->dirRef != 0 ) (void) closedir ( this->dirRef );
529 		this->dirRef = 0;
530 		this->folderPath.erase();
531 
532 	}	// XMP_FolderInfo::Close
533 
534 	// ---------------------------------------------------------------------------------------------
535 
GetFolderPath(std::string * folderPath)536 	bool XMP_FolderInfo::GetFolderPath ( std::string * folderPath )
537 	{
538 
539 		if ( this->folderPath.empty() ) return false;
540 
541 		*folderPath = this->folderPath;
542 		return true;
543 
544 	}	// XMP_FolderInfo::GetFolderPath
545 
546 	// ---------------------------------------------------------------------------------------------
547 
GetNextChild(std::string * childName)548 	bool XMP_FolderInfo::GetNextChild ( std::string * childName )
549 	{
550 		struct dirent * childInfo = 0;
551 
552 		if ( this->dirRef == 0 ) XMP_Throw ( "XMP_FolderInfo::GetNextChild - not open", kXMPErr_InternalFailure );
553 
554 		while ( true ) {
555 			// Ignore all children with names starting in '.'. This covers ., .., .DS_Store, etc.
556 			childInfo = readdir ( this->dirRef );	// ! Depends on global lock, readdir is not thread safe.
557 			if ( childInfo == 0 ) return false;
558 			if ( *childInfo->d_name != '.' ) break;
559 		}
560 
561 		*childName = childInfo->d_name;
562 		return true;
563 
564 	}	// XMP_FolderInfo::GetNextChild
565 
566 	// ---------------------------------------------------------------------------------------------
567 
568 #endif
569 
570 // =================================================================================================
571 // GetPacketCharForm
572 // =================
573 //
574 // The first character must be U+FEFF or ASCII, typically '<' for an outermost element, initial
575 // processing instruction, or XML declaration. The second character can't be U+0000.
576 // The possible input sequences are:
577 //   Cases with U+FEFF
578 //      EF BB BF -- - UTF-8
579 //      FE FF -- -- - Big endian UTF-16
580 //      00 00 FE FF - Big endian UTF 32
581 //      FF FE 00 00 - Little endian UTF-32
582 //      FF FE -- -- - Little endian UTF-16
583 //   Cases with ASCII
584 //      nn mm -- -- - UTF-8 -
585 //      00 00 00 nn - Big endian UTF-32
586 //      00 nn -- -- - Big endian UTF-16
587 //      nn 00 00 00 - Little endian UTF-32
588 //      nn 00 -- -- - Little endian UTF-16
589 
GetPacketCharForm(XMP_StringPtr packetStr,XMP_StringLen packetLen)590 static XMP_Uns8 GetPacketCharForm ( XMP_StringPtr packetStr, XMP_StringLen packetLen )
591 {
592 	XMP_Uns8   charForm = kXMP_CharUnknown;
593 	XMP_Uns8 * unsBytes = (XMP_Uns8*)packetStr;	// ! Make sure comparisons are unsigned.
594 
595 	if ( packetLen < 2 ) return kXMP_Char8Bit;
596 
597 	if ( packetLen < 4 ) {
598 
599 		// These cases are based on the first 2 bytes:
600 		//   00 nn Big endian UTF-16
601 		//   nn 00 Little endian UTF-16
602 		//   FE FF Big endian UTF-16
603 		//   FF FE Little endian UTF-16
604 		//   Otherwise UTF-8
605 
606 		if ( packetStr[0] == 0 ) return kXMP_Char16BitBig;
607 		if ( packetStr[1] == 0 ) return kXMP_Char16BitLittle;
608 		if ( CheckBytes ( packetStr, "\xFE\xFF", 2 ) ) return kXMP_Char16BitBig;
609 		if ( CheckBytes ( packetStr, "\xFF\xFE", 2 ) ) return kXMP_Char16BitLittle;
610 		return kXMP_Char8Bit;
611 
612 	}
613 
614 	// If we get here the packet is at least 4 bytes, could be any form.
615 
616 	if ( unsBytes[0] == 0 ) {
617 
618 		// These cases are:
619 		//   00 nn -- -- - Big endian UTF-16
620 		//   00 00 00 nn - Big endian UTF-32
621 		//   00 00 FE FF - Big endian UTF 32
622 
623 		if ( unsBytes[1] != 0 ) {
624 			charForm = kXMP_Char16BitBig;			// 00 nn
625 		} else {
626 			if ( (unsBytes[2] == 0) && (unsBytes[3] != 0) ) {
627 				charForm = kXMP_Char32BitBig;		// 00 00 00 nn
628 			} else if ( (unsBytes[2] == 0xFE) && (unsBytes[3] == 0xFF) ) {
629 				charForm = kXMP_Char32BitBig;		// 00 00 FE FF
630 			}
631 		}
632 
633 	} else {
634 
635 		// These cases are:
636 		//   FE FF -- -- - Big endian UTF-16, FE isn't valid UTF-8
637 		//   FF FE 00 00 - Little endian UTF-32, FF isn't valid UTF-8
638 		//   FF FE -- -- - Little endian UTF-16
639 		//   nn mm -- -- - UTF-8, includes EF BB BF case
640 		//   nn 00 00 00 - Little endian UTF-32
641 		//   nn 00 -- -- - Little endian UTF-16
642 
643 		if ( unsBytes[0] == 0xFE ) {
644 			if ( unsBytes[1] == 0xFF ) charForm = kXMP_Char16BitBig;	// FE FF
645 		} else if ( unsBytes[0] == 0xFF ) {
646 			if ( unsBytes[1] == 0xFE ) {
647 				if ( (unsBytes[2] == 0) && (unsBytes[3] == 0) ) {
648 					charForm = kXMP_Char32BitLittle;	// FF FE 00 00
649 				} else {
650 					charForm = kXMP_Char16BitLittle;	// FF FE
651 				}
652 			}
653 		} else if ( unsBytes[1] != 0 ) {
654 			charForm = kXMP_Char8Bit;					// nn mm
655 		} else {
656 			if ( (unsBytes[2] == 0) && (unsBytes[3] == 0) ) {
657 				charForm = kXMP_Char32BitLittle;		// nn 00 00 00
658 			} else {
659 				charForm = kXMP_Char16BitLittle;		// nn 00
660 			}
661 		}
662 
663 	}
664 
665 	//	XMP_Assert ( charForm != kXMP_CharUnknown );
666 	return charForm;
667 
668 }	// GetPacketCharForm
669 
670 // =================================================================================================
671 // FillPacketInfo
672 // ==============
673 //
674 // If a packet wrapper is present, the the packet string is roughly:
675 //   <?xpacket begin= ...?>
676 //   <outer-XML-element>
677 //     ... more XML ...
678 //   </outer-XML-element>
679 //   ... whitespace padding ...
680 //   <?xpacket end='.'?>
681 
682 // The 8-bit form is 14 bytes, the 16-bit form is 28 bytes, the 32-bit form is 56 bytes.
683 #define k8BitTrailer  "<?xpacket end="
684 #define k16BitTrailer "<\0?\0x\0p\0a\0c\0k\0e\0t\0 \0e\0n\0d\0=\0"
685 #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"
686 static XMP_StringPtr kPacketTrailiers[3] = { k8BitTrailer, k16BitTrailer, k32BitTrailer };
687 
FillPacketInfo(const std::string & packet,XMP_PacketInfo * info)688 void FillPacketInfo ( const std::string & packet, XMP_PacketInfo * info )
689 {
690 	XMP_StringPtr packetStr = packet.c_str();
691 	XMP_StringLen packetLen = (XMP_StringLen) packet.size();
692 	if ( packetLen == 0 ) return;
693 
694 	info->charForm = GetPacketCharForm ( packetStr, packetLen );
695 	XMP_StringLen charSize = XMP_GetCharSize ( info->charForm );
696 
697 	// Look for a packet wrapper. For our purposes, we can be lazy and just look for the trailer PI.
698 	// If that is present we'll assume that a recognizable header is present. First do a bytewise
699 	// search for '<', then a char sized comparison for the start of the trailer. We don't really
700 	// care about big or little endian here. We're looking for ASCII bytes with zeroes between.
701 	// Shorten the range comparisons (n*charSize) by 1 to easily tolerate both big and little endian.
702 
703 	XMP_StringLen padStart, padEnd;
704 	XMP_StringPtr packetTrailer = kPacketTrailiers [ charSize>>1 ];
705 
706 	padEnd = packetLen - 1;
707 	for ( ; padEnd > 0; --padEnd ) if ( packetStr[padEnd] == '<' ) break;
708 	if ( (packetStr[padEnd] != '<') || ((packetLen - padEnd) < (18*charSize)) ) return;
709 	if ( ! CheckBytes ( &packetStr[padEnd], packetTrailer, (13*charSize) ) ) return;
710 
711 	info->hasWrapper = true;
712 
713 	char rwFlag = packetStr [padEnd + 15*charSize];
714 	if ( rwFlag == 'w' ) info->writeable = true;
715 
716 	// Look for the start of the padding, right after the last XML end tag.
717 
718 	padStart = padEnd;	// Don't do the -charSize here, might wrap below zero.
719 	for ( ; padStart >= charSize; padStart -= charSize ) if ( packetStr[padStart] == '>' ) break;
720 	if ( padStart < charSize ) return;
721 	padStart += charSize;	// The padding starts after the '>'.
722 
723 	info->padSize = padEnd - padStart;	// We want bytes of padding, not character units.
724 
725 }	// FillPacketInfo
726 
727 // =================================================================================================
728 // ReadXMPPacket
729 // =============
730 
ReadXMPPacket(XMPFileHandler * handler)731 void ReadXMPPacket ( XMPFileHandler * handler )
732 {
733 	LFA_FileRef   fileRef   = handler->parent->fileRef;
734 	std::string & xmpPacket = handler->xmpPacket;
735 	XMP_StringLen packetLen = handler->packetInfo.length;
736 
737 	if ( packetLen == 0 ) XMP_Throw ( "ReadXMPPacket - No XMP packet", kXMPErr_BadXMP );
738 
739 	xmpPacket.erase();
740 	xmpPacket.reserve ( packetLen );
741 	xmpPacket.append ( packetLen, ' ' );
742 
743 	XMP_StringPtr packetStr = XMP_StringPtr ( xmpPacket.c_str() );	// Don't set until after reserving the space!
744 
745 	LFA_Seek ( fileRef, handler->packetInfo.offset, SEEK_SET );
746 	LFA_Read ( fileRef, (char*)packetStr, packetLen, kLFA_RequireAll );
747 
748 }	// ReadXMPPacket
749 
750 // =================================================================================================
751 // XMPFileHandler::ProcessTNail
752 // ============================
753 
ProcessTNail()754 void XMPFileHandler::ProcessTNail()
755 {
756 
757 	this->processedTNail = true;	// ! Must be overridden by handlers that support thumbnails.
758 
759 }	// XMPFileHandler::ProcessTNail
760 
761 // =================================================================================================
762 // XMPFileHandler::ProcessXMP
763 // ==========================
764 //
765 // This base implementation just parses the XMP. If the derived handler does reconciliation then it
766 // must have its own implementation of ProcessXMP.
767 
ProcessXMP()768 void XMPFileHandler::ProcessXMP()
769 {
770 
771 	if ( (!this->containsXMP) || this->processedXMP ) return;
772 
773 	if ( this->handlerFlags & kXMPFiles_CanReconcile ) {
774 		XMP_Throw ( "Reconciling file handlers must implement ProcessXMP", kXMPErr_InternalFailure );
775 	}
776 
777 	SXMPUtils::RemoveProperties ( &this->xmpObj, 0, 0, kXMPUtil_DoAllProperties );
778 	this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
779 	this->processedXMP = true;
780 
781 }	// XMPFileHandler::ProcessXMP
782 
783 // =================================================================================================
784 // XMPFileHandler::GetSerializeOptions
785 // ===================================
786 //
787 // This base implementation just selects compact serialization. The character form and padding/in-place
788 // settings are added in the common code before calling SerializeToBuffer.
789 
GetSerializeOptions()790 XMP_OptionBits XMPFileHandler::GetSerializeOptions()
791 {
792 
793 	return kXMP_UseCompactFormat;
794 
795 }	// XMPFileHandler::GetSerializeOptions
796 
797 // =================================================================================================
798