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 = FindNextFile ( 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 = FindNextFile ( 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