A scheme or workspace with a control character in a value is written as malformed XML, and XcodeProj can't read it back
还没有人认领这个 Issue。
评估
调研方向
从 Sources/XcodeProj/Extensions/AEXML+XcodeFormat.swift 以及 xmlXcodeFormat 中属性和字符串值的转义处理入手。issue 描述了 Tests/XcodeProjTests/Extensions/XcodeFormatForbiddenCharacterTests.swift 中的测试;用它们检查禁止的控制字符、有效的控制字符,以及 scheme/workspace 往返转换。完成标准是写出的 XML 仍然可读,且完整的 swift test 测试套件通过。
由索引模型根据 Issue 内容生成。
描述
Context 🕵️♀️
XCScheme.write(path:override:), XCWorkspaceData.write(path:override:) and their dataRepresentation() render
through AEXMLDocument.xmlXcodeFormat (Sources/XcodeProj/Extensions/AEXML+XcodeFormat.swift). It escapes every
attribute value with AEXML's String.xmlEscaped, which handles &, <, >, ', ", \n, \r and \t. XML 1.0
also forbids, outright, the other C0 controls (U+0001–U+0008, U+000B, U+000C, U+000E–U+001F) and U+FFFE and U+FFFF.
No escape can write them;  is malformed too. xmlEscaped copies them through, so the file XcodeProj writes is
not well-formed, and XCScheme(path:) / XCWorkspaceData(path:) throw on it.
The values come from callers: an environment variable's value, a pre- or post-action's scriptText, a command-line
argument, a file reference's path. Tools that generate schemes from a manifest pass such strings straight through.
#530 is an XcodeGen pre-action script that reached this writer. A terminal colour code pasted into a value, with a
raw ESC (U+001B), is the likeliest way in.
What 🌱
import PathKit
import XcodeProj
let launch = XCScheme.LaunchAction(
runnable: nil,
buildConfiguration: "Debug",
environmentVariables: [XCScheme.EnvironmentVariable(variable: "PROMPT", value: "\u{1B}[31m$ \u{1B}[0m", enabled: true)]
)
let path = Path("App.xcscheme")
try XCScheme(name: "App", lastUpgradeVersion: "2600", version: "1.7", launchAction: launch).write(path: path, override: true)
_ = try XCScheme(path: path)
// throws: Error Domain=NSXMLParserErrorDomain Code=9 "(null)"
// UserInfo={NSXMLParserErrorLineNumber=18, NSXMLParserErrorColumn=22, NSXMLParserErrorMessage=invalid character in attribute value}
Line 18 of App.xcscheme, through cat -v: value = "^[[31m$ ^[[0m". A workspace does the same: a FileRef at
.group("a\u{1}b.swift") is written as location = "group:a^Ab.swift", and XCWorkspaceData(path:) throws code 9
at line 5. With "red $ " and "ab.swift" instead, both files read back. Every one of the 30 code points fails the
same way.
Proposal 🎉
Replace each of those characters with U+FFFD in xmlXcodeFormat, after xmlEscaped:
--- a/Sources/XcodeProj/Extensions/AEXML+XcodeFormat.swift
+++ b/Sources/XcodeProj/Extensions/AEXML+XcodeFormat.swift
@@ -124,7 +124,7 @@ extension AEXMLElement {
func print(key: String, value: String) {
xml += "\n"
xml += indent(withDepth: parentsCount)
- xml += "\(key) = \"\(value.xmlEscaped)\""
+ xml += "\(key) = \"\(value.xcodeFormatEscaped)\""
}
if !attributes.isEmpty {
@@ -156,7 +156,7 @@ extension AEXMLElement {
// insert string value and close element
xml += ">\n"
xml += indent(withDepth: parentsCount - 1)
- xml += ">\n\(string.xmlEscaped)"
+ xml += ">\n\(string.xcodeFormatEscaped)"
}
}
@@ -190,3 +190,21 @@ extension AEXMLElement {
return indent
}
}
+
+extension String {
+ /// `xmlEscaped`, with U+FFFD in place of each character XML 1.0 cannot hold at all, even as a reference:
+ /// a C0 control other than tab, line feed and carriage return, U+FFFE or U+FFFF. A file holding one is not
+ /// well-formed, so neither XcodeProj nor any other XML reader can load it.
+ fileprivate var xcodeFormatEscaped: String {
+ var scalars = String.UnicodeScalarView()
+ for scalar in xmlEscaped.unicodeScalars {
+ switch scalar {
+ case "\u{0}"..."\u{8}", "\u{B}", "\u{C}", "\u{E}"..."\u{1F}", "\u{FFFE}", "\u{FFFF}":
+ scalars.append("\u{FFFD}")
+ default:
+ scalars.append(scalar)
+ }
+ }
+ return String(scalars)
+ }
+}
This loses the character, but the file stays loadable. The alternative is to throw from write and
dataRepresentation, which would mean xmlXcodeFormat throwing too. Output without these characters is unchanged.
I have an XCTest file, Tests/XcodeProjTests/Extensions/XcodeFormatForbiddenCharacterTests.swift, with four tests:
the scheme and the workspace above, all 30 code points in an attribute value, and a control (&, <, >, quotes,
tab and newline, which round-trip today). On main the three tests fail (32 assertions) and the control passes.
With the patch all four pass, and so does the full swift test: 387 XCTest and 95 Swift Testing tests. Happy to
open a PR with both.
AEXML's own xml output has the same gap in String.xmlEscaped (4.7.3, unchanged on its master). I have not
reported it there.
Environment. XcodeProj main at 948280a (AEXML 4.7.3). Xcode 27.2 (Swift 6.4.2), macOS 27.2, arm64.
Found while checking whether XML written by Swift packages parses.
- 主要语言
- Swift
- 星标
- 2.2k
- 派生
- 356
- 平均合并
- 5 小时 54 分钟
- 30 天内合并 PR
- 8
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 没有贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
tuist/XcodeProj 的其他 Issue
-
难度 1/5 1 小时以内 新手友好度 85/100
-
难度 3/5 1-2 天 新手友好度 52/100
-
Removing a target referenced by PBXFileSystemSynchronizedBuildFileExceptionSet crashes serialization未关闭
难度 4/5 3-5 天 新手友好度 55/100
-
PBXProjEncoder crashes when sorting projectReferences if ProjectRef file element has nil name可能已有人在做 @Ant-tree 于 263 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 35/100
-
难度 3/5 1-2 天 新手友好度 45/100
相似的 Issue
-
enhancement
难度 2/5 1-3 小时 新手友好度 72/100
RodnaPamet/agrent-ios#499 ·
维护者通常 1 天内回复
-
easy first-contributor first-contributors good first issue help wanted swift up-for-grabs
难度 1/5 1 小时以内 新手友好度 86/100
fork-commit-merge/fork-commit-merge#8473 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 62/100
-
area: agents
难度 2/5 1-3 小时 新手友好度 75/100
manaflow-ai/cmux#29762 · 1 条评论 ·
维护者通常 1 天内回复