1/* -*- mode: C; coding: macintosh; -*- */
2
3#include "osxMacTcl.h"
4
5#if TARGET_RT_MAC_MACHO
6
7#ifndef __CARBON__
8#include <Carbon/Carbon.h>
9#endif
10
11#include <dlfcn.h>
12
13// ------------------------------------------------------------------------
14
15
16static Tcl_Encoding gFSpPathMacRomanEncoding=NULL;
17
18#define SetupFSpPathEncoding() { \
19	if(!gFSpPathMacRomanEncoding) \
20		gFSpPathMacRomanEncoding = Tcl_GetEncoding(NULL,"macRoman"); \
21}
22
23
24/*============================== CFStrings ==============================*/
25
26/*
27 * CFStringGetCString[Path]() with kCFStringEncodingUTF8 do not work.
28 * They return fully decomposed Utf8 characters which Tcl does not
29 * understand.  See Bug 587 and associated discussion on
30 * AlphaTcl-developers
31 *
32 * !!!  conversion through macRoman is grotesque and should not be
33 * necessary, but Tcl appears not to properly handle accented character
34 * encodings.  See Bug 587 and the associated discussion on
35 * AlphaTcl-developers. !!!
36 */
37
38#ifndef MAC_OS_X_VERSION_10_2
39/* define constants from 10.2 CFString.h to allow compilation in 10.1 */
40typedef enum {
41	kCFStringNormalizationFormD = 0, // Canonical Decomposition
42	kCFStringNormalizationFormKD, // Compatibility Decomposition
43	kCFStringNormalizationFormC, // Canonical Decomposition followed by Canonical Composition
44	kCFStringNormalizationFormKC // Compatibility Decomposition followed by Canonical Composition
45} CFStringNormalizationForm;
46#endif
47
48/*
49 *----------------------------------------------------------------------
50 *
51 * TryCFStringNormalize --
52 *
53 * call the 10.2 only CFStringNormalize() in a backwards compatible way.
54 *
55 * Results:
56 *	normalized mutable copy of string (retained, needs to be released!)
57 *  or NULL if CFStringNormalize not available.
58 *
59 * Side effects:
60 *	None.
61 *
62 *----------------------------------------------------------------------
63 */
64#include <mach-o/dyld.h>
65
66static CFMutableStringRef TryCFStringNormalize(CFStringRef theString, CFStringNormalizationForm theForm)
67{
68	static Boolean initialized = FALSE;
69	static void (*cfstringnormalize)(CFMutableStringRef, CFStringNormalizationForm) = NULL;
70
71	if(!initialized) {
72            void* handle = dlopen("CoreFoundation", RTLD_LAZY);
73            if (dlerror() != NULL) {
74                cfstringnormalize = dlsym(handle, "_CFStringNormalize");
75                dlclose(handle);
76                if (cfstringnormalize) {
77                    initialized = TRUE;
78                }
79            }
80	}
81	if(cfstringnormalize) {
82		CFMutableStringRef theMutableString = CFStringCreateMutableCopy(NULL, 0, theString);
83		if (theMutableString) {
84			cfstringnormalize(theMutableString, theForm);
85			return(theMutableString);
86		}
87	}
88	return(NULL);
89}
90
91/*
92 *----------------------------------------------------------------------
93 *
94 * CFStringToDString --
95 *
96 *	This helper function converts a CFString into a DString,
97 *  first transforming to canonical composed or decomposed unicode
98 *  depending on the 'compose' flag then transforming to external
99 *  (macRoman) encoding if 'external' is set.
100 *
101 *  Uses the most direct transformation possible on the current
102 *  system, e.g. CFStringNormalize if available, or by transcoding
103 *  to/from macRoman otherwise (potentially lossy!).
104 *
105 * Results:
106 *	Tcl error code.
107 *
108 * Side effects:
109 *	None.
110 *
111 *----------------------------------------------------------------------
112 */
113
114static int CFStringToDString(Tcl_Interp * interp, CFStringRef strRef, Tcl_DString * dsPtr,
115			Boolean compose, Boolean external)
116{
117	Boolean			success;
118	int				len;
119	int				result = TCL_ERROR;
120
121	CFStringRef 		theStrRef = NULL;
122	CFStringEncoding 	theEncoding;
123	Tcl_DString			ds, *theDsPtr = dsPtr;
124	Boolean				usedNormalize = FALSE;
125
126	if (compose)
127		theStrRef = TryCFStringNormalize(strRef, kCFStringNormalizationFormC);
128	else
129		theStrRef = TryCFStringNormalize(strRef, kCFStringNormalizationFormD);
130
131	if(theStrRef) {
132		usedNormalize = TRUE;
133
134	} else {
135		theStrRef = strRef;
136		theEncoding = kCFStringEncodingMacRoman;
137	}
138
139	if (usedNormalize && !external)
140		theEncoding = kCFStringEncodingUTF8;
141	else
142		theEncoding = kCFStringEncodingMacRoman;
143
144	if(!usedNormalize && !external)
145		theDsPtr = &ds; // will need ExternalToUtf conversion
146
147	len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(theStrRef), theEncoding);
148	Tcl_DStringInit(theDsPtr);
149	Tcl_DStringSetLength(theDsPtr, len);
150
151	success = CFStringGetCString(theStrRef, Tcl_DStringValue(theDsPtr), len+1, theEncoding);
152
153	if (success) {
154		/* len was only a guess */
155		Tcl_DStringSetLength(theDsPtr, strlen(Tcl_DStringValue(theDsPtr)));
156		result = TCL_OK;
157	} else
158		if (interp)  Tcl_SetResult(interp, "Can't extract string from CFString", TCL_STATIC);
159
160	if (!usedNormalize && !external) {
161		// need ExternalToUtf conversion
162		if(success) {
163			SetupFSpPathEncoding();
164			Tcl_ExternalToUtfDString(gFSpPathMacRomanEncoding,
165					Tcl_DStringValue(theDsPtr), Tcl_DStringLength(theDsPtr), dsPtr);
166		}
167		Tcl_DStringFree(theDsPtr);
168	}
169	if(usedNormalize)
170		CFRelease(theStrRef);
171
172	return result;
173}
174
175/*
176 *----------------------------------------------------------------------
177 *
178 * BufferToDString --
179 *
180 *	This helper function converts a text buffer of given lenth into
181 *  a DString (if length == -1, buffer is assumed to be a C string),
182 *  first transforming from external (macRoman) encoding if
183 *  'fromExternal' is set, then transforming to canonical composed
184 *  or decomposed unicode depending on the 'compose' flag and finally
185 *  transforming to external (macRoman) encoding if 'external' is set.
186 *
187 *  Tries to use the most efficient transformations possible on the
188 *  current  system, e.g. CFStringNormalize if available, and
189 *  CFStringCreateWithCStringNoCopy if given a C string.
190 *
191 * Results:
192 *	Tcl error code.
193 *
194 * Side effects:
195 *	None.
196 *
197 *----------------------------------------------------------------------
198 */
199
200static int BufferToDString(Tcl_Interp * interp, CONST84 char *buffer, int length,
201		Tcl_DString * dsPtr, Boolean compose, Boolean toExternal, Boolean fromExternal)
202{
203	int result = TCL_ERROR;
204	CFStringRef			theString;
205	CFStringEncoding 	theEncoding;
206
207	theEncoding = (fromExternal ? kCFStringEncodingMacRoman : kCFStringEncodingUTF8);
208
209	if(length < 0) //assume buffer is a C string
210	    theString = CFStringCreateWithCStringNoCopy(NULL, buffer, theEncoding, kCFAllocatorNull);
211	else
212	    theString = CFStringCreateWithBytes(NULL, (const unsigned char *) buffer, length, theEncoding, FALSE);
213
214	if(theString) {
215		result = CFStringToDString(interp, theString, dsPtr, compose, toExternal);
216		CFRelease(theString); // bug 671
217	} else {
218		if (interp)  Tcl_SetResult(interp, "Can't create CFString from buffer", TCL_STATIC);
219	}
220
221	return result;
222}
223
224/* CFString to external DString */
225int CFStringToExternalDString(Tcl_Interp * interp, CFStringRef strRef, Tcl_DString * dsPtr)
226{
227	return CFStringToDString(interp, strRef, dsPtr, TRUE, TRUE);
228}
229
230/* CFString to DString */
231int CFStringToUtfDString(Tcl_Interp * interp, CFStringRef strRef, Tcl_DString * dsPtr)
232{
233	return CFStringToDString(interp, strRef, dsPtr, TRUE, FALSE);
234}
235
236/* decomposed utf8 buffer to external DString */
237int DUtfToExternalDString(Tcl_Interp * interp, CONST84 char * src, int length, Tcl_DString * dsPtr)
238{
239	return BufferToDString(interp, src, length, dsPtr, TRUE, TRUE, FALSE);
240}
241
242/* decomposed utf8 buffer to DString */
243int DUtfToUtfDString(Tcl_Interp * interp, CONST84 char * src, int length, Tcl_DString * dsPtr)
244{
245	return BufferToDString(interp, src, length, dsPtr, TRUE, FALSE, FALSE);
246}
247
248/* external buffer to decomposed utf8 DString */
249int ExternalToDUtfDString(Tcl_Interp * interp, CONST84 char * src, int length, Tcl_DString * dsPtr)
250{
251	return BufferToDString(interp, src, length, dsPtr, FALSE, FALSE, TRUE);
252}
253
254/* utf8 buffer to decomposed utf8 DString */
255int UtfToDUtfDString(Tcl_Interp * interp, CONST84 char * src, int length, Tcl_DString * dsPtr)
256{
257	return BufferToDString(interp, src, length, dsPtr, FALSE, FALSE, FALSE);
258}
259
260static Tcl_Obj * _CFStringToTclObj(CFStringRef strRef)
261{
262	Tcl_Obj *		outObj;
263	CFIndex 		len = CFStringGetLength(strRef);
264	const UniChar *	chars = CFStringGetCharactersPtr(strRef);
265
266	if (chars != NULL) {
267		outObj = Tcl_NewUnicodeObj(chars, len);
268	} else {
269		UniChar * buffer = (UniChar*) ckalloc(len * sizeof(UniChar));
270		CFStringGetCharacters(strRef, CFRangeMake(0, len), buffer);
271		outObj = Tcl_NewUnicodeObj(buffer, len);
272		ckfree((char*) buffer);
273	}
274
275	return outObj;
276}
277
278
279/* CFStringRef to decomposed Unicode Tcl_Obj */
280Tcl_Obj * CFStringToTclObj(CFStringRef strRef)
281{
282	Tcl_Obj *			outObj;
283	CFStringRef 		theStrRef = NULL;
284
285	theStrRef = TryCFStringNormalize(strRef, kCFStringNormalizationFormC);
286
287/*
288 *     if (theStrRef != NULL) {
289 *         const UniChar *    chars = CFStringGetCharactersPtr(theStrRef);
290 *         CFIndex        len = CFStringGetLength(theStrRef);
291 *         outObj = Tcl_NewUnicodeObj(chars, len);
292 *         outObj = Tcl_NewUnicodeObj(CFStringGetCharactersPtr(theStrRef), CFStringGetLength(theStrRef));
293 *         CFRelease(theStrRef);
294 *     } else {
295 *         outObj = Tcl_NewUnicodeObj(CFStringGetCharactersPtr(strRef), CFStringGetLength(strRef));
296 *     }
297 */
298
299	if (theStrRef != NULL) {
300		outObj = _CFStringToTclObj(theStrRef);
301		CFRelease(theStrRef);
302	} else {
303		outObj = _CFStringToTclObj(strRef);
304	}
305
306	return outObj;
307}
308
309/* Unicode Tcl_Obj * to CFStringRef */
310CFStringRef TclObjToCFString(Tcl_Obj * inObj)
311{
312	if (inObj == NULL) {
313		return CFSTR("");
314	} else {
315		return CFStringCreateWithCharacters(NULL, Tcl_GetUnicode(inObj), Tcl_GetCharLength(inObj));
316	}
317}
318
319/*==============================        ==============================*/
320
321//  das 091200 reimplemented the following routines from scratch
322//             for Tcl on OSX using modern FileMgr APIs and FSRefs
323//             they are analogous to the MacTcl routines in tclMacUtil.c
324//
325//             on OSX these routines are used in oldEndre.c instead
326//             of the crufty old Alpha versions
327
328
329#define MAXPATHLEN 1024
330
331/*
332 *----------------------------------------------------------------------
333 *
334 * FSpLocationFromPath --
335 *
336 *	This function obtains an FSRef for a given macintosh path.
337 *	Unlike the More Files function FSpLocationFromFullPath, this
338 *	function will also accept partial paths and resolve any aliases
339 *	along the path. It will also create an FSRef for a path that
340 *	does not yet exist.
341 *
342 * Results:
343 *	OSErr code.
344 *
345 * Side effects:
346 *	None.
347 *
348 *----------------------------------------------------------------------
349 */
350
351
352OSErr
353FSpLocationFromPath(
354    int length,			/* Length of path. */
355    CONST84 char *path,		/* The path to convert. */
356    FSRefPtr fileRefPtr)	/* On return the reference for the path. */
357{
358    UInt8 fileName[MAXPATHLEN];
359	unsigned int fileNameLen;
360    OSErr err;
361    unsigned int pos, cur = 0;
362    Boolean isDirectory=TRUE, filenotexist=FALSE, wasAlias, done;
363	Tcl_DString ds;
364
365	// FSRefMakePath et al use deomposed UTF8 on OSX
366	if(ExternalToDUtfDString(NULL, path, length, &ds) == TCL_ERROR) {
367		err = coreFoundationUnknownErr;
368		goto done;
369	}
370
371	path = Tcl_DStringValue(&ds);
372	length = Tcl_DStringLength(&ds);
373
374    pos = 0;
375    fileName[0] = 0;
376	fileNameLen = 0;
377
378    /*
379     * Check to see if this is a full path.  If partial
380     * we assume that path starts with the current working
381     * directory.  (Ie. volume & dir = 0)
382     */
383    if ((done=(length == 0)) || (path[0] != '/')) {
384	     // start at current directory
385		{
386		CFBundleRef appBundle=CFBundleGetMainBundle();
387		CFURLRef	appURL=NULL, parentURL=NULL;
388		err = coreFoundationUnknownErr;
389		if(appBundle)
390		{
391			appURL=CFBundleCopyBundleURL(appBundle);
392			if(appURL)
393			{
394				parentURL=CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorDefault,appURL);
395				CFRelease(appURL);
396				if(parentURL)
397				{
398					if(CFURLGetFSRef(parentURL, fileRefPtr))
399						err=noErr;
400					CFRelease(parentURL);
401				}
402			}
403		}
404		}
405		if (err != noErr) goto done;
406		if(!done){
407			err = FSRefMakePath(fileRefPtr,fileName,MAXPATHLEN);
408			if (err != noErr) goto done;
409			fileNameLen=strlen((const char*) fileName);
410		}
411    } else {
412    if(path[0] == '/') {
413        if((done=(length == 1)))
414        {
415	    /*
416	     * If path = "/", just get root directory.
417	     */
418            err = FSPathMakeRef((UInt8 *) path, fileRefPtr, &isDirectory);
419            if (err != noErr) goto done;
420        } else {
421            pos++;
422        }
423
424    }
425    }
426    if(!done) {
427        fileName[fileNameLen++] = '/';
428        fileName[fileNameLen] = 0;
429
430    while (pos < length) {
431	if (!isDirectory || filenotexist) {err=dirNFErr; goto done;}
432	cur = pos;
433	while (path[pos] != '/' && pos < length) {
434	    pos++;
435	}
436	if (fileNameLen+pos-cur > MAXPATHLEN) {
437	    err=bdNamErr; goto done;
438	} else {
439	    strncpy((char*) fileName+fileNameLen, &path[cur], pos - cur);
440	    fileNameLen += pos - cur;
441	}
442        fileName[fileNameLen] = 0;
443        err = FSPathMakeRef(fileName, fileRefPtr, &isDirectory);
444        if ((err != noErr) && !(filenotexist=(err == fnfErr))) goto done;
445        if (!filenotexist) {
446			err = FSResolveAliasFile(fileRefPtr, true, &isDirectory, &wasAlias);
447			if (err != noErr) goto done;
448			if(wasAlias){
449				err = FSRefMakePath(fileRefPtr,fileName,MAXPATHLEN);
450            if (err != noErr) goto done;
451			fileNameLen=strlen((const char*) fileName);
452			}
453        }
454
455	if (path[pos] == '/') {
456            if (!isDirectory || filenotexist) {err=dirNFErr; goto done;}
457	    pos++;
458	    fileName[fileNameLen++] = '/';
459		fileName[fileNameLen] = 0;
460	}
461    }
462    }
463
464done:
465	Tcl_DStringFree(&ds);
466    return err;
467}
468
469/*
470 *----------------------------------------------------------------------
471 *
472 * FSpPathFromLocation --
473 *
474 *	This function obtains a full path name for a given macintosh
475 *	FSRef.  Unlike the More Files function FSpGetFullPath, this
476 *	function will return a C string in the Handle.  It also will
477 *	create paths for FSRef that do not yet exist.
478 *
479 * Results:
480 *	OSErr code.
481 *
482 * Side effects:
483 *	None.
484 *
485 *----------------------------------------------------------------------
486 */
487
488OSErr
489FSpPathFromLocation(
490    FSRefPtr fsrefP,		/* The location we want a path for. */
491    int *length,		/* Length of the resulting path. */
492    Handle *fullPath)		/* Handle to path. */
493{
494    OSErr err;
495    UInt8 fileName[MAXPATHLEN];
496        unsigned int fileNameLen;
497
498    *fullPath = NULL;
499
500    err = FSRefMakePath(fsrefP,fileName,MAXPATHLEN);
501    if (err == noErr) {
502        fileNameLen=strlen((const char*) fileName);
503        FSCatalogInfo catalogInfo;
504        err = FSGetCatalogInfo(fsrefP,kFSCatInfoNodeFlags,&catalogInfo,NULL,NULL,NULL);
505        if (err == noErr && (catalogInfo.nodeFlags & kFSNodeIsDirectoryMask)) {
506            // if we have a directory, end path with /
507            if(fileNameLen < MAXPATHLEN) {
508                fileName[fileNameLen++] = '/';
509            } else {
510                err=bdNamErr;
511            }
512        }
513        if (err == noErr) {
514            // FSRefMakePath et al use decomposed UTF8 on OSX
515            Tcl_DString ds;
516            fileName[fileNameLen] = 0; // add 0 cstr terminator
517            if (DUtfToExternalDString(NULL, (const char*) fileName, -1, &ds) == TCL_OK) {
518                err = PtrToHand(Tcl_DStringValue(&ds), fullPath, Tcl_DStringLength(&ds)+1);
519                *length = Tcl_DStringLength(&ds);
520                Tcl_DStringFree(&ds); // bug 671
521            } else {
522                err = coreFoundationUnknownErr;
523            }
524        }
525    }
526
527    /*
528     * On error Dispose the handle, set it to NULL & return the err.
529     * Otherwise, set the length & return.
530     */
531    if (err != noErr) {
532        if ( *fullPath != NULL ) {
533            DisposeHandle(*fullPath);
534        }
535        *fullPath = NULL;
536        *length = 0;
537    }
538
539    return err;
540}
541
542#endif
543