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