Compare commits

...

2 Commits

Author SHA1 Message Date
Duc-Tam Nguyen 9eabd93098 Redirect / to the main page instead of serving it in place
The served main page broke its own CSS and images when the entry point
was a nested page. kage saves each page's asset links as mirror-relative
paths (../_kage/...) computed for that page's own location, but the
handler served the main page's bytes directly at /, so the browser
resolved those relative URLs against / and 404ed every one of them. A
developer.apple.com/documentation mirror landed at / with no styles.

Redirect / to the main page's canonical content path, the way the
archive's W/mainPage redirect already does, so the browser navigates to
the page's real URL and resolves its relative assets correctly. Kiwix
was unaffected because it follows that redirect itself.
2026-06-15 22:46:42 +07:00
Tam Nguyen Duc 2ccf7bc5db Merge pull request #26 from tamnd/release-0.3.0
Cut the v0.3.0 release notes
2026-06-15 21:32:23 +07:00
2 changed files with 31 additions and 3 deletions
+18
View File
@@ -326,6 +326,24 @@ func TestHandler(t *testing.T) {
if code, body := get("/"); code != 200 || !bytes.Contains([]byte(body), []byte("Example Home")) {
t.Errorf("GET / = %d %.30q", code, body)
}
// "/" must redirect to the main page's canonical content path, not serve its
// bytes in place, so the page's mirror-relative asset URLs resolve against the
// right base instead of 404ing.
noFollow := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error {
return http.ErrUseLastResponse
}}
resp, err := noFollow.Get(srv.URL + "/")
if err != nil {
t.Fatal(err)
}
_ = resp.Body.Close()
if resp.StatusCode != http.StatusFound {
t.Errorf("GET / status = %d, want 302", resp.StatusCode)
}
if loc := resp.Header.Get("Location"); loc != "/index.html" {
t.Errorf("GET / Location = %q, want %q", loc, "/index.html")
}
if code, _ := get("/about/index.html"); code != 200 {
t.Errorf("GET /about/index.html = %d", code)
}
+13 -3
View File
@@ -8,14 +8,24 @@ import (
"github.com/tamnd/kage/zim"
)
// Handler serves a ZIM archive over HTTP. "/" maps to the archive's main page;
// "/a/b.png" maps to the C/a/b.png content entry. Because the saved HTML's links
// are mirror-relative paths, and those are exactly the C urls, a click in a
// Handler serves a ZIM archive over HTTP. "/" redirects to the archive's main
// page; "/a/b.png" maps to the C/a/b.png content entry. Because the saved HTML's
// links are mirror-relative paths, and those are exactly the C urls, a click in a
// served page hits the right entry with no rewriting. A miss is a plain 404.
func Handler(r *zim.Reader) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
p := strings.TrimPrefix(req.URL.Path, "/")
if p == "" {
// The main page's saved HTML carries mirror-relative asset URLs
// (../_kage/...) computed for its own nested location, so serving its
// bytes at "/" would resolve them against the wrong base and 404 the
// page's CSS and images. Redirect to the page's canonical content path
// instead, the way the archive's W/mainPage redirect does, so the
// browser resolves those relative URLs correctly.
if ns, url, ok := r.MainPageRef(); ok && ns == zim.NamespaceContent {
http.Redirect(w, req, "/"+url, http.StatusFound)
return
}
blob, err := r.MainPage()
if err != nil {
http.NotFound(w, req)