Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/Plot/API/Attribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,6 @@ extension Attribute: AnyAttribute {
return ignoreIfValueIsEmpty ? "" : name
}

return "\(name)=\"\(value)\""
return "\(name)=\"\(value.escapedForAttribute())\""
}
}
5 changes: 5 additions & 0 deletions Sources/Plot/Internal/String+Escaping.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
*/

internal extension String {
func escapedForAttribute() -> String {
escaped().replacingOccurrences(of: "\"", with: """)
.replacingOccurrences(of: "'", with: "'")
}

func escaped() -> String {
var pendingAmpersandString: String?

Expand Down
2 changes: 1 addition & 1 deletion Tests/PlotTests/HTMLTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,7 @@ final class HTMLTests: XCTestCase {
)
)
assertEqualHTMLContent(html, """
<body><div onclick="javascript:alert('Hello World')"></div></body>
<body><div onclick="javascript:alert(&#39;Hello World&#39;)"></div></body>
""")
}
}
36 changes: 36 additions & 0 deletions Tests/PlotTests/NodeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,42 @@ final class NodeTests: XCTestCase {
XCTAssertEqual(node.render(), #"key="value""#)
}

func testEscapingDoubleQuotesInAttributeValue() {
let node = Node<Any>.attribute(named: "key", value: #"a"b"#)
XCTAssertEqual(node.render(), #"key="a&quot;b""#)
}

func testEscapingSingleQuotesInAttributeValue() {
let node = Node<Any>.attribute(named: "key", value: "a'b")
XCTAssertEqual(node.render(), #"key="a&#39;b""#)
}

func testEscapingAngleBracketsInAttributeValue() {
let node = Node<Any>.attribute(named: "key", value: "<script>alert(1)</script>")
XCTAssertEqual(node.render(), #"key="&lt;script&gt;alert(1)&lt;/script&gt;""#)
}

func testEscapingAmpersandInAttributeValue() {
let node = Node<Any>.attribute(named: "key", value: "a&b")
XCTAssertEqual(node.render(), #"key="a&amp;b""#)
}

func testNotDoubleEscapingAttributeValue() {
let node = Node<Any>.attribute(named: "key", value: "&amp;")
XCTAssertEqual(node.render(), #"key="&amp;""#)
}

func testEscapingXSSInAttributeValue() {
let node = Node<Any>.attribute(
named: "value",
value: #""><img src=x onerror=alert(1)><input value=""#
)
XCTAssertEqual(
node.render(),
#"value="&quot;&gt;&lt;img src=x onerror=alert(1)&gt;&lt;input value=&quot;""#
)
}

func testCustomElementWithCustomAttribute() {
let node = Node<Any>.element(named: "custom", attributes: [
Attribute(name: "key", value: "value")
Expand Down