
This change pulls in some code copied from net/http's fs.go so that we can support If-Match/If-None-Match requests. This will make it easy to put a caching proxy in front of linx-server instances. Request validation will still happen as long as the proxy can contact the origin, so expiration and deletion will still work as expected under normal circumstances.
104 lines
2.5 KiB
Go
104 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/andreimarcu/linx-server/backends"
|
|
"github.com/andreimarcu/linx-server/expiry"
|
|
"github.com/andreimarcu/linx-server/httputil"
|
|
"github.com/zenazn/goji/web"
|
|
)
|
|
|
|
func fileServeHandler(c web.C, w http.ResponseWriter, r *http.Request) {
|
|
fileName := c.URLParams["name"]
|
|
|
|
metadata, err := checkFile(fileName)
|
|
if err == backends.NotFoundErr {
|
|
notFoundHandler(c, w, r)
|
|
return
|
|
} else if err != nil {
|
|
oopsHandler(c, w, r, RespAUTO, "Corrupt metadata.")
|
|
return
|
|
}
|
|
|
|
if !Config.allowHotlink {
|
|
referer := r.Header.Get("Referer")
|
|
u, _ := url.Parse(referer)
|
|
p, _ := url.Parse(getSiteURL(r))
|
|
if referer != "" && !sameOrigin(u, p) {
|
|
http.Redirect(w, r, Config.sitePath+fileName, 303)
|
|
return
|
|
}
|
|
}
|
|
|
|
w.Header().Set("Content-Security-Policy", Config.fileContentSecurityPolicy)
|
|
w.Header().Set("Referrer-Policy", Config.fileReferrerPolicy)
|
|
|
|
w.Header().Set("Content-Type", metadata.Mimetype)
|
|
w.Header().Set("Content-Length", strconv.FormatInt(metadata.Size, 10))
|
|
w.Header().Set("Etag", fmt.Sprintf("\"%s\"", metadata.Sha256sum))
|
|
w.Header().Set("Cache-Control", "public, no-cache")
|
|
|
|
modtime := time.Unix(0, 0)
|
|
if done := httputil.CheckPreconditions(w, r, modtime); done == true {
|
|
return
|
|
}
|
|
|
|
if r.Method != "HEAD" {
|
|
_, reader, err := storageBackend.Get(fileName)
|
|
if err != nil {
|
|
oopsHandler(c, w, r, RespAUTO, "Unable to open file.")
|
|
return
|
|
}
|
|
defer reader.Close()
|
|
|
|
if _, err = io.CopyN(w, reader, metadata.Size); err != nil {
|
|
oopsHandler(c, w, r, RespAUTO, err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
func staticHandler(c web.C, w http.ResponseWriter, r *http.Request) {
|
|
path := r.URL.Path
|
|
if path[len(path)-1:] == "/" {
|
|
notFoundHandler(c, w, r)
|
|
return
|
|
} else {
|
|
if path == "/favicon.ico" {
|
|
path = Config.sitePath + "/static/images/favicon.gif"
|
|
}
|
|
|
|
filePath := strings.TrimPrefix(path, Config.sitePath+"static/")
|
|
file, err := staticBox.Open(filePath)
|
|
if err != nil {
|
|
notFoundHandler(c, w, r)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Etag", fmt.Sprintf("\"%s\"", timeStartedStr))
|
|
w.Header().Set("Cache-Control", "public, max-age=86400")
|
|
http.ServeContent(w, r, filePath, timeStarted, file)
|
|
return
|
|
}
|
|
}
|
|
|
|
func checkFile(filename string) (metadata backends.Metadata, err error) {
|
|
metadata, err = storageBackend.Head(filename)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if expiry.IsTsExpired(metadata.Expiry) {
|
|
storageBackend.Delete(filename)
|
|
err = backends.NotFoundErr
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|