- Fix url escape for webdav files

- Add support for bind address, url base
This commit is contained in:
Mukhtar Akere
2025-04-17 15:26:58 +01:00
parent b5b6f0ff73
commit 80615e06d1
10 changed files with 115 additions and 50 deletions

View File

@@ -8,9 +8,11 @@ import (
"github.com/sirrobot01/decypharr/pkg/qbit"
"github.com/sirrobot01/decypharr/pkg/server"
"github.com/sirrobot01/decypharr/pkg/service"
"github.com/sirrobot01/decypharr/pkg/version"
"github.com/sirrobot01/decypharr/pkg/web"
"github.com/sirrobot01/decypharr/pkg/webdav"
"github.com/sirrobot01/decypharr/pkg/worker"
"net/http"
"os"
"runtime/debug"
"strconv"
@@ -111,7 +113,7 @@ func startServices(ctx context.Context) error {
+-------------------------------------------------------+
| |
| ╔╦╗╔═╗╔═╗╦ ╦╔═╗╦ ╦╔═╗╦═╗╦═╗ |
| ║║║╣ ║ └┬┘╠═╝╠═╣╠═╣╠╦╝╠╦╝ |
| ║║║╣ ║ └┬┘╠═╝╠═╣╠═╣╠╦╝╠╦╝ (%s) |
| ═╩╝╚═╝╚═╝ ┴ ╩ ╩ ╩╩ ╩╩╚═╩╚═ |
| |
+-------------------------------------------------------+
@@ -119,11 +121,10 @@ func startServices(ctx context.Context) error {
+-------------------------------------------------------+
`
_log.Info().Msgf(asciiArt, cfg.LogLevel)
fmt.Printf(asciiArt, version.GetInfo(), cfg.LogLevel)
svc := service.New()
_qbit := qbit.New()
srv := server.New()
_webdav := webdav.New()
ui := web.New(_qbit).Routes()
@@ -131,9 +132,12 @@ func startServices(ctx context.Context) error {
qbitRoutes := _qbit.Routes()
// Register routes
srv.Mount("/", ui)
srv.Mount("/api/v2", qbitRoutes)
srv.Mount("/webdav", webdavRoutes)
handlers := map[string]http.Handler{
"/": ui,
"/api/v2": qbitRoutes,
"/webdav": webdavRoutes,
}
srv := server.New(handlers)
safeGo := func(f func() error) {
wg.Add(1)

View File

@@ -26,7 +26,7 @@ func main() {
config.SetConfigPath(configPath)
cfg := config.Get()
// Get port from environment variable or use default
qbitPort := getEnvOrDefault("QBIT_PORT", cfg.QBitTorrent.Port)
port := getEnvOrDefault("QBIT_PORT", cfg.Port)
webdavPath := ""
for _, debrid := range cfg.Debrids {
if debrid.UseWebDav {
@@ -47,18 +47,18 @@ func main() {
defer cancel()
// Check qBittorrent API
if checkQbitAPI(ctx, qbitPort) {
if checkQbitAPI(ctx, port) {
status.QbitAPI = true
}
// Check Web UI
if checkWebUI(ctx, qbitPort) {
if checkWebUI(ctx, port) {
status.WebUI = true
}
// Check WebDAV if enabled
if webdavPath != "" {
if checkWebDAV(ctx, qbitPort, webdavPath) {
if checkWebDAV(ctx, port, webdavPath) {
status.WebDAVService = true
}
} else {

View File

@@ -34,7 +34,7 @@ type Debrid struct {
type QBitTorrent struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Port string `json:"port,omitempty"`
Port string `json:"port,omitempty"` // deprecated
DownloadFolder string `json:"download_folder,omitempty"`
Categories []string `json:"categories,omitempty"`
RefreshInterval int `json:"refresh_interval,omitempty"`
@@ -81,6 +81,11 @@ type WebDav struct {
}
type Config struct {
// server
BindAddress string `json:"bind_address,omitempty"`
URLBase string `json:"url_base,omitempty"`
Port string `json:"port,omitempty"`
LogLevel string `json:"log_level,omitempty"`
Debrids []Debrid `json:"debrids,omitempty"`
MaxCacheSize int `json:"max_cache_size,omitempty"`
@@ -127,6 +132,16 @@ func (c *Config) loadConfig() error {
c.AllowedExt = getDefaultExtensions()
}
c.Port = cmp.Or(c.Port, c.QBitTorrent.Port)
if c.URLBase == "" {
c.URLBase = "/"
}
// validate url base starts with /
if c.URLBase[0] != '/' {
c.URLBase = "/" + c.URLBase
}
// Load the auth file
c.Auth = c.GetAuth()

View File

@@ -7,6 +7,7 @@ import (
"github.com/sirrobot01/decypharr/pkg/debrid/types"
"io"
"net/http"
"net/url"
"os"
"sort"
"strings"
@@ -22,7 +23,8 @@ type fileInfo struct {
isDir bool
}
func (fi *fileInfo) Name() string { return fi.name }
func (fi *fileInfo) Name() string { return url.PathEscape(fi.name) }
func (fi *fileInfo) RawName() string { return fi.name }
func (fi *fileInfo) Size() int64 { return fi.size }
func (fi *fileInfo) Mode() os.FileMode { return fi.mode }
func (fi *fileInfo) ModTime() time.Time { return fi.modTime }

View File

@@ -25,7 +25,7 @@ type QBit struct {
func New() *QBit {
_cfg := config.Get()
cfg := _cfg.QBitTorrent
port := cmp.Or(cfg.Port, os.Getenv("QBIT_PORT"), "8282")
port := cmp.Or(_cfg.Port, os.Getenv("QBIT_PORT"), "8282")
refreshInterval := cmp.Or(cfg.RefreshInterval, 10)
return &QBit{
Username: cfg.Username,

View File

@@ -1,7 +1,6 @@
package server
import (
"cmp"
"context"
"errors"
"fmt"
@@ -15,6 +14,7 @@ import (
"net/http"
"os"
"os/signal"
path "path/filepath"
"runtime"
"syscall"
)
@@ -24,32 +24,47 @@ type Server struct {
logger zerolog.Logger
}
func New() *Server {
func New(handlers map[string]http.Handler) *Server {
l := logger.New("http")
r := chi.NewRouter()
r.Use(middleware.Recoverer)
r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
return &Server{
router: r,
cfg := config.Get()
s := &Server{
logger: l,
}
r.Handle(path.Join(cfg.URLBase, "static")+"/*",
http.StripPrefix(path.Join(cfg.URLBase, "static"), http.FileServer(http.Dir("static"))),
)
r.Route(cfg.URLBase, func(r chi.Router) {
for pattern, handler := range handlers {
r.Mount(pattern, handler)
}
//logs
r.Get("/logs", s.getLogs)
//stats
r.Get("/stats", s.getStats)
//webhooks
r.Post("/webhooks/tautulli", s.handleTautulli)
})
s.router = r
return s
}
func (s *Server) Start(ctx context.Context) error {
cfg := config.Get()
// Register routes
// Register webhooks
s.router.Post("/webhooks/tautulli", s.handleTautulli)
// Register logs
s.router.Get("/logs", s.getLogs)
s.router.Get("/stats", s.getStats)
p := cmp.Or(cfg.QBitTorrent.Port, "8282")
port := fmt.Sprintf(":%s", p)
s.logger.Info().Msgf("Server started on %s", port)
addr := fmt.Sprintf("%s:%s", cfg.BindAddress, cfg.Port)
s.logger.Info().Msgf("Starting server on %s%s", addr, cfg.URLBase)
srv := &http.Server{
Addr: port,
Addr: addr,
Handler: s.router,
}
@@ -68,10 +83,6 @@ func (s *Server) Start(ctx context.Context) error {
return srv.Shutdown(context.Background())
}
func (s *Server) AddRoutes(routes func(r chi.Router) http.Handler) {
routes(s.router)
}
func (s *Server) Mount(pattern string, handler http.Handler) {
s.router.Mount(pattern, handler)
}

View File

@@ -33,6 +33,42 @@
Open Magnet Links in Decypharr
</div>
</div>
<div class="col-md-6 mt-3">
<div class="form-group">
<label for="urlBase">URL Base</label>
<input type="text"
disabled
class="form-control"
id="urlBase"
name="url_base"
placeholder="/">
<small class="form-text text-muted">URL base for the application</small>
</div>
</div>
<div class="col-md-4 mt-3">
<div class="form-group">
<label for="bindAddress">Bind Address</label>
<input type="text"
disabled
class="form-control"
id="bindAddress"
name="bind_address"
placeholder="">
<small class="form-text text-muted">Bind address for the application(default is all interface)</small>
</div>
</div>
<div class="col-md-2 mt-3">
<div class="form-group">
<label for="port">Port</label>
<input type="text"
disabled
class="form-control"
id="port"
name="port"
placeholder="8282">
<small class="form-text text-muted">Port</small>
</div>
</div>
<div class="col-md-6 mt-3">
<div class="form-group">
<label for="discordWebhookUrl">Discord Webhook URL</label>
@@ -69,7 +105,6 @@
<small class="form-text text-muted">Minimum file size to download (0 for no limit)</small>
</div>
</div>
<div class="col-md-6 mt-3">
<div class="form-group">
<label for="maxFileSize">Maximum File Size</label>
@@ -106,10 +141,6 @@
<label class="form-label" for="qbit.password">Password</label>
<input type="password" class="form-control" name="qbit.password" id="qbit.password">
</div>
<div class="col-md-6 mb-3">
<label class="form-label" for="qbit.port">Port</label>
<input type="text" class="form-control" name="qbit.port" id="qbit.port" placeholder="e.g., 8080">
</div>
<div class="col-md-6 mb-3">
<label class="form-label" for="qbit.download_folder">Symlink/Download Folder</label>
<input type="text" class="form-control" name="qbit.download_folder" id="qbit.download_folder">
@@ -369,6 +400,15 @@
if (config.discord_webhook_url) {
document.querySelector('[name="discord_webhook_url"]').value = config.discord_webhook_url;
}
if (config.url_base) {
document.querySelector('[name="url_base"]').value = config.url_base;
}
if (config.bind_address) {
document.querySelector('[name="bind_address"]').value = config.bind_address;
}
if (config.port) {
document.querySelector('[name="port"]').value = config.port;
}
})
.catch(error => {
console.error('Error loading configuration:', error);
@@ -532,7 +572,6 @@
qbittorrent: {
username: document.querySelector('[name="qbit.username"]').value,
password: document.querySelector('[name="qbit.password"]').value,
port: document.querySelector('[name="qbit.port"]').value,
download_folder: document.querySelector('[name="qbit.download_folder"]').value,
refresh_interval: parseInt(document.querySelector('[name="qbit.refresh_interval"]').value || '0', 10)
},

View File

@@ -1,6 +1,7 @@
package webdav
import (
"net/url"
"os"
"time"
)
@@ -14,7 +15,8 @@ type FileInfo struct {
isDir bool
}
func (fi *FileInfo) Name() string { return fi.name }
func (fi *FileInfo) Name() string { return url.PathEscape(fi.name) }
func (fi *FileInfo) RawName() string { return fi.name }
func (fi *FileInfo) Size() int64 { return fi.size }
func (fi *FileInfo) Mode() os.FileMode { return fi.mode }
func (fi *FileInfo) ModTime() time.Time { return fi.modTime }

View File

@@ -14,7 +14,6 @@ import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
path "path/filepath"
"slices"
@@ -466,13 +465,6 @@ func (h *Handler) serveDirectory(w http.ResponseWriter, r *http.Request, file we
"add": func(a, b int) int {
return a + b
},
"urlpath": func(p string) string {
segments := strings.Split(p, "/")
for i, segment := range segments {
segments[i] = url.PathEscape(segment)
}
return strings.Join(segments, "/")
},
"formatSize": func(bytes int64) string {
const (
KB = 1024

View File

@@ -109,13 +109,13 @@ const directoryTemplate = `
<h1>Index of {{.Path}}</h1>
<ul>
{{if .ShowParent}}
<li><a href="{{urlpath .ParentPath}}" class="parent-dir"><span class="file-number"></span>Parent Directory</a></li>
<li><a href="{{ .ParentPath}}" class="parent-dir"><span class="file-number"></span>Parent Directory</a></li>
{{end}}
{{range $index, $file := .Children}}
<li>
<a href="{{urlpath (printf "%s/%s" $.Path $file.Name)}}">
<a href="{{(printf "%s/%s" $.Path $file.Name)}}">
<span class="file-number">{{add $index 1}}.</span>
<span class="file-name">{{$file.Name}}{{if $file.IsDir}}/{{end}}</span>
<span class="file-name">{{$file.RawName}}{{if $file.IsDir}}/{{end}}</span>
<span class="file-info">
{{if not $file.IsDir}}
{{formatSize $file.Size}}