fix: harden config-file include scanning
- Add cycle detection via visited path set to prevent infinite recursion on cyclic config-file includes. - Resolve relative include paths against the parent directory of the including config file. - Strip trailing '?' from optional include paths (Ghostty convention). - Use UUID-based path for missing file test. - Add tests for relative includes, optional includes, and cyclic includes.
This commit is contained in:
parent
12e91aa4fe
commit
a0ba82f8be
2 changed files with 78 additions and 8 deletions
|
|
@ -1109,9 +1109,10 @@ class GhosttyApp {
|
|||
"~/Library/Application Support/com.mitchellh.ghostty/config.ghostty",
|
||||
]
|
||||
) -> Bool {
|
||||
var visited = Set<String>()
|
||||
for rawPath in configPaths {
|
||||
let path = NSString(string: rawPath).expandingTildeInPath
|
||||
if Self.configFileContainsCodepointMap(atPath: path) {
|
||||
if Self.configFileContainsCodepointMap(atPath: path, visited: &visited) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -1119,11 +1120,21 @@ class GhosttyApp {
|
|||
}
|
||||
|
||||
/// Scans a single config file (and any files it includes) for
|
||||
/// `font-codepoint-map` entries.
|
||||
private static func configFileContainsCodepointMap(atPath path: String) -> Bool {
|
||||
guard let contents = try? String(contentsOfFile: path, encoding: .utf8) else {
|
||||
/// `font-codepoint-map` entries. Tracks visited paths to prevent
|
||||
/// infinite recursion on cyclic includes.
|
||||
private static func configFileContainsCodepointMap(
|
||||
atPath path: String,
|
||||
visited: inout Set<String>
|
||||
) -> Bool {
|
||||
let resolved = (path as NSString).standardizingPath
|
||||
guard !visited.contains(resolved) else { return false }
|
||||
visited.insert(resolved)
|
||||
|
||||
guard let contents = try? String(contentsOfFile: resolved, encoding: .utf8) else {
|
||||
return false
|
||||
}
|
||||
let parentDir = (resolved as NSString).deletingLastPathComponent
|
||||
|
||||
for line in contents.components(separatedBy: .newlines) {
|
||||
let trimmed = line.trimmingCharacters(in: .whitespaces)
|
||||
if trimmed.hasPrefix("#") { continue }
|
||||
|
|
@ -1133,11 +1144,18 @@ class GhosttyApp {
|
|||
if trimmed.hasPrefix("config-file") {
|
||||
let parts = trimmed.split(separator: "=", maxSplits: 1)
|
||||
if parts.count == 2 {
|
||||
let includePath = parts[1]
|
||||
var includePath = parts[1]
|
||||
.trimmingCharacters(in: .whitespaces)
|
||||
.trimmingCharacters(in: CharacterSet(charactersIn: "\""))
|
||||
let resolved = NSString(string: includePath).expandingTildeInPath
|
||||
if configFileContainsCodepointMap(atPath: resolved) {
|
||||
// Ghostty supports optional includes with a trailing '?'
|
||||
if includePath.hasSuffix("?") {
|
||||
includePath = String(includePath.dropLast())
|
||||
}
|
||||
let expanded = NSString(string: includePath).expandingTildeInPath
|
||||
let absolute = (expanded as NSString).isAbsolutePath
|
||||
? expanded
|
||||
: (parentDir as NSString).appendingPathComponent(expanded)
|
||||
if configFileContainsCodepointMap(atPath: absolute, visited: &visited) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1431,8 +1431,9 @@ final class GhosttyMouseFocusTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testUserConfigContainsCJKCodepointMapReturnsFalseForMissingFiles() {
|
||||
let path = NSTemporaryDirectory() + "cmux-nonexistent-\(UUID().uuidString)/config"
|
||||
XCTAssertFalse(
|
||||
GhosttyApp.userConfigContainsCJKCodepointMap(configPaths: ["/nonexistent/path/config"])
|
||||
GhosttyApp.userConfigContainsCJKCodepointMap(configPaths: [path])
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1452,4 +1453,55 @@ final class GhosttyMouseFocusTests: XCTestCase {
|
|||
|
||||
XCTAssertTrue(GhosttyApp.userConfigContainsCJKCodepointMap(configPaths: [main.path]))
|
||||
}
|
||||
|
||||
func testUserConfigContainsCJKCodepointMapFollowsRelativeIncludes() throws {
|
||||
let dir = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("cmux-test-cjk-rel-\(UUID().uuidString)")
|
||||
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
||||
defer { try? FileManager.default.removeItem(at: dir) }
|
||||
|
||||
let included = dir.appendingPathComponent("fonts.conf")
|
||||
try "font-codepoint-map = U+4E00-U+9FFF=Hiragino Sans\n"
|
||||
.write(to: included, atomically: true, encoding: .utf8)
|
||||
|
||||
let main = dir.appendingPathComponent("config")
|
||||
try "config-file = fonts.conf\n"
|
||||
.write(to: main, atomically: true, encoding: .utf8)
|
||||
|
||||
XCTAssertTrue(GhosttyApp.userConfigContainsCJKCodepointMap(configPaths: [main.path]))
|
||||
}
|
||||
|
||||
func testUserConfigContainsCJKCodepointMapHandlesOptionalInclude() throws {
|
||||
let dir = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("cmux-test-cjk-opt-\(UUID().uuidString)")
|
||||
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
||||
defer { try? FileManager.default.removeItem(at: dir) }
|
||||
|
||||
let included = dir.appendingPathComponent("fonts.conf")
|
||||
try "font-codepoint-map = U+4E00-U+9FFF=Hiragino Sans\n"
|
||||
.write(to: included, atomically: true, encoding: .utf8)
|
||||
|
||||
let main = dir.appendingPathComponent("config")
|
||||
try "config-file = \(included.path)?\n"
|
||||
.write(to: main, atomically: true, encoding: .utf8)
|
||||
|
||||
XCTAssertTrue(GhosttyApp.userConfigContainsCJKCodepointMap(configPaths: [main.path]))
|
||||
}
|
||||
|
||||
func testUserConfigContainsCJKCodepointMapHandlesCyclicIncludes() throws {
|
||||
let dir = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("cmux-test-cjk-cycle-\(UUID().uuidString)")
|
||||
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
||||
defer { try? FileManager.default.removeItem(at: dir) }
|
||||
|
||||
let fileA = dir.appendingPathComponent("a.conf")
|
||||
let fileB = dir.appendingPathComponent("b.conf")
|
||||
try "config-file = \(fileB.path)\n"
|
||||
.write(to: fileA, atomically: true, encoding: .utf8)
|
||||
try "config-file = \(fileA.path)\n"
|
||||
.write(to: fileB, atomically: true, encoding: .utf8)
|
||||
|
||||
// Should not hang; should return false since neither file has font-codepoint-map
|
||||
XCTAssertFalse(GhosttyApp.userConfigContainsCJKCodepointMap(configPaths: [fileA.path]))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue