mirror of
https://github.com/idanoo/autobrr
synced 2025-07-22 16:29:12 +00:00

* chore: update copyright year in license headers * Revert "chore: update copyright year in license headers" This reverts commit 3e58129c431b9a491089ce36b908f9bb6ba38ed3. * chore: update copyright year in license headers * fix: sort go imports * fix: add missing license headers
147 lines
3.2 KiB
Go
147 lines
3.2 KiB
Go
// Copyright (c) 2021 - 2025, Ludvig Lundgren and the autobrr contributors.
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
package http
|
|
|
|
import (
|
|
"io/fs"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/autobrr/autobrr/internal/config"
|
|
"github.com/autobrr/autobrr/internal/logger"
|
|
|
|
"github.com/dustin/go-humanize"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/render"
|
|
)
|
|
|
|
type logsHandler struct {
|
|
cfg *config.AppConfig
|
|
}
|
|
|
|
func newLogsHandler(cfg *config.AppConfig) *logsHandler {
|
|
return &logsHandler{cfg: cfg}
|
|
}
|
|
|
|
func (h logsHandler) Routes(r chi.Router) {
|
|
r.Get("/files", h.files)
|
|
r.Get("/files/{logFile}", h.downloadFile)
|
|
}
|
|
|
|
func (h logsHandler) files(w http.ResponseWriter, r *http.Request) {
|
|
response := LogfilesResponse{
|
|
Files: []LogFile{},
|
|
Count: 0,
|
|
}
|
|
|
|
if h.cfg.Config.LogPath == "" {
|
|
render.JSON(w, r, response)
|
|
return
|
|
}
|
|
|
|
logsDir := path.Dir(h.cfg.Config.LogPath)
|
|
|
|
// check if dir exists before walkDir
|
|
if _, err := os.Stat(logsDir); os.IsNotExist(err) {
|
|
render.JSON(w, r, response)
|
|
return
|
|
}
|
|
|
|
var walk = func(path string, d fs.DirEntry, err error) error {
|
|
if d.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
if filepath.Ext(path) == ".log" {
|
|
i, err := d.Info()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
response.Files = append(response.Files, LogFile{
|
|
Name: d.Name(),
|
|
SizeBytes: i.Size(),
|
|
Size: humanize.Bytes(uint64(i.Size())),
|
|
UpdatedAt: i.ModTime(),
|
|
})
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
if err := filepath.WalkDir(logsDir, walk); err != nil {
|
|
render.Status(r, http.StatusInternalServerError)
|
|
render.JSON(w, r, errorResponse{
|
|
Message: err.Error(),
|
|
Status: http.StatusInternalServerError,
|
|
})
|
|
return
|
|
}
|
|
|
|
response.Count = len(response.Files)
|
|
|
|
render.JSON(w, r, response)
|
|
}
|
|
|
|
func (h logsHandler) downloadFile(w http.ResponseWriter, r *http.Request) {
|
|
if h.cfg.Config.LogPath == "" {
|
|
render.Status(r, http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
logsDir := path.Dir(h.cfg.Config.LogPath)
|
|
|
|
// check if dir exists before walkDir
|
|
if _, err := os.Stat(logsDir); os.IsNotExist(err) {
|
|
render.Status(r, http.StatusNotFound)
|
|
render.JSON(w, r, errorResponse{
|
|
Message: "log directory not found or inaccessible",
|
|
Status: http.StatusNotFound,
|
|
})
|
|
return
|
|
}
|
|
|
|
logFile := chi.URLParam(r, "logFile")
|
|
|
|
if !strings.Contains(logFile, ".log") {
|
|
render.Status(r, http.StatusBadRequest)
|
|
render.JSON(w, r, errorResponse{
|
|
Message: "invalid log file. Must have .log extension",
|
|
Status: http.StatusBadRequest,
|
|
})
|
|
return
|
|
}
|
|
|
|
filePath := filepath.Join(logsDir, logFile)
|
|
|
|
w.Header().Set("Content-Disposition", "attachment; filename="+strconv.Quote(logFile))
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
|
|
// Sanitize the log file and directly write the output to the HTTP socket
|
|
if err := logger.SanitizeLogFile(filePath, w); err != nil {
|
|
render.Status(r, http.StatusInternalServerError)
|
|
render.JSON(w, r, errorResponse{
|
|
Message: err.Error(),
|
|
Status: http.StatusInternalServerError,
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
type LogFile struct {
|
|
Name string `json:"filename"`
|
|
SizeBytes int64 `json:"size_bytes"`
|
|
Size string `json:"size"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type LogfilesResponse struct {
|
|
Files []LogFile `json:"files"`
|
|
Count int `json:"count"`
|
|
}
|