@@ -422,86 +422,42 @@ extension String {
422422// MARK: - Filesystem String Extensions
423423
424424#if !NO_FILESYSTEM
425-
426- internal static func homeDirectoryPath( forUser user : String ? = nil ) -> String {
425+
426+ internal static func homeDirectoryPath( ) -> String {
427427 #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 " )
439431 }
440432
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
480434 }
481-
435+
482436 var hToken : HANDLE ? = nil
483437 guard OpenProcessToken ( GetCurrentProcess ( ) , TOKEN_QUERY, & hToken) else {
484438 guard let UserProfile = ProcessInfo . processInfo. environment [ " UserProfile " ] else {
485- fatalError ( " unable to evaluate `%UserProfile%` " )
439+ return fallbackCurrentUserDirectory ( )
486440 }
487441 return UserProfile
488442 }
489443 defer { CloseHandle ( hToken) }
490-
444+
491445 var dwcchSize : DWORD = 0
492446 _ = GetUserProfileDirectoryW ( hToken, nil , & dwcchSize)
493-
447+
494448 return withUnsafeTemporaryAllocation ( of: WCHAR . self, capacity: Int ( dwcchSize) ) {
495449 var dwcchSize : DWORD = DWORD ( $0. count)
496450 guard GetUserProfileDirectoryW ( hToken, $0. baseAddress, & dwcchSize) else {
497- fatalError ( " unable to query user profile directory " )
451+ return fallbackCurrentUserDirectory ( )
498452 }
499453 return String ( decodingCString: $0. baseAddress!, as: UTF16 . self)
500454 }
501- #else // os(Windows)
502-
455+ #else
456+
457+
503458 #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 " ) {
505461 return String ( cString: envValue) . standardizingPath
506462 }
507463 #endif
@@ -512,28 +468,81 @@ extension String {
512468 }
513469
514470 #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
526476 }
527- #endif // !os(WASI)
528-
477+ #endif
478+
529479 // Fallback to HOME for the current user if possible
530- if user == nil , let home = getenv ( " HOME " ) {
480+ if let home = getenv ( " HOME " ) {
531481 return String ( cString: home) . standardizingPath
532482 }
533483
534- // If all else fails, log and fall back to /var/empty
484+ // If all else fails, fall back to /var/empty
535485 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
537546 }
538547
539548 // From swift-corelibs-foundation's NSTemporaryDirectory. Internal for now, pending a better public API.
@@ -674,13 +683,17 @@ extension String {
674683
675684 private var _expandingTildeInPath : String {
676685 guard utf8. first == UInt8 ( ascii: " ~ " ) else { return self }
677- var user : String ? = nil
678686 let firstSlash = utf8. firstIndex ( of: . _slash) ?? endIndex
679687 let indexAfterTilde = utf8. index ( after: startIndex)
688+ var userDir : String
680689 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 ( )
682696 }
683- let userDir = String . homeDirectoryPath ( forUser: user)
684697 return userDir + self [ firstSlash... ]
685698 }
686699
0 commit comments