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
16 changes: 12 additions & 4 deletions Sources/Ink/API/MarkdownParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,21 @@
///
/// To customize how this parser performs its work, attach
/// a `Modifier` using the `addModifier` method.
///
/// To prevent HTML tags from passing through to HTML
/// output for untrusted input, set safeMode:true
/// The angle brackets will be represented as HTML
/// character entities to prevent interpretation.
///
public struct MarkdownParser {
private var modifiers: ModifierCollection

private var safeMode : Bool

/// Initialize an instance, optionally passing an array
/// of modifiers used to customize the parsing process.
public init(modifiers: [Modifier] = []) {
public init(modifiers: [Modifier] = [], safeMode: Bool = false) {
self.modifiers = ModifierCollection(modifiers: modifiers)
self.safeMode = safeMode
}

/// Add a modifier to this parser, which can be used to
Expand All @@ -40,7 +48,7 @@ public struct MarkdownParser {
/// both the HTML representation of the given string, and also any
/// metadata values found within it.
public func parse(_ markdown: String) -> Markdown {
var reader = Reader(string: markdown)
var reader = Reader(string: markdown, safeMode: safeMode)
var fragments = [ParsedFragment]()
var urlsByName = [String : URL]()
var titleHeading: Heading?
Expand Down Expand Up @@ -132,7 +140,7 @@ private extension MarkdownParser {
switch character {
case "#": return Heading.self
case "!": return Image.self
case "<": return HTML.self
case "<": return safeMode ? SafedHTML.self : HTML.self
case ">": return Blockquote.self
case "`": return CodeBlock.self
case "-" where character == nextCharacter,
Expand Down
1 change: 1 addition & 0 deletions Sources/Ink/API/Modifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public extension Modifier {
case headings
case horizontalLines
case html
case safedHtml
case images
case inlineCode
case links
Expand Down
2 changes: 1 addition & 1 deletion Sources/Ink/Internal/FormattedText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ private extension FormattedText {
case "`": return InlineCode.self
case "[": return Link.self
case "!": return Image.self
case "<": return HTML.self
case "<": return reader.safeMode ? SafedHTML.self : HTML.self
default: return nil
}
}
Expand Down
18 changes: 18 additions & 0 deletions Sources/Ink/Internal/HTML.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,24 @@ internal struct HTML: Fragment {
}
}

internal struct SafedHTML : Fragment {
private var element: Reader.HTMLElement

static func read(using reader: inout Reader) throws -> SafedHTML {
return try SafedHTML( element: reader.readHTMLElement())
}

var modifierTarget: Modifier.Target { .safedHtml }

func html(usingURLs urls: NamedURLCollection, modifiers: ModifierCollection) -> String {
return "&lt;\(element.name)\(element.isSelfClosing ? "/":"")&gt;"
}

func plainText() -> String {
return "<\(element.name)\(element.isSelfClosing ? "/":"")>"
}
}

private extension Reader {
typealias HTMLElement = (name: Substring, isSelfClosing: Bool)

Expand Down
6 changes: 4 additions & 2 deletions Sources/Ink/Internal/Reader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
internal struct Reader {
private let string: String
private(set) var currentIndex: String.Index

init(string: String) {
let safeMode : Bool

init(string: String, safeMode: Bool = false) {
self.string = string
self.currentIndex = string.startIndex
self.safeMode = safeMode
}
}

Expand Down
15 changes: 14 additions & 1 deletion Tests/InkTests/HTMLTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,17 @@ final class HTMLTests: XCTestCase {

XCTAssertEqual(html, "<p>Hello &amp; welcome to &lt;Ink&gt;</p>")
}

func testHTMLSafeMode() {
let html = MarkdownParser(safeMode:true).html(from: "Hello<h2>World</h2>.<br/>Be safe.")
XCTAssertEqual(html, "<p>Hello&lt;h2&gt;World&lt;/h2&gt;.&lt;br/&gt;Be safe.</p>")
}

func testHTMLSafeModeFirst() {
let html = MarkdownParser(safeMode:true).html(from: "<h2>Hello</h2><br/>World.")
XCTAssertEqual(html, "&lt;h2&gt;<p>Hello&lt;/h2&gt;&lt;br/&gt;World.</p>")
}

}

extension HTMLTests {
Expand All @@ -127,7 +138,9 @@ extension HTMLTests {
("testInlineSelfClosingHTMLElement", testInlineSelfClosingHTMLElement),
("testTopLevelHTMLLineBreak", testTopLevelHTMLLineBreak),
("testHTMLComment", testHTMLComment),
("testHTMLEntities", testHTMLEntities)
("testHTMLEntities", testHTMLEntities),
("testHTMLSafeMode", testHTMLSafeMode),
("testHTMLSafeModeFirst", testHTMLSafeModeFirst)
]
}
}