9eabd93098
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.
56 lines
1.6 KiB
Go
56 lines
1.6 KiB
Go
package pack
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/tamnd/kage/zim"
|
|
)
|
|
|
|
// 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)
|
|
return
|
|
}
|
|
serveBlob(w, blob)
|
|
return
|
|
}
|
|
blob, err := r.Get(zim.NamespaceContent, p)
|
|
if errors.Is(err, zim.ErrNotFound) {
|
|
http.NotFound(w, req)
|
|
return
|
|
}
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
serveBlob(w, blob)
|
|
})
|
|
}
|
|
|
|
func serveBlob(w http.ResponseWriter, b zim.Blob) {
|
|
if b.MimeType != "" {
|
|
w.Header().Set("Content-Type", b.MimeType)
|
|
}
|
|
_, _ = w.Write(b.Data)
|
|
}
|