Skip to content

Bug: image src not rewritten in Markdown when page title contains an apostrophe #15

Description

@elliothallais

Problem

When converting a Confluence page whose title contains an apostrophe (e.g. My team's page), images are downloaded to the local assets folder but the src attribute in the generated Markdown still points to the remote Confluence URL instead of the local file.

Root cause

In confluence.rs, download_images_and_rewrite_html uses two separate regexes:

  • IMG_RE — correctly uses alternation (?:"([^"]*)"|'([^']*)') to capture the src value without ambiguity.
  • REPLACE_RE — uses (["'])(.*?)(["']) with a lazy quantifier and a character class ["'] for both the opening and closing delimiter.

When the image URL contains an unencoded apostrophe (which happens whenever the Confluence page title has one, since Confluence embeds the raw title in attachment URLs), the lazy .*? stops at the first ' it finds inside the URL, mistaking it for the closing quote. Because quote_open (") ≠ quote_close ('), the guard condition fires and the replacement is skipped — even though the file was already downloaded successfully.

// URL produced by Confluence for a page titled "My team's page":
src="https://confluence.example.com/download/attachments/.../My%20team's%20page/image.jpg?api=v2"
//                                                                      ^
//                              REPLACE_RE stops here, thinking ' is the closing quote

IMG_RE does not have this problem because its alternation branches use [^"]* / [^']* (negated character classes) which never match the wrong delimiter.

Suggested fix

Replace REPLACE_RE with the same alternation pattern used by IMG_RE:

// Before (buggy when URL contains an apostrophe):
static REPLACE_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r#"(?is)(<img\b[^>]*\bsrc=)(["'])(.*?)(["'])"#).unwrap());

// After:
static REPLACE_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r#"(?is)(<img\b[^>]*\bsrc=)(?:"([^"]*)"|'([^']*)')"#).unwrap());

The closure would then mirror the IMG_RE capture-group convention (group 2 = double-quoted src, group 3 = single-quoted src).

Steps to reproduce

  1. Create (or use) a Confluence page whose title contains an apostrophe.
  2. Add an image attachment to that page.
  3. Run confluence2md against it.
  4. Observe that the image file is present in the _assets/ folder, but the Markdown still references the remote URL.

Expected behaviour

The src in the generated Markdown points to the locally downloaded asset (e.g. My_team's_page_assets/image.jpg).

Actual behaviour

The src still points to the original remote Confluence URL.

Additional notes

  • The rewrite_image.html dump (via --dump-state-path) is identical to export.html, confirming the substitution never takes place.
  • The downloaded file is present and valid in the assets folder, so the download step itself works correctly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions