Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an HTML 'safeMode' flag #61

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
Add a 'safeMode' flag to MarkdownParser(). This will prevent prevent …
…HTML on the input from getting to the HTML output.

This is useful if you publish user generated content.
Notice there were two places we had to recognize safeMode when reading a "<" and choose to use SafedHTML.
  • Loading branch information
jimstudt committed Nov 19, 2020
commit 61ad64963238b524c6fa3d3c14db320eb427be53
16 changes: 12 additions & 4 deletions Sources/Ink/API/MarkdownParser.swift
Original file line number Diff line number Diff line change
@@ -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
@@ -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?
@@ -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,
1 change: 1 addition & 0 deletions Sources/Ink/API/Modifier.swift
Original file line number Diff line number Diff line change
@@ -47,6 +47,7 @@ public extension Modifier {
case headings
case horizontalLines
case html
case safedHtml
case images
case inlineCode
case links
2 changes: 1 addition & 1 deletion Sources/Ink/Internal/FormattedText.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
18 changes: 18 additions & 0 deletions Sources/Ink/Internal/HTML.swift
Original file line number Diff line number Diff line change
@@ -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)

6 changes: 4 additions & 2 deletions Sources/Ink/Internal/Reader.swift
Original file line number Diff line number Diff line change
@@ -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
}
}

15 changes: 14 additions & 1 deletion Tests/InkTests/HTMLTests.swift
Original file line number Diff line number Diff line change
@@ -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 {
@@ -127,7 +138,9 @@ extension HTMLTests {
("testInlineSelfClosingHTMLElement", testInlineSelfClosingHTMLElement),
("testTopLevelHTMLLineBreak", testTopLevelHTMLLineBreak),
("testHTMLComment", testHTMLComment),
("testHTMLEntities", testHTMLEntities)
("testHTMLEntities", testHTMLEntities),
("testHTMLSafeMode", testHTMLSafeMode),
("testHTMLSafeModeFirst", testHTMLSafeModeFirst)
]
}
}