Skip to content

Commit 5aea80c

Browse files
committed
Refactor the code a bit
1 parent 22144f5 commit 5aea80c

7 files changed

Lines changed: 132 additions & 153 deletions

File tree

Sources/HTMLKit/Abstraction/Elements/BodyElements.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21138,7 +21138,7 @@ public struct Script: ContentNode, HeadElement, BodyElement, FormElement, Figure
2113821138
///
2113921139
/// - Parameter content: The script's content.
2114021140
public init(@ContentBuilder<String> content: () -> [String]) {
21141-
self.content = [JsString(content())]
21141+
self.content = [TaintedString(content().joined(), as: .js)]
2114221142
}
2114321143

2114421144
internal init(attributes: OrderedDictionary<String, Any>?, content: [Content]) {

Sources/HTMLKit/Abstraction/Elements/HeadElements.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ public struct Style: ContentNode, HeadElement {
715715
///
716716
/// - Parameter content: The style's content.
717717
public init(@ContentBuilder<String> content: () -> [String]) {
718-
self.content = [CssString(content())]
718+
self.content = [TaintedString(content().joined(), as: .css)]
719719
}
720720

721721
internal init(attributes: OrderedDictionary<String, Any>?, content: [Content]) {

Sources/HTMLKit/Framework/Rendering/Encoding/Encoder.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import Foundation
22

3-
/// A type responsible for encoding characters to their safe equivalents
3+
/// A type that represents an encoder
4+
///
5+
/// The encoder is responsible for encoding characters to their safe equivalents.
46
internal struct Encoder {
57

68
/// A enumeration of potential encoding mechanism
@@ -13,7 +15,7 @@ internal struct Encoder {
1315
case attribute
1416

1517
/// Mechanism to use on the element body
16-
case body
18+
case element
1719
}
1820

1921
/// Use a html encoding mechanism
@@ -35,7 +37,7 @@ internal struct Encoder {
3537
case .attribute:
3638
return ["&", "'", "\""]
3739

38-
case .body:
40+
case .element:
3941
return ["<", ">"]
4042
}
4143

Sources/HTMLKit/Framework/Rendering/Encoding/HtmlString.swift

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,3 @@ public struct HtmlString: Content {
1818
self.value = values.joined()
1919
}
2020
}
21-
22-
internal struct JsString: Content {
23-
24-
internal let value: String
25-
26-
internal init(_ value: String) {
27-
self.value = value
28-
}
29-
30-
internal init(_ values: [String]) {
31-
self.value = values.joined()
32-
}
33-
}
34-
35-
internal struct CssString: Content {
36-
37-
internal let value: String
38-
39-
internal init(_ value: String) {
40-
self.value = value
41-
}
42-
43-
internal init(_ values: [String]) {
44-
self.value = values.joined()
45-
}
46-
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/// A string that represents a untrusted string
2+
///
3+
/// The tainted string comes from a untrusted source and must be escaped by the renderer,
4+
/// before it is getting consumed.
5+
internal struct TaintedString: Content {
6+
7+
internal enum Context {
8+
9+
/// The context within a html context
10+
internal enum Context {
11+
12+
/// Consider an attribute value
13+
case attribute
14+
15+
/// Consider an element body
16+
case element
17+
}
18+
19+
/// Consider an html context
20+
case html(Context)
21+
22+
/// Consider a css context
23+
case css
24+
25+
/// Consider a js context
26+
case js
27+
}
28+
29+
/// The string value marked as tainted
30+
internal let value: String
31+
32+
/// The context of the source of the string
33+
internal let context: Context
34+
35+
/// Create a tainted string
36+
///
37+
/// - Parameters:
38+
/// - value: The string value
39+
/// - context: The context the string is coming from
40+
internal init(_ value: String, as context: Context) {
41+
42+
self.value = value
43+
self.context = context
44+
}
45+
}

Sources/HTMLKit/Framework/Rendering/Renderer.swift

Lines changed: 30 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -43,29 +43,6 @@ public struct Renderer {
4343
}
4444
}
4545

46-
/// A enumeration of potential escape contexts
47-
private enum EscapeContext {
48-
49-
/// A enumeration of potential html contexts
50-
internal enum Context {
51-
52-
/// Consider an attribute value
53-
case attribute
54-
55-
/// Consider an element body
56-
case element
57-
}
58-
59-
/// Consider an html context
60-
case html(Context)
61-
62-
/// Consider a css context
63-
case css
64-
65-
/// Consider a js context
66-
case js
67-
}
68-
6946
/// The context environment used during rendering
7047
private var environment: Environment
7148

@@ -159,7 +136,7 @@ public struct Renderer {
159136
try render(modifier: modifier, on: &result)
160137

161138
case let value as EnvironmentValue:
162-
result += escape(try render(envvalue: value), as: .html(.element))
139+
result += escape(tainted: .init(try render(envvalue: value), as: .html(.element)))
163140

164141
case let statement as Statement:
165142
try render(statement: statement, on: &result)
@@ -173,7 +150,7 @@ public struct Renderer {
173150
case let string as MarkdownString:
174151

175152
if !features.contains(.markdown) {
176-
result += escape(string.raw, as: .html(.element))
153+
result += escape(tainted: .init(string.raw, as: .html(.element)))
177154

178155
} else {
179156
result += try render(markstring: string)
@@ -185,11 +162,8 @@ public struct Renderer {
185162
case let string as HtmlString:
186163
result += string.value
187164

188-
case let string as JsString:
189-
result += escape(string.value, as: .js)
190-
191-
case let string as CssString:
192-
result += escape(string.value, as: .css)
165+
case let string as TaintedString:
166+
result += escape(tainted: string)
193167

194168
case let doubleValue as Double:
195169
result += String(doubleValue)
@@ -201,7 +175,7 @@ public struct Renderer {
201175
result += String(intValue)
202176

203177
case let stringValue as String:
204-
result += escape(stringValue, as: .html(.element))
178+
result += escape(tainted: .init(stringValue, as: .html(.element)))
205179

206180
case let date as Date:
207181

@@ -279,7 +253,7 @@ public struct Renderer {
279253

280254
result += "<!--"
281255

282-
result += escape(element.content, as: .html(.element))
256+
result += escape(tainted: .init(element.content, as: .html(.element)))
283257

284258
result += "-->"
285259
}
@@ -459,10 +433,10 @@ public struct Renderer {
459433
result += try render(localized: string)
460434

461435
case let value as EnvironmentValue:
462-
result += escape(try render(envvalue: value), as: .html(.attribute))
436+
result += escape(tainted: .init(try render(envvalue: value), as: .html(.attribute)))
463437

464438
default:
465-
result += escape("\(attribute.value)", as: .html(.attribute))
439+
result += escape(tainted: .init("\(attribute.value)", as: .html(.attribute)))
466440
}
467441

468442
result += "\""
@@ -475,7 +449,7 @@ public struct Renderer {
475449
///
476450
/// - Returns: The string representation
477451
private func render(markstring: MarkdownString) throws -> String {
478-
return markdown.render(string: escape(markstring.raw, as: .html(.element)))
452+
return markdown.render(string: escape(tainted: .init(markstring.raw, as: .html(.element))))
479453
}
480454

481455
/// Renders a environment string
@@ -540,8 +514,11 @@ public struct Renderer {
540514
case let string as HtmlString:
541515
result += string.value
542516

517+
case let string as TaintedString:
518+
result += escape(tainted: string)
519+
543520
case let string as String:
544-
result += escape(string, as: .html(.element))
521+
result += escape(tainted: .init(string, as: .html(.element)))
545522

546523
case is EnvironmentValue:
547524

@@ -633,40 +610,44 @@ public struct Renderer {
633610
try render(loop: envstring.values, with: value, on: &result)
634611
}
635612

636-
/// Escapes a string according to the given context.
637-
///
638-
/// - Parameter value: The string value to escape
639-
/// - Parameter context: The context that defines the escaping
613+
/// Escapes a tainted string
614+
///
615+
/// It takes precautions such as character encoding and input sanitization to ensure a safe output. Though the escaping alone
616+
/// does not guarantee complete safety. It only helps mitigate the risk.
617+
///
618+
/// - Parameter string: The string value to escape.
640619
///
641-
/// - Returns: The escaped string
642-
private func escape(_ value: String, as context: EscapeContext) -> String {
643-
620+
/// - Returns: The untainted string
621+
private func escape(tainted string: TaintedString) -> String {
622+
644623
if !features.contains(.escaping) {
645-
return value
624+
625+
// Bail early, if the escaping is not desired
626+
return string.value
646627
}
647628

648-
switch context {
629+
switch string.context {
649630
case .html(let context):
650631

651632
switch context {
652633
case .attribute:
653-
return encoder.encode(value, as: .html(.attribute))
634+
return encoder.encode(string.value, as: .html(.attribute))
654635

655636
case .element:
656-
return encoder.encode(value, as: .html(.body))
637+
return encoder.encode(string.value, as: .html(.element))
657638
}
658639

659640
case .css:
660641

661642
// To prevent breaking out of context
662-
let sanitized = sanitizer.strip("style", from: value)
643+
let sanitized = sanitizer.strip("style", from: string.value)
663644

664645
return encoder.encode(sanitized, as: .css)
665646

666647
case .js:
667648

668649
// To prevent breaking out of context
669-
let sanitized = sanitizer.strip("script", from: value)
650+
let sanitized = sanitizer.strip("script", from: string.value)
670651

671652
return encoder.encode(sanitized, as: .js)
672653

0 commit comments

Comments
 (0)