A scheme or workspace with a control character in a value is written as malformed XML, and XcodeProj can't read it back
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 85/100
Línea de trabajo
Empieza con Sources/XcodeProj/Extensions/AEXML+XcodeFormat.swift y el escape de atributos y valores de cadena en xmlXcodeFormat. El issue describe pruebas en Tests/XcodeProjTests/Extensions/XcodeFormatForbiddenCharacterTests.swift; úsalas para comprobar los controles prohibidos, los controles válidos y los round-trips de scheme/workspace. El trabajo estará terminado cuando el XML escrito siga siendo legible y toda la suite de pruebas swift test pase.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
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.
- Lenguaje dominante
- Swift
- Estrellas
- 2.2k
- Forks
- 356
- Merge medio
- 5 h 54 min
- PR fusionados (30 d)
- 8
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Sin guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de tuist/XcodeProj
-
New Xcode 26 formatAbierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
-
Dificultad 3/5 1-2 días Aptitud para principiantes 52/100
-
Removing a target referenced by PBXFileSystemSynchronizedBuildFileExceptionSet crashes serializationAbierto
Dificultad 4/5 3-5 días Aptitud para principiantes 55/100
-
PBXProjEncoder crashes when sorting projectReferences if ProjectRef file element has nil namePosiblemente ocupada @Ant-tree la tomó hace 262 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 35/100
-
Dificultad 3/5 1-2 días Aptitud para principiantes 45/100
Todos los issues de tuist/XcodeProj
Issues similares
-
🎉 Add BossanovaAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
jaywcjlove/awesome-mac#4096 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
Los mantenedores suelen responder en 1 día
-
[BUG] FluidMeet: Google Meet browser calls not detected after Meet moved rooms to /meet/<code>Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
altic-dev/FluidVoice#1264 ·
Los mantenedores suelen responder en 1 día
-
needs-triage S3: minor
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
manaflow-ai/cmux#28422 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
`Heap._checkInvariants()` is `internal` under `COLLECTIONS_INTERNAL_CHECKS` but `public` otherwiseAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
apple/swift-collections#813 ·
Los mantenedores suelen responder en 1 día