package web import ( "html/template" "net/url" "strings" "testing" "gitea.dooplex.hu/admin/felhom-controller/internal/infra" ) // The deep-link template, pinned against the shipped FileBrowser Quantum router (SPIKE P2). func TestFileBrowserLink_Template(t *testing.T) { const dom = "demo-felhom.eu" for _, tc := range []struct{ name, source, rel, want string }{ {"ascii source, one segment", "hdd_1", "media/movies", "https://files.demo-felhom.eu/files/hdd_1/media/movies"}, {"accented source (the import label)", infra.FileBrowserImportLabel, "paperless", "https://files.demo-felhom.eu/files/Beolvas%C3%A1s/paperless"}, {"source root, empty relpath", infra.FileBrowserImportLabel, "", "https://files.demo-felhom.eu/files/Beolvas%C3%A1s"}, {"leading/trailing slashes are ignored", "hdd_1", "/media/books/", "https://files.demo-felhom.eu/files/hdd_1/media/books"}, {"accented path segment", "hdd_1", "media/könyvek", "https://files.demo-felhom.eu/files/hdd_1/media/k%C3%B6nyvek"}, } { if got := fileBrowserLink(dom, tc.source, tc.rel); got != tc.want { t.Errorf("%s:\n got %q\n want %q", tc.name, got, tc.want) } } } // THE trap, measured in SPIKE P2. QueryEscape turns a space into "+", which in a path segment is a // literal plus and lands the customer on a folder that does not exist. This test fails if anyone // swaps the escaper. func TestFileBrowserLink_UsesPathEscapeNotQueryEscape(t *testing.T) { const spaced = "Média & könyvtár" got := fileBrowserLink("x.eu", spaced, "a b") if strings.Contains(got, "+") { t.Errorf("link contains '+' — QueryEscape was used somewhere; a '+' in a path segment is a literal plus, not a space: %q", got) } if want := url.PathEscape(spaced); !strings.Contains(got, want) { t.Errorf("source not PathEscape'd: got %q, want it to contain %q", got, want) } // Guard the exact divergence the spike measured, so the two escapers can never be confused here. if url.PathEscape(spaced) == url.QueryEscape(spaced) { t.Fatal("fixture no longer distinguishes the two escapers — pick a name where they differ") } if strings.Contains(got, url.QueryEscape(spaced)) { t.Errorf("link used QueryEscape: %q", got) } } // The link is embedded in HTML. Percent-encoding and attribute-escaping must COMPOSE: PathEscape // leaves "&" bare, html/template turns it into "&", and the browser decodes it back to "&". // Anything that double-encodes (a hand-rolled HTML escape before the template) breaks the path. func TestFileBrowserLink_ComposesWithHTMLEscaping(t *testing.T) { link := fileBrowserLink("x.eu", "Média & könyvtár", "docs") var sb strings.Builder tmpl := template.Must(template.New("a").Parse(`x`)) if err := tmpl.Execute(&sb, link); err != nil { t.Fatal(err) } out := sb.String() if !strings.Contains(out, "&") { t.Errorf("html/template should escape the bare & in the href: %q", out) } // It must NOT have been percent-encoded a second time (%2526 etc. would be double-encoding). if strings.Contains(out, "%25") { t.Errorf("double percent-encoding detected — do not pre-escape before the template: %q", out) } // And no raw quote/angle escaped into the attribute. if strings.Contains(out, `href=""`) { t.Errorf("html/template refused the URL (would render an empty href): %q", out) } } // importFolderLink targets the CANONICAL source, so a drop-zone link is identical regardless of // which drive the app itself sits on. func TestImportFolderLink_IsCanonical(t *testing.T) { a := importFolderLink("demo-felhom.eu", "paperless") b := importFolderLink("demo-felhom.eu", "calibre") for _, l := range []string{a, b} { if !strings.Contains(l, "/files/"+url.PathEscape(infra.FileBrowserImportLabel)+"/") { t.Errorf("import link must go through the canonical source: %q", l) } if strings.Contains(l, "hdd_1") || strings.Contains(l, "felhom-drives") { t.Errorf("import link must never name a data drive: %q", l) } } if a == b { t.Error("different apps must get different folders") } }