Commit 77bb69d3d0
Unsigned
Layout: unified · split
Sources/OrgSwift/AST/OrgInlineParser.swift +8 −2
| @@ -142,13 +142,19 @@ extension OrgParser { | |||
| 142 | 142 | ||
| 143 | private static func scanEmphasis(_ chars: [Character], from start: Int, marker: Character) | 143 | private static func scanEmphasis(_ chars: [Character], from start: Int, marker: Character) |
| 144 | -> (body: String, next: Int)? { | 144 | -> (body: String, next: Int)? { |
| 145 | // org's border rule: the body may not begin with whitespace, so an opener followed by | ||
| 146 | // a space is not an opener at all. | ||
| 147 | guard start + 1 < chars.count, !chars[start + 1].isWhitespace else { return nil } | ||
| 148 | |||
| 145 | var j = start + 1 | 149 | var j = start + 1 |
| 146 | var body = "" | 150 | var body = "" |
| 147 | while j < chars.count { | 151 | while j < chars.count { |
| 148 | if chars[j] == marker { | 152 | // The body may not end with whitespace either — which is what makes the closer of |
| 153 | // `=ssh.mode = "system"=` the last `=` and not the middle one. | ||
| 154 | if chars[j] == marker, let last = body.last, !last.isWhitespace { | ||
| 149 | // The closer must end the run or be followed by space/punctuation. | 155 | // The closer must end the run or be followed by space/punctuation. |
| 150 | let after = j + 1 < chars.count ? chars[j + 1] : " " | 156 | let after = j + 1 < chars.count ? chars[j + 1] : " " |
| 151 | if !body.isEmpty, after.isWhitespace || ".,;:!?)]}'\"".contains(after) || j + 1 == chars.count { | 157 | if after.isWhitespace || ".,;:!?)]}'\"".contains(after) || j + 1 == chars.count { |
| 152 | return (body, j + 1) | 158 | return (body, j + 1) |
| 153 | } | 159 | } |
| 154 | } | 160 | } |
Tests/OrgSwiftTests/OrgTreeTests.swift +21
| @@ -323,6 +323,27 @@ struct OrgRenderOptionsTests { | |||
| 323 | #expect(without.contains("<p>Body.</p>")) | 323 | #expect(without.contains("<p>Body.</p>")) |
| 324 | } | 324 | } |
| 325 | 325 | ||
| 326 | /// org's border rule: an emphasis body may not begin or end with whitespace. Without the | ||
| 327 | /// trailing half, `=ssh.mode = "system"=` closed at the middle `=` and rendered as two | ||
| 328 | /// verbatim spans with the quoted value loose between them. | ||
| 329 | @Test | ||
| 330 | func emphasisBodyCannotBeginOrEndWithWhitespace() { | ||
| 331 | let objects = OrgParser.parseInline(#"=ssh.mode = "system"= to run"#) | ||
| 332 | let verbatims = objects.compactMap { object -> String? in | ||
| 333 | if case .verbatim(let text) = object { return text } else { return nil } | ||
| 334 | } | ||
| 335 | #expect(verbatims == [#"ssh.mode = "system""#]) | ||
| 336 | |||
| 337 | // A leading space means the marker never opens. | ||
| 338 | #expect(OrgParser.parseInline("= not verbatim=") == [.text("= not verbatim=")]) | ||
| 339 | |||
| 340 | // The ordinary cases still work. | ||
| 341 | #expect(OrgParser.parseInline("=plain=") == [.verbatim("plain")]) | ||
| 342 | let bold = OrgParser.parseInline("*bold* and *two words*") | ||
| 343 | #expect(bold.contains(.bold([.text("bold")]))) | ||
| 344 | #expect(bold.contains(.bold([.text("two words")]))) | ||
| 345 | } | ||
| 346 | |||
| 326 | @Test | 347 | @Test |
| 327 | func rejectsUnsafeSchemes() { | 348 | func rejectsUnsafeSchemes() { |
| 328 | let html = OrgRenderer.renderToHTML("[[javascript:alert(1)][click]]") | 349 | let html = OrgRenderer.renderToHTML("[[javascript:alert(1)][click]]") |