apple / swift-corelibs-foundation

The Foundation Project, providing core utilities, internationalization, and OS independence
swift.org
Apache License 2.0
5.26k stars 1.13k forks source link

[SR-12843] NSString.expandingTildeInPath not working correctly if HOME variable is set #3255

Open fabianfett opened 4 years ago

fabianfett commented 4 years ago
Previous ID SR-12843
Radar None
Original Reporter @fabianfett
Type Bug
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 0 | |Component/s | Foundation | |Labels | Bug | |Assignee | None | |Priority | Medium | md5: d00baaf0707960035a9b504222e29460

Issue Description:

If the default `$HOME` path is changed by an environment variable, `NSString.expandingTildeInPath` does not reflect this change.

As a user I would expect that `NSString.expandingTildeInPath` returns the same as `echo $HOME` or `echo \~` on the command line or a direct call against libc.

This problem surfaces when using GitHub actions where the default HOME path is changed to: `/github/home`

See: https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners

Code to reproduce:

#if os(Linux)
import Glibc
#else
import Darwin
#endif
import Foundation

func expandTildeInFilePath(_ filePath: String) -> String {
    filePath.withCString { (ptr) -> String in
        var wexp = wordexp_t()
        guard 0 == wordexp(ptr, &wexp, 0), let we_wordv = wexp.we_wordv else {
            return filePath
        }
        defer {
            wordfree(&wexp)
        }

        guard let resolved = we_wordv[0], let pth = String(cString: resolved, encoding: .utf8) else {
             return filePath
        }

        return pth
    }
}

let expandableFilePath = "~/test"

let expandedNewPath = expandTildeInFilePath(expandableFilePath)
let expandedNSString = NSString(string: expandableFilePath).expandingTildeInPath

print("results")
print("  Glibc/Darwin: \(expandedNewPath)")
print("  Foundation  : \(expandedNSString)")

Logs:

[ fat@fatBook-WLAN ] ~/Developer/nio/test
$> docker run -it --rm -v $(pwd):/src --workdir /src swift:5.2 /bin/bash                                  
root@4d76cca8d7e0:/src# swift build
root@4d76cca8d7e0:/src# .build/debug/test
results
  Glibc/Darwin: /root/test
  Foundation  : /root/test

[ fat@fatBook-WLAN ] ~/Developer/nio/test
$> docker run -it --rm -e HOME="/github/home" -v $(pwd):/src --workdir /src swift:5.2 /bin/bash
root@4d9507e716e5:/src# echo ~
/github/home
root@4d9507e716e5:/src# echo $HOME
/github/home
root@4d9507e716e5:/src# .build/debug/test
results
  Glibc/Darwin: /github/home/test
  Foundation  : /root/test

Verified with:

spevans commented 4 years ago

Although NSString.expandingTildeInPath does make use of $HOME it checks the user's home directory using getpwnam(3) first so the $HOME is not considered

(see _CFCopyHomeDirURLForUser in https://github.com/apple/swift-corelibs-foundation/blob/092c58acc6a3ec7839eaa226e0dd50f176dca46c/CoreFoundation/Base.subproj/CFPlatform.c#L228)

However a re-reading of https://developer.apple.com/documentation/foundation/1413045-nshomedirectory, the documentation for NSHomeDirectory, says that it returns "In macOS, it is the application’s sandbox directory or the current user’s home directory (if the application is not in a sandbox)"

The sandboxing sets the CFFIXED_USER_HOME environment variable which overrides the use of getpwnam so it can be used as follows:

$ docker run -it --rm -e HOME="/github/home" -v $(pwd):/src --workdir /src swift:5.2 /bin/bash
root@65419c9f1a73:/src# cat home.swift
import Foundation

print("NSHomeDirectory()", NSHomeDirectory())
let homedir = ("~/somedir" as NSString).expandingTildeInPath
print(homedir)

print("NSHomeDirectoryForUser(\"unknown\")", NSHomeDirectoryForUser("unknown") as Any)
let homedir2 = ("~unknown/somedir" as NSString).expandingTildeInPath
print(homedir2)
root@65419c9f1a73:/src# swiftc -o home home.swift
root@65419c9f1a73:/src# ./home
NSHomeDirectory() /root
/root/somedir
NSHomeDirectoryForUser("unknown") nil
~unknown/somedir
root@65419c9f1a73:/src# CFFIXED_USER_HOME=${HOME} ./home 
NSHomeDirectory() /github/home
/github/home/somedir
NSHomeDirectoryForUser("unknown") Optional("/github/home")
/github/home/somedir
root@65419c9f1a73:/src# 

So the best workaround to use $HOME would be to use

export CFFIXED_USER_HOME=${HOME}

The code that uses $HOME cant really be changed in CoreFoundation as it would probably break other programs depending on the behaviour.