Common/Source/CFGrowlAdditions.c
author Rudy Richter
Wed Jun 24 23:01:22 2009 -0400 (2009-06-24)
changeset 4229 7293e4e2f375
parent 4095 7f5387ba611d
child 4246 4f52d1d98978
permissions -rw-r--r--
fixes bug #385044
     1 //
     2 //  CFGrowlAdditions.c
     3 //  Growl
     4 //
     5 //  Created by Mac-arena the Bored Zo on Wed Jun 18 2004.
     6 //  Copyright 2005-2006 The Growl Project.
     7 //
     8 // This file is under the BSD License, refer to License.txt for details
     9 
    10 #include <Carbon/Carbon.h>
    11 #include <unistd.h>
    12 #include <netinet/in.h>
    13 #include <arpa/inet.h>
    14 #include <netdb.h>
    15 #include "CFGrowlAdditions.h"
    16 
    17 #ifndef MIN
    18 # define MIN(a,b) ((a) < (b) ? (a) : (b))
    19 #endif
    20 
    21 extern Boolean CFStringGetFileSystemRepresentation() __attribute__((weak_import));
    22 extern CFIndex CFStringGetMaximumSizeOfFileSystemRepresentation(CFStringRef string) __attribute__((weak_import));
    23 
    24 char *createFileSystemRepresentationOfString(CFStringRef str) {
    25 	char *buffer;
    26 #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4
    27 	/* CFStringGetFileSystemRepresentation will cause a link error despite the weak_import attribute above on 10.5 when compiling with 10.2 compatibility using gcc 3.3.
    28 	 * PPC will therefore always use the 10.3 and below method of creating a file system representation.
    29 	 */
    30 	if (CFStringGetFileSystemRepresentation) {
    31 		CFIndex size = CFStringGetMaximumSizeOfFileSystemRepresentation(str);
    32 		buffer = malloc(size);
    33 		CFStringGetFileSystemRepresentation(str, buffer, size);
    34 	} else 
    35 #endif
    36 	{
    37 		buffer = malloc(512);
    38 		CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, str, kCFURLPOSIXPathStyle, false);
    39 		if (!CFURLGetFileSystemRepresentation(url, false, (UInt8 *)buffer, 512)) {
    40 			free(buffer);
    41 			buffer = NULL;
    42 		}
    43 		CFRelease(url);
    44 	}
    45 	return buffer;
    46 }
    47 
    48 STRING_TYPE createStringWithDate(CFDateRef date) {
    49 	CFLocaleRef locale = CFLocaleCopyCurrent();
    50 	CFDateFormatterRef dateFormatter = CFDateFormatterCreate(kCFAllocatorDefault,
    51 															 locale,
    52 															 kCFDateFormatterMediumStyle,
    53 															 kCFDateFormatterMediumStyle);
    54 	CFRelease(locale);
    55 	CFStringRef dateString = CFDateFormatterCreateStringWithDate(kCFAllocatorDefault,
    56 																 dateFormatter,
    57 																 date);
    58 	CFRelease(dateFormatter);
    59 	return dateString;
    60 }
    61 
    62 STRING_TYPE createStringWithContentsOfFile(CFStringRef filename, CFStringEncoding encoding) {
    63 	CFStringRef str = NULL;
    64 
    65 	char *path = createFileSystemRepresentationOfString(filename);
    66 	if (path) {
    67 		FILE *fp = fopen(path, "rb");
    68 		if (fp) {
    69 			fseek(fp, 0, SEEK_END);
    70 			unsigned long size = ftell(fp);
    71 			fseek(fp, 0, SEEK_SET);
    72 			unsigned char *buffer = malloc(size);
    73 			if (buffer && fread(buffer, 1, size, fp) == size)
    74 				str = CFStringCreateWithBytes(kCFAllocatorDefault, buffer, size, encoding, true);
    75 			fclose(fp);
    76 		}
    77 		free(path);
    78 	}
    79 
    80 	return str;
    81 }
    82 
    83 STRING_TYPE createStringWithStringAndCharacterAndString(STRING_TYPE str0, UniChar ch, STRING_TYPE str1) {
    84 	CFStringRef cfstr0 = (CFStringRef)str0;
    85 	CFStringRef cfstr1 = (CFStringRef)str1;
    86 	CFIndex len0 = (cfstr0 ? CFStringGetLength(cfstr0) : 0);
    87 	CFIndex len1 = (cfstr1 ? CFStringGetLength(cfstr1) : 0);
    88 	unsigned length = (len0 + (ch != 0xffff) + len1);
    89 
    90 	UniChar *buf = malloc(sizeof(UniChar) * length);
    91 	unsigned i = 0U;
    92 
    93 	if (cfstr0) {
    94 		CFStringGetCharacters(cfstr0, CFRangeMake(0, len0), buf);
    95 		i += len0;
    96 	}
    97 	if (ch != 0xffff)
    98 		buf[i++] = ch;
    99 	if (cfstr1)
   100 		CFStringGetCharacters(cfstr1, CFRangeMake(0, len1), &buf[i]);
   101 
   102 	return CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, buf, length, /*contentsDeallocator*/ kCFAllocatorMalloc);
   103 }
   104 
   105 char *copyCString(STRING_TYPE str, CFStringEncoding encoding) {
   106 	CFIndex size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), encoding) + 1;
   107 	char *buffer = calloc(size, 1);
   108 	CFStringGetCString(str, buffer, size, encoding);
   109 	return buffer;
   110 }
   111 
   112 STRING_TYPE copyCurrentProcessName(void) {
   113 	ProcessSerialNumber PSN = { 0, kCurrentProcess };
   114 	CFStringRef name = NULL;
   115 	OSStatus err = CopyProcessName(&PSN, &name);
   116 	if (err != noErr) {
   117 		NSLog(CFSTR("in copyCurrentProcessName in CFGrowlAdditions: Could not get process name because CopyProcessName returned %li"), (long)err);
   118 		name = NULL;
   119 	}
   120 	return name;
   121 }
   122 
   123 URL_TYPE copyCurrentProcessURL(void) {
   124 	ProcessSerialNumber psn = { 0, kCurrentProcess };
   125 	FSRef fsref;
   126 	CFURLRef URL = NULL;
   127 	OSStatus err = GetProcessBundleLocation(&psn, &fsref);
   128 	if (err != noErr) {
   129 		NSLog(CFSTR("in copyCurrentProcessURL in CFGrowlAdditions: Could not get application location, because GetProcessBundleLocation returned %li\n"), (long)err);
   130 	} else {
   131 		URL = CFURLCreateFromFSRef(kCFAllocatorDefault, &fsref);
   132 	}
   133 	return URL;
   134 }
   135 STRING_TYPE copyCurrentProcessPath(void) {
   136 	CFURLRef URL = copyCurrentProcessURL();
   137 	CFStringRef path = CFURLCopyFileSystemPath(URL, kCFURLPOSIXPathStyle);
   138 	CFRelease(URL);
   139 	return path;
   140 }
   141 
   142 URL_TYPE copyTemporaryFolderURL(void) {
   143 	FSRef ref;
   144 	CFURLRef url = NULL;
   145 
   146 	OSStatus err = FSFindFolder(kOnAppropriateDisk, kTemporaryFolderType, kCreateFolder, &ref);
   147 	if (err != noErr)
   148 		NSLog(CFSTR("in copyTemporaryFolderPath in CFGrowlAdditions: Could not locate temporary folder because FSFindFolder returned %li"), (long)err);
   149 	else
   150 		url = CFURLCreateFromFSRef(kCFAllocatorDefault, &ref);
   151 
   152 	return url;
   153 }
   154 STRING_TYPE copyTemporaryFolderPath(void) {
   155 	CFStringRef path = NULL;
   156 
   157 	CFURLRef url = copyTemporaryFolderURL();
   158 	if (url) {
   159 		path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
   160 		CFRelease(url);
   161 	}
   162 
   163 	return path;
   164 }
   165 
   166 DATA_TYPE readFile(const char *filename)
   167 {
   168 	CFDataRef data;
   169 	// read the file into a CFDataRef
   170 	FILE *fp = fopen(filename, "r");
   171 	if (fp) {
   172 		fseek(fp, 0, SEEK_END);
   173 		long dataLength = ftell(fp);
   174 		fseek(fp, 0, SEEK_SET);
   175 		unsigned char *fileData = malloc(dataLength);
   176 		fread(fileData, 1, dataLength, fp);
   177 		fclose(fp);
   178 		data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, fileData, dataLength, kCFAllocatorMalloc);
   179 	} else
   180 		data = NULL;
   181 
   182 	return data;
   183 }
   184 
   185 URL_TYPE copyURLForApplication(STRING_TYPE appName)
   186 {
   187 	CFURLRef appURL = NULL;
   188 	OSStatus err = LSFindApplicationForInfo(/*inCreator*/  kLSUnknownCreator,
   189 											/*inBundleID*/ NULL,
   190 											/*inName*/     appName,
   191 											/*outAppRef*/  NULL,
   192 											/*outAppURL*/  &appURL);
   193 	return (err == noErr) ? appURL : NULL;
   194 }
   195 
   196 STRING_TYPE createStringWithAddressData(DATA_TYPE aAddressData) {
   197 	struct sockaddr *socketAddress = (struct sockaddr *)CFDataGetBytePtr(aAddressData);
   198 	// IPv6 Addresses are "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"
   199 	//      at max, which is 40 bytes (0-terminated)
   200 	// IPv4 Addresses are "255.255.255.255" at max which is smaller
   201 	char stringBuffer[40];
   202 	CFStringRef addressAsString = NULL;
   203 	if (socketAddress->sa_family == AF_INET) {
   204 		struct sockaddr_in *ipv4 = (struct sockaddr_in *)socketAddress;
   205 		if (inet_ntop(AF_INET, &(ipv4->sin_addr), stringBuffer, 40))
   206 			addressAsString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s:%d"), stringBuffer, ipv4->sin_port);
   207 		else
   208 			addressAsString = CFSTR("IPv4 un-ntopable");
   209 	} else if (socketAddress->sa_family == AF_INET6) {
   210 		struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)socketAddress;
   211 		if (inet_ntop(AF_INET6, &(ipv6->sin6_addr), stringBuffer, 40))
   212 			// Suggested IPv6 format (see http://www.faqs.org/rfcs/rfc2732.html)
   213 			addressAsString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("[%s]:%d"), stringBuffer, ipv6->sin6_port);
   214 		else
   215 			addressAsString = CFSTR("IPv6 un-ntopable");
   216 	} else
   217 		addressAsString = CFSTR("neither IPv6 nor IPv4");
   218 
   219 	return addressAsString;
   220 }
   221 
   222 STRING_TYPE createHostNameForAddressData(DATA_TYPE aAddressData) {
   223 	char hostname[NI_MAXHOST];
   224 	struct sockaddr *socketAddress = (struct sockaddr *)CFDataGetBytePtr(aAddressData);
   225 	if (getnameinfo(socketAddress, CFDataGetLength(aAddressData),
   226 					hostname, sizeof(hostname),
   227 					/*serv*/ NULL, /*servlen*/ 0,
   228 					NI_NAMEREQD))
   229 		return NULL;
   230 	else
   231 		return CFStringCreateWithCString(kCFAllocatorDefault, hostname, kCFStringEncodingASCII);
   232 }
   233 
   234 DATA_TYPE copyIconDataForPath(STRING_TYPE path) {
   235 	CFDataRef data = NULL;
   236 
   237 	//false is probably safest, and is harmless when the object really is a directory.
   238 	CFURLRef URL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path, kCFURLPOSIXPathStyle, /*isDirectory*/ false);
   239 	if (URL) {
   240 		data = copyIconDataForURL(URL);
   241 		CFRelease(URL);
   242 	}
   243 
   244 	return data;
   245 }
   246 
   247 DATA_TYPE copyIconDataForURL(URL_TYPE URL)
   248 {
   249 	CFDataRef data = NULL;
   250 
   251 	if (URL) {
   252 		FSRef ref;
   253 		if (CFURLGetFSRef(URL, &ref)) {
   254 			IconRef icon = NULL;
   255 			SInt16 label_noOneCares;
   256 			OSStatus err = GetIconRefFromFileInfo(&ref,
   257 												  /*inFileNameLength*/ 0U, /*inFileName*/ NULL,
   258 												  kFSCatInfoNone, /*inCatalogInfo*/ NULL,
   259 												  kIconServicesNoBadgeFlag | kIconServicesUpdateIfNeededFlag,
   260 												  &icon,
   261 												  &label_noOneCares);
   262 			if (err != noErr) {
   263 				NSLog(CFSTR("in copyIconDataForURL in CFGrowlAdditions: could not get icon for %@: GetIconRefFromFileInfo returned %li\n"), URL, (long)err);
   264 			} else {
   265 				IconFamilyHandle fam = NULL;
   266 				err = IconRefToIconFamily(icon, kSelectorAllAvailableData, &fam);
   267 				if (err != noErr) {
   268 					NSLog(CFSTR("in copyIconDataForURL in CFGrowlAdditions: could not get icon for %@: IconRefToIconFamily returned %li\n"), URL, (long)err);
   269 				} else {
   270 					HLock((Handle)fam);
   271 					data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)*(Handle)fam, GetHandleSize((Handle)fam));
   272 					HUnlock((Handle)fam);
   273 					DisposeHandle((Handle)fam);
   274 				}
   275 				ReleaseIconRef(icon);
   276 			}
   277 		}
   278 	}
   279 
   280 	return data;
   281 }
   282 
   283 URL_TYPE createURLByMakingDirectoryAtURLWithName(URL_TYPE parent, STRING_TYPE name)
   284 {
   285 	CFURLRef newDirectory = NULL;
   286 
   287 	CFAllocatorRef allocator = parent ? CFGetAllocator(parent) : name ? CFGetAllocator(name) : kCFAllocatorDefault;
   288 
   289 	if (parent) parent = CFRetain(parent);
   290 	else {
   291 		char *cwdBytes = alloca(PATH_MAX);
   292 		getcwd(cwdBytes, PATH_MAX);
   293 		parent = CFURLCreateFromFileSystemRepresentation(allocator, (const unsigned char *)cwdBytes, strlen(cwdBytes), /*isDirectory*/ true);
   294 		if (!name) {
   295 			newDirectory = parent;
   296 			goto end;
   297 		}
   298 	}
   299 	if (!parent)
   300 		NSLog(CFSTR("in createURLByMakingDirectoryAtURLWithName in CFGrowlAdditions: parent directory URL is NULL (please tell the Growl developers)\n"), parent);
   301 	else {
   302 		if (name)
   303 			name = CFRetain(name);
   304 		else {
   305 			name = CFURLCopyLastPathComponent(parent);
   306 			CFURLRef newParent = CFURLCreateCopyDeletingLastPathComponent(allocator, parent);
   307 			CFRelease(parent);
   308 			parent = newParent;
   309 		}
   310 
   311 		if (!name)
   312 			NSLog(CFSTR("in createURLByMakingDirectoryAtURLWithName in CFGrowlAdditions: name of directory to create is NULL (please tell the Growl developers)\n"), parent);
   313 		else {
   314 			FSRef parentRef;
   315 			if (!CFURLGetFSRef(parent, &parentRef))
   316 				NSLog(CFSTR("in createURLByMakingDirectoryAtURLWithName in CFGrowlAdditions: could not create FSRef for parent directory at %@ (please tell the Growl developers)\n"), parent);
   317 			else {
   318 				FSRef newDirectoryRef;
   319 
   320 				struct HFSUniStr255 nameUnicode;
   321 				CFRange range = { 0, MIN(CFStringGetLength(name), USHRT_MAX) };
   322 				CFStringGetCharacters(name, range, nameUnicode.unicode);
   323 				nameUnicode.length = range.length;
   324 
   325 				struct FSRefParam refPB = {
   326 					.ref              = &parentRef,
   327 					.nameLength       = nameUnicode.length,
   328 					.name             = nameUnicode.unicode,
   329 					.whichInfo        = kFSCatInfoNone,
   330 					.catInfo          = NULL,
   331 					.textEncodingHint = kTextEncodingUnknown,
   332 					.newRef           = &newDirectoryRef,
   333 				};
   334 
   335 				OSStatus err = PBCreateDirectoryUnicodeSync(&refPB);
   336 				if (err == dupFNErr) {
   337 					//dupFNErr == file (or folder) exists already. this is fine.
   338 					err = PBMakeFSRefUnicodeSync(&refPB);
   339 				}
   340 				if (err == noErr) {
   341 					NSLog(CFSTR("PBCreateDirectoryUnicodeSync or PBMakeFSRefUnicodeSync returned %li; calling CFURLCreateFromFSRef"), (long)err); //XXX
   342 					newDirectory = CFURLCreateFromFSRef(allocator, &newDirectoryRef);
   343 					NSLog(CFSTR("CFURLCreateFromFSRef returned %@"), newDirectory); //XXX
   344 				} else
   345 					NSLog(CFSTR("in createURLByMakingDirectoryAtURLWithName in CFGrowlAdditions: could not create directory '%@' in parent directory at %@: FSCreateDirectoryUnicode returned %li (please tell the Growl developers)"), name, parent, (long)err);
   346 			}
   347 
   348 		} //if (name)
   349 		if(parent)
   350 			CFRelease(parent);
   351 		if(name)
   352 			CFRelease(name);
   353 	} //if (parent)
   354 
   355 end:
   356 	return newDirectory;
   357 }
   358 
   359 #ifndef COPYFORK_BUFSIZE
   360 #	define COPYFORK_BUFSIZE 5242880U /*5 MiB*/
   361 #endif
   362 
   363 static OSStatus copyFork(const struct HFSUniStr255 *forkName, const FSRef *srcFile, const FSRef *destDir, const struct HFSUniStr255 *destName, FSRef *outDestFile) {
   364 	OSStatus err, closeErr;
   365 	struct FSForkIOParam srcPB = {
   366 		.ref = srcFile,
   367 		.forkNameLength = forkName->length,
   368 		.forkName = forkName->unicode,
   369 		.permissions = fsRdPerm,
   370 	};
   371 	unsigned char debuggingPathBuf[PATH_MAX] = "";
   372 	OSStatus debuggingPathErr;
   373 
   374 	err = PBOpenForkSync(&srcPB);
   375 	if (err != noErr) {
   376 		debuggingPathErr = FSRefMakePath(srcFile, debuggingPathBuf, PATH_MAX);
   377 		if (debuggingPathErr != noErr)
   378 			snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for source file: FSRefMakePath returned %li)", (long)debuggingPathErr);
   379 		NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBOpenForkSync (source: %s) returned %li"), debuggingPathBuf, (long)err);
   380 	} else {
   381 		FSRef destFile;
   382 
   383 		/*the first thing to do is get the name of the destination file, if one
   384 		 *	wasn't provided.
   385 		 *and while we're at it, we get the catalogue info as well.
   386 		 */
   387 		struct FSCatalogInfo catInfo;
   388 		struct FSRefParam refPB = {
   389 			.ref       = srcFile,
   390 			.whichInfo = kFSCatInfoGettableInfo & kFSCatInfoSettableInfo,
   391 			.catInfo   = &catInfo,
   392 			.spec      = NULL,
   393 			.parentRef = NULL,
   394 			.outName   = destName ? NULL : (struct HFSUniStr255 *)(destName = alloca(sizeof(struct HFSUniStr255))),
   395 		};
   396 
   397 		err = PBGetCatalogInfoSync(&refPB);
   398 		if (err != noErr) {
   399 			debuggingPathErr = FSRefMakePath(srcFile, debuggingPathBuf, PATH_MAX);
   400 			if (debuggingPathErr != noErr)
   401 				snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for source file: FSRefMakePath returned %li)", (long)debuggingPathErr);
   402 			NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBGetCatalogInfoSync (source: %s) returned %li"), debuggingPathBuf, (long)err);
   403 		} else {
   404 			refPB.ref              = destDir;
   405 			refPB.nameLength       = destName->length;
   406 			refPB.name             = destName->unicode;
   407 			refPB.textEncodingHint = kTextEncodingUnknown;
   408 			refPB.newRef           = &destFile;
   409 
   410 			const char *functionName = "PBMakeFSRefUnicodeSync"; //for error-reporting message
   411 
   412 			err = PBMakeFSRefUnicodeSync(&refPB);
   413 			if ((err != noErr) && (err != fnfErr)) {
   414 			handleMakeFSRefError:
   415 				debuggingPathErr = FSRefMakePath(destDir, debuggingPathBuf, PATH_MAX);
   416 				if (debuggingPathErr != noErr)
   417 					snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for destination directory: FSRefMakePath returned %li)", (long)debuggingPathErr);
   418 
   419 				//get filename too
   420 				CFStringRef debuggingFilename = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
   421 																				   destName->unicode,
   422 																				   destName->length,
   423 																				   /*contentsDeallocator*/ kCFAllocatorNull);
   424 				if (!debuggingFilename)
   425 					debuggingFilename = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, "(could not get filename for destination file: CFStringCreateWithCharactersNoCopy returned NULL)", kCFStringEncodingASCII, /*contentsDeallocator*/ kCFAllocatorNull);
   426 
   427 				NSLog(CFSTR("in copyFork in CFGrowlAdditions: %s (destination: %s/%@) returned %li"), functionName, debuggingPathBuf, debuggingFilename, (long)err);
   428 
   429 				if (debuggingFilename) CFRelease(debuggingFilename);
   430 			} else {
   431 				//that file doesn't exist in that folder; create it.
   432 				err = PBCreateFileUnicodeSync(&refPB);
   433 				if (err == noErr) {
   434 					/*make sure the Finder knows about the new file.
   435 					 *FNNotify returns a status code too, but this isn't an
   436 					 *	essential step, so we just ignore it.
   437 					 */
   438 					FNNotify(destDir, kFNDirectoryModifiedMessage, kNilOptions);
   439 				} else if (err == dupFNErr) {
   440 					/*dupFNErr: the file already exists.
   441 					 *we can safely ignore this error.
   442 					 */
   443 					err = noErr;
   444 				} else {
   445 					functionName = "PBCreateFileUnicodeSync";
   446 					goto handleMakeFSRefError;
   447 				}
   448 			}
   449 		}
   450 		if (err == noErr) {
   451 			if (outDestFile)
   452 				memcpy(outDestFile, &destFile, sizeof(destFile));
   453 
   454 			struct FSForkIOParam destPB = {
   455 				.ref            = &destFile,
   456 				.forkNameLength = forkName->length,
   457 				.forkName       = forkName->unicode,
   458 				.permissions    = fsWrPerm,
   459 			};
   460 			err = PBOpenForkSync(&destPB);
   461 			NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBOpenForkSync (dest) returned %li"), (long)err);
   462 			if (err != noErr) {
   463 				debuggingPathErr = FSRefMakePath(&destFile, debuggingPathBuf, PATH_MAX);
   464 				if (debuggingPathErr != noErr)
   465 					snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for dest file: FSRefMakePath returned %li)", (long)debuggingPathErr);
   466 				NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBOpenForkSync (destination: %s) returned %li"), debuggingPathBuf, (long)err);
   467 			} else {
   468 				void *buf = malloc(COPYFORK_BUFSIZE);
   469 				if (buf) {
   470 					srcPB.buffer = destPB.buffer = buf;
   471 					srcPB.requestCount = COPYFORK_BUFSIZE;
   472 					while (err == noErr) {
   473 						err = PBReadForkSync(&srcPB);
   474 						if (err == eofErr) {
   475 							err = noErr;
   476 							if (srcPB.actualCount == 0)
   477 								break;
   478 						}
   479 						if (err != noErr) {
   480 							debuggingPathErr = FSRefMakePath(&destFile, debuggingPathBuf, PATH_MAX);
   481 							if (debuggingPathErr != noErr)
   482 								snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for source file: FSRefMakePath returned %li)", (long)debuggingPathErr);
   483 							NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBReadForkSync (source: %s) returned %li"), debuggingPathBuf, (long)err);
   484 						} else {
   485 							destPB.requestCount = srcPB.actualCount;
   486 							err = PBWriteForkSync(&destPB);
   487 							if (err != noErr) {
   488 								debuggingPathErr = FSRefMakePath(&destFile, debuggingPathBuf, PATH_MAX);
   489 								if (debuggingPathErr != noErr)
   490 									snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for dest file: FSRefMakePath returned %li)", (long)debuggingPathErr);
   491 								NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBWriteForkSync (destination: %s) returned %li"), debuggingPathBuf, (long)err);
   492 							}
   493 						}
   494 					}
   495 
   496 					free(buf);
   497 				}
   498 
   499 				closeErr = PBCloseForkSync(&destPB);
   500 				if (closeErr != noErr) {
   501 					debuggingPathErr = FSRefMakePath(&destFile, debuggingPathBuf, PATH_MAX);
   502 					if (debuggingPathErr != noErr)
   503 						snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for dest file: FSRefMakePath returned %li)", (long)debuggingPathErr);
   504 					NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBCloseForkSync (destination: %s) returned %li"), debuggingPathBuf, (long)err);
   505 				}
   506 				if (err == noErr) err = closeErr;
   507 			}
   508 		}
   509 
   510 		closeErr = PBCloseForkSync(&srcPB);
   511 		if (closeErr != noErr) {
   512 			debuggingPathErr = FSRefMakePath(&destFile, debuggingPathBuf, PATH_MAX);
   513 			if (debuggingPathErr != noErr)
   514 				snprintf((char *)debuggingPathBuf, PATH_MAX, "(could not get path for source file: FSRefMakePath returned %li)", (long)debuggingPathErr);
   515 			NSLog(CFSTR("in copyFork in CFGrowlAdditions: PBCloseForkSync (source: %s) returned %li"), debuggingPathBuf, (long)err);
   516 		}
   517 		if (err == noErr) err = closeErr;
   518 	}
   519 
   520 	return err;
   521 }
   522 
   523 static OSStatus GrowlCopyObjectSync(const FSRef *fileRef, const FSRef *destRef, FSRef *destFileRef) {
   524 	OSStatus err;
   525 	struct HFSUniStr255 forkName;
   526 	struct FSForkIOParam forkPB = {
   527 		.ref = fileRef,
   528 		.forkIterator = {
   529 			.initialize = 0L
   530 		},
   531 		.outForkName = &forkName,
   532 	};
   533 
   534 	do {
   535 		err = PBIterateForksSync(&forkPB);
   536 		NSLog(CFSTR("PBIterateForksSync returned %li"), (long)err);
   537 		if (err != noErr) {
   538 			if (err != errFSNoMoreItems)
   539 				NSLog(CFSTR("in GrowlCopyObjectSync in CFGrowlAdditions: PBIterateForksSync returned %li"), (long)err);
   540 		} else {
   541 			err = copyFork(&forkName, fileRef, destRef, /*destName*/ NULL, /*outDestFile*/ destFileRef);
   542 			//copyFork prints its own error messages
   543 		}
   544 	} while (err == noErr);
   545 	if (err == errFSNoMoreItems) err = noErr;
   546 
   547 	return err;
   548 }
   549 
   550 URL_TYPE createURLByCopyingFileFromURLToDirectoryURL(URL_TYPE file, URL_TYPE dest)
   551 {
   552 	CFURLRef destFileURL = NULL;
   553 
   554 	FSRef fileRef, destRef, destFileRef;
   555 	Boolean gotFileRef = CFURLGetFSRef(file, &fileRef);
   556 	Boolean gotDestRef = CFURLGetFSRef(dest, &destRef);
   557 	if (!gotFileRef)
   558 		NSLog(CFSTR("in createURLByCopyingFileFromURLToDirectoryURL in CFGrowlAdditions: CFURLGetFSRef failed with source URL %@"), file);
   559 	else if (!gotDestRef)
   560 		NSLog(CFSTR("in createURLByCopyingFileFromURLToDirectoryURL in CFGrowlAdditions: CFURLGetFSRef failed with destination URL %@"), dest);
   561 	else {
   562 		OSStatus err;
   563 
   564 		/*
   565 		 * 10.2 has a problem with weak symbols in frameworks so we use
   566 		 * MAC_OS_X_VERSION_MIN_REQUIRED >= 10.3.
   567 		 */
   568 #if defined(NSAppKitVersionNumber10_3) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_3
   569 		if (FSCopyObjectSync) {
   570 			err = FSCopyObjectSync(&fileRef, &destRef, /*destName*/ NULL, &destFileRef, kFSFileOperationOverwrite);
   571 		} else {
   572 #endif
   573 			err = GrowlCopyObjectSync(&fileRef, &destRef, &destFileRef);
   574 #if defined(NSAppKitVersionNumber10_3) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_3
   575 		}
   576 #endif
   577 
   578 		if (err == noErr)
   579 			destFileURL = CFURLCreateFromFSRef(kCFAllocatorDefault, &destFileRef);
   580 		else
   581 			NSLog(CFSTR("in createURLByCopyingFileFromURLToDirectoryURL in CFGrowlAdditions: CopyObjectSync returned %li for source URL %@"), (long)err, file);
   582 	}
   583 
   584 	return destFileURL;
   585 }
   586 
   587 PLIST_TYPE createPropertyListFromURL(URL_TYPE file, u_int32_t mutability, CFPropertyListFormat *outFormat, STRING_TYPE *outErrorString)
   588 {
   589 	CFPropertyListRef plist = NULL;
   590 
   591 	if (!file)
   592 		NSLog(CFSTR("in createPropertyListFromURL in CFGrowlAdditions: cannot read from a NULL URL"));
   593 	else {
   594 		CFReadStreamRef stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, file);
   595 		if (!stream)
   596 			NSLog(CFSTR("in createPropertyListFromURL in CFGrowlAdditions: could not create stream for reading from URL %@"), file);
   597 		else {
   598 			if (!CFReadStreamOpen(stream))
   599 				NSLog(CFSTR("in createPropertyListFromURL in CFGrowlAdditions: could not open stream for reading from URL %@"), file);
   600 			else {
   601 				CFPropertyListFormat format;
   602 				CFStringRef errorString = NULL;
   603 
   604 				plist = CFPropertyListCreateFromStream(kCFAllocatorDefault,
   605 													   stream,
   606 													   /*streamLength*/ 0,
   607 													   mutability,
   608 													   &format,
   609 													   &errorString);
   610 				if (!plist)
   611 					NSLog(CFSTR("in createPropertyListFromURL in CFGrowlAdditions: could not read property list from URL %@ (error string: %@)"), file, errorString);
   612 
   613 				if (outFormat) *outFormat = format;
   614 				if (errorString) {
   615 					if (outErrorString)
   616 						*outErrorString = errorString;
   617 					else
   618 						CFRelease(errorString);
   619 				}
   620 
   621 				CFReadStreamClose(stream);
   622 			}
   623 
   624 			CFRelease(stream);
   625 		}
   626 	}
   627 
   628 	return plist;
   629 }