package server import ( "net/http" "net/url" "os" "strings" "time" "github.com/go-chi/chi/v5" "git.hubris.network/dtoro/artifacto/internal/auth" ) type unlockData struct { Slug string Error string } func (s *Server) getArtifact(w http.ResponseWriter, r *http.Request) { slug := chi.URLParam(r, "slug") art, err := s.store.GetArtifact(slug) if err != nil { http.NotFound(w, r) return } if art.ExpiresAt != nil && time.Now().After(*art.ExpiresAt) { http.NotFound(w, r) return } if art.HasPassword && !s.artifact.Verify(r, slug) { s.render(w, "unlock", unlockData{Slug: slug}) return } body, err := os.ReadFile(s.store.ArtifactPath(slug)) if err != nil { http.Error(w, "artifact missing on disk", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write(body) // Log view asynchronously. Failures are non-fatal. go s.logView(r, slug) } func (s *Server) postUnlock(w http.ResponseWriter, r *http.Request) { slug := chi.URLParam(r, "slug") art, err := s.store.GetArtifact(slug) if err != nil || !art.HasPassword { http.NotFound(w, r) return } if err := r.ParseForm(); err != nil { http.Error(w, "bad form", http.StatusBadRequest) return } vid := s.visitorID(r) key := slug + "|" + vid if !s.rateLimiter.Allow(key) { w.Header().Set("Retry-After", "600") http.Error(w, "too many attempts, try again in ~10 minutes", http.StatusTooManyRequests) return } ok := auth.VerifyPassword(art.PasswordHash, r.FormValue("password")) _ = s.store.LogUnlockAttempt(slug, vid, ok, time.Now()) if !ok { s.render(w, "unlock", unlockData{Slug: slug, Error: "wrong password"}) return } s.artifact.SetCookie(w, slug) http.Redirect(w, r, "/p/"+slug, http.StatusSeeOther) } func (s *Server) logView(r *http.Request, slug string) { now := time.Now() vid := s.visitorID(r) ref := refererHost(r.Referer()) _ = s.store.LogView(slug, vid, ref, now) _ = s.store.TouchView(slug, now) } func refererHost(raw string) string { if raw == "" { return "" } u, err := url.Parse(raw) if err != nil || u.Host == "" { return "" } return strings.ToLower(u.Host) }