diff options
Diffstat (limited to 'app/cache.go')
| -rwxr-xr-x | app/cache.go | 41 |
1 files changed, 27 insertions, 14 deletions
diff --git a/app/cache.go b/app/cache.go index 1cb8988..91c9847 100755 --- a/app/cache.go +++ b/app/cache.go @@ -1,8 +1,9 @@ -// TODO: implement JSON caching and clean up the code package app +// TODO: implement JSON caching and clean up the code. + import ( - "crypto/sha1" + "crypto/sha1" //nolint:gosec // G505: SHA-1 is a cache-key hash here, not a security primitive "encoding/hex" "io" "os" @@ -20,6 +21,9 @@ type file struct { var tempFS = make(map[[20]byte]*file) var mx = &sync.RWMutex{} +// DownloadAndSendMedia proxies one image from DeviantArt's wixmp CDN to the +// client, serving it from the on-disk or in-memory cache when enabled. It +// responds 403 when proxying is turned off for this instance. func (s skunkyart) DownloadAndSendMedia(subdomain, path string) { var url strings.Builder url.WriteString("https://images-wixmp-") @@ -35,20 +39,24 @@ func (s skunkyart) DownloadAndSendMedia(subdomain, path string) { switch { case CFG.Cache.Enabled: - fileName := sha1.Sum([]byte(subdomain + path)) + fileName := sha1.Sum([]byte(subdomain + path)) //nolint:gosec // G401: cache-key hash, not a security primitive filePath := CFG.Cache.Path + "/" + hex.EncodeToString(fileName[:]) c := func() { - file, err := os.Open(filePath) + // filePath is built from a SHA-1 of the request, not from user input, + // so it cannot escape the cache directory. + file, err := os.Open(filePath) //nolint:gosec // G304: path is a hash, not user-controlled if err != nil { - if dwnld := Download(url.String()); dwnld.Status == 200 && dwnld.Headers["Content-Type"][0][:5] == "image" { + dwnld := Download(url.String()) + if dwnld.Status == 200 && strings.HasPrefix(dwnld.Headers.Get("Content-Type"), "image") { response = dwnld.Body - try(os.WriteFile(filePath, response, 0700)) + try(os.WriteFile(filePath, response, 0600)) } else { s.ReturnHTTPError(dwnld.Status) return } } else { + defer func() { try(file.Close()) }() file, e := io.ReadAll(file) try(e) response = file @@ -104,16 +112,19 @@ func (s skunkyart) DownloadAndSendMedia(subdomain, path string) { response = []byte("Sorry, butt proxy on this instance are disabled.") } - s.Writer.Write(response) + _, _ = s.Writer.Write(response) } +// InitCacheSystem runs the cache rotation loop forever, evicting files past +// their lifetime and emptying the cache when it outgrows max-size. Run it in its +// own goroutine. func InitCacheSystem() { c := &CFG.Cache for { dir, err := os.ReadDir(c.Path) if err != nil { if os.IsNotExist(err) { - os.Mkdir(c.Path, 0700) + try(os.Mkdir(c.Path, 0700)) continue } println(err.Error()) @@ -128,11 +139,13 @@ func InitCacheSystem() { if c.Lifetime != "" { now := time.Now().UnixMilli() - stat := fileInfo.Sys().(*syscall.Stat_t) - time := statTime(stat) - - if time+lifetimeParsed <= now { - try(os.RemoveAll(fileName)) + // Sys() is platform-specific and only documented to be a + // *syscall.Stat_t on unix; skip rotation rather than panic + // if the filesystem reports something else. + if stat, ok := fileInfo.Sys().(*syscall.Stat_t); ok { + if statTime(stat)+lifetimeParsed <= now { + try(os.RemoveAll(fileName)) + } } } @@ -144,7 +157,7 @@ func InitCacheSystem() { if c.MaxSize != 0 && total > c.MaxSize { try(os.RemoveAll(c.Path)) - os.Mkdir(c.Path, 0700) + try(os.Mkdir(c.Path, 0700)) } time.Sleep(time.Second * time.Duration(c.UpdateInterval)) |
