package server import ( "encoding/base64" "fmt" "net/http" "path/filepath" "strconv" "git.mchus.pro/mchus/logpile/internal/sanitize" ) // sanitizeArtifact holds the de-identified copy of the current upload, ready for // download. Kept in memory only, like convertOutput. type sanitizeArtifact struct { Data []byte Filename string } func (s *Server) setSanitizeArtifact(a *sanitizeArtifact) { s.mu.Lock() s.sanitizeResult = a s.mu.Unlock() } func (s *Server) getSanitizeArtifact() *sanitizeArtifact { s.mu.RLock() defer s.mu.RUnlock() return s.sanitizeResult } // handleSanitize builds a de-identified copy of the retained original upload and // returns the change preview. The file itself is fetched from GET /api/sanitize/download. func (s *Server) handleSanitize(w http.ResponseWriter, r *http.Request) { pkg := s.GetRawExport() if pkg == nil || pkg.Source.Kind != "file_bytes" { jsonError(w, "sanitize is only available for an uploaded archive or log file", http.StatusUnprocessableEntity) return } if !sanitize.CanSanitize(pkg.Source.Filename) { jsonError(w, "this file format cannot be sanitized in place", http.StatusUnprocessableEntity) return } data, err := base64.StdEncoding.DecodeString(pkg.Source.Data) if err != nil { jsonError(w, "cannot read the original file bytes", http.StatusInternalServerError) return } res, err := sanitize.Sanitize(pkg.Source.Filename, data) if err != nil { jsonError(w, "sanitize failed: "+err.Error(), http.StatusUnprocessableEntity) return } s.setSanitizeArtifact(&sanitizeArtifact{Data: res.Data, Filename: pkg.Source.Filename}) jsonResponse(w, map[string]any{ "filename": filepath.Base(pkg.Source.Filename), "input_size": len(data), "output_size": len(res.Data), "total_replaced": res.TotalReplaced, "changes": res.Changes, "skipped_binary": res.SkippedBinary, }) } func (s *Server) handleSanitizeDownload(w http.ResponseWriter, r *http.Request) { art := s.getSanitizeArtifact() if art == nil { jsonError(w, "no sanitized file ready; run POST /api/sanitize first", http.StatusNotFound) return } // Always octet-stream so the browser saves rather than renders it. w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filepath.Base(art.Filename))) w.Header().Set("Content-Length", strconv.Itoa(len(art.Data))) _, _ = w.Write(art.Data) }