-
Notifications
You must be signed in to change notification settings - Fork 12
[Detail Bug] CSS relative asset URLs are not rewritten to absolute URLs when rewriteUrls is enabled #236
Description
Detail Bug Report
Summary
- Context: The
replaceCssUrlsfunction is responsible for converting relative CSS URLs to absolute URLs whenrewriteUrls: trueis enabled, ensuring that images and assets referenced in stylesheets work when the HTML is used in different contexts. - Bug: The function only converts root-relative URLs (starting with
/) to absolute URLs, completely ignoring other types of relative URLs likeimages/bg.jpg,./images/bg.jpg, and../images/bg.jpg. - Actual vs. expected: When
rewriteUrls: trueis set, the README states that "CSS/HTML relatives URLs present in the HTML markup" will be rewritten to absolutes, but non-root-relative CSS URLs remain unchanged while HTML relative URLs are correctly converted. - Impact: CSS background images and other assets referenced with relative paths will break when the HTML is saved to disk, displayed in an iframe, or used in any context different from the original source URL.
Code with Bug
const replaceCssUrls = (url, stylesheet) => {
const cssUrls = Array.from(execall(cssUrl(), stylesheet)).reduce(
(acc, match) => {
match.subMatches.forEach(match => acc.add(match))
return acc
},
new Set()
)
cssUrls.forEach(cssUrl => {
if (cssUrl.startsWith('/')) { // <-- BUG 🔴 Only handles root-relative URLs, ignores other relative paths
try {
const absoluteUrl = new URL(cssUrl, url).toString()
stylesheet = stylesheet.replaceAll(
`url(${cssUrl})`,
`url(${absoluteUrl})`
)
} catch (_) {}
}
})
return stylesheet
}Explanation
new URL(cssUrl, url) correctly resolves all relative URL forms (e.g., images/x.png, ./images/x.png, ../images/x.png, and /images/x.png) against a base URL. However, replaceCssUrls gates rewriting behind cssUrl.startsWith('/'), so only root-relative paths are rewritten; other relative paths are left unchanged, causing broken asset references when the HTML is used outside its original URL context.
Codebase Inconsistency
HTML attribute URL rewriting uses new URL(attr, url) without restricting to root-relative values, so HTML relative URLs are rewritten as documented, but CSS URLs are not:
const urlObj = new URL(attr, url) // Handles ALL relative URLs correctlyRecommended Fix
Remove the cssUrl.startsWith('/') guard and resolve all CSS URLs via new URL(cssUrl, url), while skipping already-absolute http(s) URLs.
History
This bug was introduced in commit a9aecd6. The original change attempted to prevent CSS selectors and data URIs from being incorrectly rewritten as URLs, but the fix was overly restrictive: it changed from checking isRelativeUrl(cssUrl) to only checking cssUrl.startsWith('/'), inadvertently breaking support for images/..., ./..., and ../... CSS URL patterns.