mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-08-31 13:17:09 +00:00
better logic for relative urls
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -192,6 +193,15 @@ func (f *Fetcher) RenderMarkdown(content []byte) (string, error) {
|
||||
return f.renderMarkdown(content, "")
|
||||
}
|
||||
|
||||
// Regex patterns for matching relative URLs that need rewriting
|
||||
// These match src="..." or href="..." where the URL is relative (not absolute, not data:, not #anchor)
|
||||
var (
|
||||
// Match src="filename" where filename doesn't start with http://, https://, //, /, #, data:, or mailto:
|
||||
relativeSrcPattern = regexp.MustCompile(`src="([^"/:][^"]*)"`)
|
||||
// Match href="filename" where filename doesn't start with http://, https://, //, /, #, data:, or mailto:
|
||||
relativeHrefPattern = regexp.MustCompile(`href="([^"/:][^"]*)"`)
|
||||
)
|
||||
|
||||
// rewriteRelativeURLs converts relative URLs to absolute URLs
|
||||
func rewriteRelativeURLs(html, baseURL string) string {
|
||||
if baseURL == "" {
|
||||
@@ -203,20 +213,51 @@ func rewriteRelativeURLs(html, baseURL string) string {
|
||||
return html
|
||||
}
|
||||
|
||||
// Simple string replacement for common patterns
|
||||
// This is a basic implementation - for production, consider using an HTML parser
|
||||
// Handle root-relative URLs (starting with /) first
|
||||
// Must be done before bare relative URLs to avoid double-processing
|
||||
if base.Scheme != "" && base.Host != "" {
|
||||
root := fmt.Sprintf("%s://%s/", base.Scheme, base.Host)
|
||||
// Replace src="/" and href="/" but not src="//" (protocol-relative URLs)
|
||||
html = strings.ReplaceAll(html, `src="/`, fmt.Sprintf(`src="%s`, root))
|
||||
html = strings.ReplaceAll(html, `href="/`, fmt.Sprintf(`href="%s`, root))
|
||||
}
|
||||
|
||||
// Handle explicit relative paths (./something and ../something)
|
||||
html = strings.ReplaceAll(html, `src="./`, fmt.Sprintf(`src="%s`, baseURL))
|
||||
html = strings.ReplaceAll(html, `href="./`, fmt.Sprintf(`href="%s`, baseURL))
|
||||
html = strings.ReplaceAll(html, `src="../`, fmt.Sprintf(`src="%s../`, baseURL))
|
||||
html = strings.ReplaceAll(html, `href="../`, fmt.Sprintf(`href="%s../`, baseURL))
|
||||
|
||||
// Handle root-relative URLs (starting with /)
|
||||
if base.Scheme != "" && base.Host != "" {
|
||||
root := fmt.Sprintf("%s://%s/", base.Scheme, base.Host)
|
||||
// Replace src="/" and href="/" but not src="//" (absolute URLs)
|
||||
html = strings.ReplaceAll(html, `src="/`, fmt.Sprintf(`src="%s`, root))
|
||||
html = strings.ReplaceAll(html, `href="/`, fmt.Sprintf(`href="%s`, root))
|
||||
}
|
||||
// Handle bare relative URLs (e.g., src="image.png" without ./ prefix)
|
||||
// Skip URLs that are already absolute (start with http://, https://, or //)
|
||||
// Skip anchors (#), data URLs (data:), and mailto links
|
||||
html = relativeSrcPattern.ReplaceAllStringFunc(html, func(match string) string {
|
||||
// Extract the URL from src="..."
|
||||
url := match[5 : len(match)-1] // Remove 'src="' and '"'
|
||||
|
||||
// Skip if already processed or is a special URL type
|
||||
if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") ||
|
||||
strings.HasPrefix(url, "//") || strings.HasPrefix(url, "#") ||
|
||||
strings.HasPrefix(url, "data:") || strings.HasPrefix(url, "mailto:") {
|
||||
return match
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`src="%s%s"`, baseURL, url)
|
||||
})
|
||||
|
||||
html = relativeHrefPattern.ReplaceAllStringFunc(html, func(match string) string {
|
||||
// Extract the URL from href="..."
|
||||
url := match[6 : len(match)-1] // Remove 'href="' and '"'
|
||||
|
||||
// Skip if already processed or is a special URL type
|
||||
if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") ||
|
||||
strings.HasPrefix(url, "//") || strings.HasPrefix(url, "#") ||
|
||||
strings.HasPrefix(url, "data:") || strings.HasPrefix(url, "mailto:") {
|
||||
return match
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`href="%s%s"`, baseURL, url)
|
||||
})
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
@@ -145,6 +145,48 @@ func TestRewriteRelativeURLs(t *testing.T) {
|
||||
baseURL: "https://example.com/docs/",
|
||||
expected: `<img src="https://example.com//cdn.example.com/image.png">`,
|
||||
},
|
||||
{
|
||||
name: "bare relative src (no ./ prefix)",
|
||||
html: `<img src="image.png">`,
|
||||
baseURL: "https://example.com/docs/",
|
||||
expected: `<img src="https://example.com/docs/image.png">`,
|
||||
},
|
||||
{
|
||||
name: "bare relative href (no ./ prefix)",
|
||||
html: `<a href="page.html">link</a>`,
|
||||
baseURL: "https://example.com/docs/",
|
||||
expected: `<a href="https://example.com/docs/page.html">link</a>`,
|
||||
},
|
||||
{
|
||||
name: "bare relative with path",
|
||||
html: `<img src="images/logo.png">`,
|
||||
baseURL: "https://example.com/docs/",
|
||||
expected: `<img src="https://example.com/docs/images/logo.png">`,
|
||||
},
|
||||
{
|
||||
name: "anchor links unchanged",
|
||||
html: `<a href="#section">link</a>`,
|
||||
baseURL: "https://example.com/docs/",
|
||||
expected: `<a href="#section">link</a>`,
|
||||
},
|
||||
{
|
||||
name: "data URLs unchanged",
|
||||
html: `<img src="data:image/png;base64,abc123">`,
|
||||
baseURL: "https://example.com/docs/",
|
||||
expected: `<img src="data:image/png;base64,abc123">`,
|
||||
},
|
||||
{
|
||||
name: "mailto links unchanged",
|
||||
html: `<a href="mailto:test@example.com">email</a>`,
|
||||
baseURL: "https://example.com/docs/",
|
||||
expected: `<a href="mailto:test@example.com">email</a>`,
|
||||
},
|
||||
{
|
||||
name: "mixed bare and prefixed relative URLs",
|
||||
html: `<img src="slices_and_lucy.png"><a href="./other.md">link</a>`,
|
||||
baseURL: "https://github.com/user/repo/blob/main/",
|
||||
expected: `<img src="https://github.com/user/repo/blob/main/slices_and_lucy.png"><a href="https://github.com/user/repo/blob/main/other.md">link</a>`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
Reference in New Issue
Block a user