mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
fix(markdown): stop escaped link destinations from swallowing later content (#1630)
This commit is contained in:
@@ -412,10 +412,7 @@ fn extract_escaped_url(text: &str, start: usize) -> Option<UrlInfo<'_>> {
|
||||
if byte_at(text, pos) == b'>' {
|
||||
let url = &text[start..pos];
|
||||
pos += 1;
|
||||
while pos < text.len() && byte_at(text, pos) != b')' {
|
||||
pos += advance_one(text, pos);
|
||||
}
|
||||
if pos >= text.len() {
|
||||
if pos >= text.len() || byte_at(text, pos) != b')' {
|
||||
return None;
|
||||
}
|
||||
return Some(UrlInfo {
|
||||
@@ -1410,7 +1407,34 @@ pub fn max_inline_scan() -> usize {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::has_valid_code_fence_language;
|
||||
use super::{extract_escaped_url, has_valid_code_fence_language};
|
||||
|
||||
#[test]
|
||||
fn escaped_url_accepts_paren_immediately_after_destination() {
|
||||
let info = extract_escaped_url("https://example.com>) trailing", 0).expect("valid");
|
||||
assert_eq!(info.url, "https://example.com");
|
||||
assert!(info.escaped);
|
||||
assert_eq!(info.advance_by, "https://example.com>)".len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escaped_url_rejects_content_between_destination_and_paren() {
|
||||
assert!(extract_escaped_url("https://example.com> and more)", 0).is_none());
|
||||
assert!(extract_escaped_url("https://example.com> )", 0).is_none());
|
||||
assert!(extract_escaped_url("https://example.com>text)", 0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escaped_url_rejects_missing_paren() {
|
||||
assert!(extract_escaped_url("https://example.com>", 0).is_none());
|
||||
assert!(extract_escaped_url("https://example.com", 0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escaped_url_keeps_paren_inside_destination() {
|
||||
let info = extract_escaped_url("https://example.com/a.yaml)>)", 0).expect("valid");
|
||||
assert_eq!(info.url, "https://example.com/a.yaml)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_leading_whitespace() {
|
||||
|
||||
@@ -259,6 +259,43 @@ fn native_parser_rejects_apostrophe_in_masked_link_authority() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_parser_keeps_content_after_unterminated_escaped_destination() {
|
||||
let flags = ParserFlags::ALLOW_MASKED_LINKS | ParserFlags::ALLOW_AUTOLINKS;
|
||||
assert_eq!(
|
||||
parse(
|
||||
"a [x](<https://example.com/y)> b [e](<https://example.com/f>)",
|
||||
flags,
|
||||
""
|
||||
),
|
||||
json!({"nodes":[
|
||||
{"type":"Text","content":"a [x]("},
|
||||
{"type":"Link","url":"https://example.com/y)","escaped":true,"rawUrl":"https://example.com/y)","source":"<https://example.com/y)>"},
|
||||
{"type":"Text","content":" b "},
|
||||
{"type":"Link","text":{"type":"Text","content":"e"},"url":"https://example.com/f","escaped":true,"rawUrl":"https://example.com/f","source":"[e](<https://example.com/f>)"}
|
||||
]})
|
||||
);
|
||||
assert_eq!(
|
||||
parse("[bad](<https://example.com/a> )", flags, ""),
|
||||
json!({"nodes":[
|
||||
{"type":"Text","content":"[bad]("},
|
||||
{"type":"Link","url":"https://example.com/a","escaped":true,"rawUrl":"https://example.com/a","source":"<https://example.com/a>"},
|
||||
{"type":"Text","content":" )"}
|
||||
]})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_parser_still_accepts_well_formed_escaped_destinations() {
|
||||
let flags = ParserFlags::ALLOW_MASKED_LINKS | ParserFlags::ALLOW_AUTOLINKS;
|
||||
assert_eq!(
|
||||
parse("[ok](<https://example.com/a>)", flags, ""),
|
||||
json!({"nodes":[
|
||||
{"type":"Link","text":{"type":"Text","content":"ok"},"url":"https://example.com/a","escaped":true,"rawUrl":"https://example.com/a","source":"[ok](<https://example.com/a>)"}
|
||||
]})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_parser_rejects_masked_links_without_visible_text() {
|
||||
for input in [
|
||||
|
||||
Reference in New Issue
Block a user