summaryrefslogtreecommitdiff
path: root/internal/serve/serve.go
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-09-21 07:05:24 +0000
committerClaude <noreply@anthropic.com>2026-09-21 07:05:24 +0000
commite3ecae12d7a309eb5a6c8e62910257096cb99375 (patch)
tree4f13a1e67dff0af95e5ff49126c75e56e49d83c6 /internal/serve/serve.go
parent293a627358553c9f61802caf9d4bc76af9c2e239 (diff)
Split download from hosting; carry the token into enclosure URLsHEADmain
The machine that hosts a feed and the machine that downloads the audio do not have to be the same, and usually shouldn't be: a datacentre IP is treated very differently from a home connection by the larger video sites. Downloading where the person actually is sidesteps that entirely, and means the server needs no credentials for or route to the source. - push/ingest: `podkit push` uploads a local library to a remote `serve --ingest`, asking for a manifest first so only the difference is sent. Uploads land via a temp file and rename, so an interrupted transfer cannot be picked up as a short episode. Ingest is off by default and refuses to start without a token. - get: downloads a direct audio URL or every enclosure in an RSS feed, with no extractor involved. Numbers RSS items in reverse so a mirrored series plays in order rather than backwards. Two bugs found by running it rather than by testing it: - A tokened server produced enclosure URLs with no token. The feed loaded, listed every episode, and all of them 404'd in a real client — the whole point of the tool, broken in the default configuration. Feeds now carry the token; regression test asserts enclosures from a tokened feed actually resolve against a tokened server. - Ingest ran filepath.Base before validating, so "../evil.mp3" was stored as "evil.mp3" — contained, but under a name the client never asked for, leaving push and the server disagreeing about the library. Names are now validated raw and rejected. Also: identify with a real user-agent (libsyn 403s Go's default), write feed.xml via rename so a polling client never reads a truncated feed, and match ingested file permissions to downloaded ones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
Diffstat (limited to 'internal/serve/serve.go')
-rw-r--r--internal/serve/serve.go15
1 files changed, 15 insertions, 0 deletions
diff --git a/internal/serve/serve.go b/internal/serve/serve.go
index 0b3b340..396babe 100644
--- a/internal/serve/serve.go
+++ b/internal/serve/serve.go
@@ -23,6 +23,16 @@ type Options struct {
// secret in the URL is the mechanism that actually works with them. Anyone
// holding the URL holds the access.
Token string
+
+ // AllowIngest enables /manifest and /ingest, letting `podkit push` upload
+ // episodes from elsewhere. Off by default: a feed host that also accepts
+ // writes is a different security proposition from one that only reads.
+ AllowIngest bool
+
+ // Rebuild regenerates feed.xml after an upload. Injected rather than built
+ // here so the server does not need to know the feed's metadata, and so the
+ // ingest path is testable without a real feed.
+ Rebuild func() error
}
// NewToken returns a random token suitable for Options.Token.
@@ -45,6 +55,11 @@ func Handler(opts Options) http.Handler {
http.ServeFile(w, r, path.Join(opts.Dir, "feed.xml"))
})
+ if opts.AllowIngest {
+ mux.HandleFunc("/manifest", handleManifest(opts.Dir))
+ mux.HandleFunc("/ingest", handleIngest(opts.Dir, opts.Rebuild))
+ }
+
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)