diff --git a/Sources/HTMLKit/Framework/Rendering/Encoder.swift b/Sources/HTMLKit/Framework/Rendering/Encoding/Encoder.swift similarity index 100% rename from Sources/HTMLKit/Framework/Rendering/Encoder.swift rename to Sources/HTMLKit/Framework/Rendering/Encoding/Encoder.swift diff --git a/Sources/HTMLKit/Framework/Rendering/Encoding/HtmlString.swift b/Sources/HTMLKit/Framework/Rendering/Encoding/HtmlString.swift new file mode 100644 index 00000000..9038d3d5 --- /dev/null +++ b/Sources/HTMLKit/Framework/Rendering/Encoding/HtmlString.swift @@ -0,0 +1,15 @@ +/// A type that remains unescaped during rendering. +/// +/// > Warning: Use with caution, as this may lead to security vulnerabilities +/// > if the argument is not properly validated or not trusted. +@_documentation(visibility: internal) +public struct HtmlString: Content { + + /// The wrapped string + internal let value: String + + /// Initializes an html string + public init(_ value: String) { + self.value = value + } +} diff --git a/Sources/HTMLKit/Framework/Rendering/Renderer.swift b/Sources/HTMLKit/Framework/Rendering/Renderer.swift index 1a367d92..cb76cf89 100644 --- a/Sources/HTMLKit/Framework/Rendering/Renderer.swift +++ b/Sources/HTMLKit/Framework/Rendering/Renderer.swift @@ -155,6 +155,9 @@ public struct Renderer { case let string as EnvironmentString: try render(envstring: string, on: &result) + case let string as HtmlString: + result += string.value + case let doubleValue as Double: result += String(doubleValue) @@ -501,6 +504,9 @@ public struct Renderer { case let string as EnvironmentString: try render(envstring: string, on: &result) + case let string as HtmlString: + result += string.value + case let string as String: result += escape(content: string) diff --git a/Tests/HTMLKitTests/SecurityTests.swift b/Tests/HTMLKitTests/SecurityTests.swift index 140e0817..2f86617b 100644 --- a/Tests/HTMLKitTests/SecurityTests.swift +++ b/Tests/HTMLKitTests/SecurityTests.swift @@ -111,5 +111,25 @@ final class SecurityTests: XCTestCase { """ ) } + + /// Tests the renderers behaviour when handling a desired unescaped string. + /// + /// The renderer is expected to emit the string as-is. + func testIgnoringHtmlString() throws { + + let html = "" + + let view = TestView { + Paragraph { + HtmlString(html) + } + } + + XCTAssertEqual(try renderer.render(view: view), + """ +

+ """ + ) + } }