mirror of
https://github.com/amir20/dozzle.git
synced 2026-08-07 10:14:44 +00:00
fix(ui): highlighted URLs staying clickable (#4791)
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
This commit is contained in:
committed by
GitHub
parent
9b9c04498c
commit
acaec204b6
@@ -13,6 +13,8 @@ import (
|
||||
|
||||
// URL marker regex compiled once for performance
|
||||
var urlMarkerRegex = regexp.MustCompile(URLMarkerStart + "(.*?)" + URLMarkerEnd)
|
||||
var searchMarkerStripper = strings.NewReplacer(MarkerStart, "", MarkerEnd, "")
|
||||
var searchMarkerHTMLReplacer = strings.NewReplacer(MarkerStart, "<mark>", MarkerEnd, "</mark>")
|
||||
|
||||
func EscapeHTMLValues(logEvent *container.LogEvent) {
|
||||
MarkURLs(logEvent)
|
||||
@@ -45,9 +47,13 @@ func EscapeHTMLValues(logEvent *container.LogEvent) {
|
||||
|
||||
func escapeAndProcessMarkers(value string) string {
|
||||
value = html.EscapeString(value)
|
||||
value = strings.ReplaceAll(value, MarkerStart, "<mark>")
|
||||
value = strings.ReplaceAll(value, MarkerEnd, "</mark>")
|
||||
value = urlMarkerRegex.ReplaceAllString(value, "<a href=\"$1\" target=\"_blank\" rel=\"noopener noreferrer external\">$1</a>")
|
||||
value = urlMarkerRegex.ReplaceAllStringFunc(value, func(match string) string {
|
||||
url := strings.TrimSuffix(strings.TrimPrefix(match, URLMarkerStart), URLMarkerEnd)
|
||||
href := searchMarkerStripper.Replace(url)
|
||||
text := searchMarkerHTMLReplacer.Replace(url)
|
||||
return "<a href=\"" + href + "\" target=\"_blank\" rel=\"noopener noreferrer external\">" + text + "</a>"
|
||||
})
|
||||
value = searchMarkerHTMLReplacer.Replace(value)
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package support_web
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
)
|
||||
|
||||
func TestEscapeHTMLValuesKeepsSearchedURLClickable(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
search string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "https path",
|
||||
url: "https://example.com/static/uploads/proofs/image.webp",
|
||||
search: "/proofs",
|
||||
want: "https://example.com/static/uploads<mark>/proofs</mark>/image.webp",
|
||||
},
|
||||
{
|
||||
name: "https segment",
|
||||
url: "https://example.com/static/uploads/proofs/image.webp",
|
||||
search: "uploads",
|
||||
want: "https://example.com/static/<mark>uploads</mark>/proofs/image.webp",
|
||||
},
|
||||
{
|
||||
name: "http path",
|
||||
url: "http://example.com/static/uploads/proofs/image.webp",
|
||||
search: "/proofs",
|
||||
want: "http://example.com/static/uploads<mark>/proofs</mark>/image.webp",
|
||||
},
|
||||
{
|
||||
name: "localhost path",
|
||||
url: "http://localhost:3000/static/uploads/proofs/image.webp",
|
||||
search: "/proofs",
|
||||
want: "http://localhost:3000/static/uploads<mark>/proofs</mark>/image.webp",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
event := &container.LogEvent{Type: container.LogTypeSingle, Message: tt.url}
|
||||
regex, err := ParseRegex(tt.search)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !Search(regex, event) {
|
||||
t.Fatal("expected search to match URL")
|
||||
}
|
||||
|
||||
EscapeHTMLValues(event)
|
||||
|
||||
got, ok := event.Message.(string)
|
||||
if !ok {
|
||||
t.Fatalf("expected message to be string, got %T", event.Message)
|
||||
}
|
||||
if !strings.Contains(got, `href="`+tt.url+`"`) {
|
||||
t.Fatalf("expected full URL href, got %q", got)
|
||||
}
|
||||
if !strings.Contains(got, ">"+tt.want+"</a>") {
|
||||
t.Fatalf("expected highlighted URL text, got %q", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,17 @@ const (
|
||||
URLMarkerEnd = "\uE003"
|
||||
)
|
||||
|
||||
var (
|
||||
searchMarkerChars = regexp.QuoteMeta(MarkerStart + MarkerEnd)
|
||||
urlHostChars = "[-a-zA-Z0-9@:%._+~#=" + searchMarkerChars + "]"
|
||||
urlTLDChars = "[a-zA-Z0-9()" + searchMarkerChars + "]"
|
||||
urlPathChars = "[-a-zA-Z0-9()@:%_+.~#?&/=" + searchMarkerChars + "]"
|
||||
urlTailChars = "[-a-zA-Z0-9@%_+~#?&/=" + searchMarkerChars + "]"
|
||||
urlHostRegex = `(?:` + urlHostChars + `{1,256}\.` + urlTLDChars + `{1,6}|localhost(?::[0-9]+)?)`
|
||||
)
|
||||
|
||||
// Standard URL regex pattern to match http/https URLs
|
||||
var urlRegex = regexp.MustCompile(`(https?://[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}[-a-zA-Z0-9()@:%_+.~#?&/=]*/?(?:[-a-zA-Z0-9@%_+~#?&/=]|\b))`)
|
||||
var urlRegex = regexp.MustCompile(`(https?://` + urlHostRegex + urlPathChars + `*/?(?:` + urlTailChars + `|\b))`)
|
||||
|
||||
// MarkURLs marks URLs in the logEvent message with special markers
|
||||
func MarkURLs(logEvent *container.LogEvent) bool {
|
||||
|
||||
Reference in New Issue
Block a user