@@ -422,86 +422,42 @@ extension String {
422
422
// MARK: - Filesystem String Extensions
423
423
424
424
#if !NO_FILESYSTEM
425
-
426
- internal static func homeDirectoryPath( forUser user : String ? = nil ) -> String {
425
+
426
+ internal static func homeDirectoryPath( ) -> String {
427
427
#if os(Windows)
428
- if let user {
429
- func fallbackUserDirectory( ) -> String {
430
- guard let fallback = ProcessInfo . processInfo. environment [ " ALLUSERSPROFILE " ] else {
431
- fatalError ( " Unable to find home directory for user \( user) and ALLUSERSPROFILE environment variable is not set " )
432
- }
433
-
434
- return fallback
435
- }
436
-
437
- guard !user. isEmpty else {
438
- return fallbackUserDirectory ( )
428
+ func fallbackCurrentUserDirectory( ) -> String {
429
+ guard let fallback = ProcessInfo . processInfo. environment [ " ALLUSERSPROFILE " ] else {
430
+ fatalError ( " Unable to find home directory for current user and ALLUSERSPROFILE environment variable is not set " )
439
431
}
440
432
441
- return user. withCString ( encodedAs: UTF16 . self) { pwszUserName in
442
- var cbSID : DWORD = 0
443
- var cchReferencedDomainName : DWORD = 0
444
- var eUse : SID_NAME_USE = SidTypeUnknown
445
- LookupAccountNameW ( nil , pwszUserName, nil , & cbSID, nil , & cchReferencedDomainName, & eUse)
446
- guard cbSID > 0 else {
447
- return fallbackUserDirectory ( )
448
- }
449
-
450
- return withUnsafeTemporaryAllocation ( of: CChar . self, capacity: Int ( cbSID) ) { pSID in
451
- return withUnsafeTemporaryAllocation ( of: WCHAR . self, capacity: Int ( cchReferencedDomainName) ) { pwszReferencedDomainName in
452
- guard LookupAccountNameW ( nil , pwszUserName, pSID. baseAddress, & cbSID, pwszReferencedDomainName. baseAddress, & cchReferencedDomainName, & eUse) else {
453
- return fallbackUserDirectory ( )
454
- }
455
-
456
- var pwszSID : LPWSTR ? = nil
457
- guard ConvertSidToStringSidW ( pSID. baseAddress, & pwszSID) else {
458
- fatalError ( " unable to convert SID to string for user \( user) " )
459
- }
460
-
461
- return #"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\ \#( String ( decodingCString: pwszSID!, as: UTF16 . self) ) "# . withCString ( encodedAs: UTF16 . self) { pwszKeyPath in
462
- return " ProfileImagePath " . withCString ( encodedAs: UTF16 . self) { pwszKey in
463
- var cbData : DWORD = 0
464
- RegGetValueW ( HKEY_LOCAL_MACHINE, pwszKeyPath, pwszKey, RRF_RT_REG_SZ, nil , nil , & cbData)
465
- guard cbData > 0 else {
466
- fatalError ( " unable to query ProfileImagePath for user \( user) " )
467
- }
468
- return withUnsafeTemporaryAllocation ( of: WCHAR . self, capacity: Int ( cbData) ) { pwszData in
469
- guard RegGetValueW ( HKEY_LOCAL_MACHINE, pwszKeyPath, pwszKey, RRF_RT_REG_SZ, nil , pwszData. baseAddress, & cbData) == ERROR_SUCCESS else {
470
- fatalError ( " unable to query ProfileImagePath for user \( user) " )
471
- }
472
- return String ( decodingCString: pwszData. baseAddress!, as: UTF16 . self)
473
- }
474
- }
475
- }
476
-
477
- }
478
- }
479
- }
433
+ return fallback
480
434
}
481
-
435
+
482
436
var hToken : HANDLE ? = nil
483
437
guard OpenProcessToken ( GetCurrentProcess ( ) , TOKEN_QUERY, & hToken) else {
484
438
guard let UserProfile = ProcessInfo . processInfo. environment [ " UserProfile " ] else {
485
- fatalError ( " unable to evaluate `%UserProfile%` " )
439
+ return fallbackCurrentUserDirectory ( )
486
440
}
487
441
return UserProfile
488
442
}
489
443
defer { CloseHandle ( hToken) }
490
-
444
+
491
445
var dwcchSize : DWORD = 0
492
446
_ = GetUserProfileDirectoryW ( hToken, nil , & dwcchSize)
493
-
447
+
494
448
return withUnsafeTemporaryAllocation ( of: WCHAR . self, capacity: Int ( dwcchSize) ) {
495
449
var dwcchSize : DWORD = DWORD ( $0. count)
496
450
guard GetUserProfileDirectoryW ( hToken, $0. baseAddress, & dwcchSize) else {
497
- fatalError ( " unable to query user profile directory " )
451
+ return fallbackCurrentUserDirectory ( )
498
452
}
499
453
return String ( decodingCString: $0. baseAddress!, as: UTF16 . self)
500
454
}
501
- #else // os(Windows)
502
-
455
+ #else
456
+
457
+
503
458
#if targetEnvironment(simulator)
504
- if user == nil , let envValue = getenv ( " CFFIXED_USER_HOME " ) ?? getenv ( " HOME " ) {
459
+ // Simulator checks these environment variables first for the current user
460
+ if let envValue = getenv ( " CFFIXED_USER_HOME " ) ?? getenv ( " HOME " ) {
505
461
return String ( cString: envValue) . standardizingPath
506
462
}
507
463
#endif
@@ -512,28 +468,81 @@ extension String {
512
468
}
513
469
514
470
#if !os(WASI) // WASI does not have user concept
515
- // Next, attempt to find the home directory via getpwnam/getpwuid
516
- if let user {
517
- if let dir = Platform . homeDirectory ( forUserName: user) {
518
- return dir. standardizingPath
519
- }
520
- } else {
521
- // We use the real UID instead of the EUID here when the EUID is the root user (i.e. a process has called seteuid(0))
522
- // In this instance, we historically do this to ensure a stable home directory location for processes that call seteuid(0)
523
- if let dir = Platform . homeDirectory ( forUID: Platform . getUGIDs ( allowEffectiveRootUID: false ) . uid) {
524
- return dir. standardizingPath
525
- }
471
+ // Next, attempt to find the home directory via getpwuid
472
+ // We use the real UID instead of the EUID here when the EUID is the root user (i.e. a process has called seteuid(0))
473
+ // In this instance, we historically do this to ensure a stable home directory location for processes that call seteuid(0)
474
+ if let dir = Platform . homeDirectory ( forUID: Platform . getUGIDs ( allowEffectiveRootUID: false ) . uid) {
475
+ return dir. standardizingPath
526
476
}
527
- #endif // !os(WASI)
528
-
477
+ #endif
478
+
529
479
// Fallback to HOME for the current user if possible
530
- if user == nil , let home = getenv ( " HOME " ) {
480
+ if let home = getenv ( " HOME " ) {
531
481
return String ( cString: home) . standardizingPath
532
482
}
533
483
534
- // If all else fails, log and fall back to /var/empty
484
+ // If all else fails, fall back to /var/empty
535
485
return " /var/empty "
536
- #endif // os(Windows)
486
+ #endif
487
+ }
488
+
489
+ internal static func homeDirectoryPath( forUser user: String ) -> String ? {
490
+ #if os(Windows)
491
+ guard !user. isEmpty else {
492
+ return nil
493
+ }
494
+
495
+ return user. withCString ( encodedAs: UTF16 . self) { pwszUserName in
496
+ var cbSID : DWORD = 0
497
+ var cchReferencedDomainName : DWORD = 0
498
+ var eUse : SID_NAME_USE = SidTypeUnknown
499
+ LookupAccountNameW ( nil , pwszUserName, nil , & cbSID, nil , & cchReferencedDomainName, & eUse)
500
+ guard cbSID > 0 else {
501
+ return nil
502
+ }
503
+
504
+ return withUnsafeTemporaryAllocation ( of: CChar . self, capacity: Int ( cbSID) ) { pSID in
505
+ return withUnsafeTemporaryAllocation ( of: WCHAR . self, capacity: Int ( cchReferencedDomainName) ) { pwszReferencedDomainName in
506
+ guard LookupAccountNameW ( nil , pwszUserName, pSID. baseAddress, & cbSID, pwszReferencedDomainName. baseAddress, & cchReferencedDomainName, & eUse) else {
507
+ return nil
508
+ }
509
+
510
+ var pwszSID : LPWSTR ? = nil
511
+ guard ConvertSidToStringSidW ( pSID. baseAddress, & pwszSID) else {
512
+ fatalError ( " unable to convert SID to string for user \( user) " )
513
+ }
514
+
515
+ return #"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\ \#( String ( decodingCString: pwszSID!, as: UTF16 . self) ) "# . withCString ( encodedAs: UTF16 . self) { pwszKeyPath in
516
+ return " ProfileImagePath " . withCString ( encodedAs: UTF16 . self) { pwszKey in
517
+ var cbData : DWORD = 0
518
+ RegGetValueW ( HKEY_LOCAL_MACHINE, pwszKeyPath, pwszKey, RRF_RT_REG_SZ, nil , nil , & cbData)
519
+ guard cbData > 0 else {
520
+ fatalError ( " unable to query ProfileImagePath for user \( user) " )
521
+ }
522
+ return withUnsafeTemporaryAllocation ( of: WCHAR . self, capacity: Int ( cbData) ) { pwszData in
523
+ guard RegGetValueW ( HKEY_LOCAL_MACHINE, pwszKeyPath, pwszKey, RRF_RT_REG_SZ, nil , pwszData. baseAddress, & cbData) == ERROR_SUCCESS else {
524
+ fatalError ( " unable to query ProfileImagePath for user \( user) " )
525
+ }
526
+ return String ( decodingCString: pwszData. baseAddress!, as: UTF16 . self)
527
+ }
528
+ }
529
+ }
530
+
531
+ }
532
+ }
533
+ }
534
+ #else
535
+ // First check CFFIXED_USER_HOME if the environment is not considered tainted
536
+ if let envVar = Platform . getEnvSecure ( " CFFIXED_USER_HOME " ) {
537
+ return envVar. standardizingPath
538
+ }
539
+ #if !os(WASI) // WASI does not have user concept
540
+ // Next, attempt to find the home directory via getpwnam
541
+ return Platform . homeDirectory ( forUserName: user) ? . standardizingPath
542
+ #else
543
+ return nil
544
+ #endif
545
+ #endif
537
546
}
538
547
539
548
// From swift-corelibs-foundation's NSTemporaryDirectory. Internal for now, pending a better public API.
@@ -674,13 +683,17 @@ extension String {
674
683
675
684
private var _expandingTildeInPath : String {
676
685
guard utf8. first == UInt8 ( ascii: " ~ " ) else { return self }
677
- var user : String ? = nil
678
686
let firstSlash = utf8. firstIndex ( of: . _slash) ?? endIndex
679
687
let indexAfterTilde = utf8. index ( after: startIndex)
688
+ var userDir : String
680
689
if firstSlash != indexAfterTilde {
681
- user = String ( self [ indexAfterTilde ..< firstSlash] )
690
+ guard let dir = String . homeDirectoryPath ( forUser: String ( self [ indexAfterTilde ..< firstSlash] ) ) else {
691
+ return self
692
+ }
693
+ userDir = dir
694
+ } else {
695
+ userDir = String . homeDirectoryPath ( )
682
696
}
683
- let userDir = String . homeDirectoryPath ( forUser: user)
684
697
return userDir + self [ firstSlash... ]
685
698
}
686
699
0 commit comments