mirror of
https://github.com/jech/galene.git
synced 2024-11-14 04:35:57 +01:00
Rework recordings server.
Simplifies the code, makes the files cachable, fixes permission issues.
This commit is contained in:
parent
eab848f2cf
commit
4e14c29fbb
1 changed files with 84 additions and 78 deletions
|
@ -107,24 +107,32 @@ func httpError(w http.ResponseWriter, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileHandler is our custom reimplementation of http.FileServer
|
|
||||||
type fileHandler struct {
|
|
||||||
root http.FileSystem
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeEtag(d os.FileInfo) string {
|
|
||||||
return fmt.Sprintf("\"%v-%v\"", d.Size(), d.ModTime().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
normalCacheControl = "max-age=1800"
|
normalCacheControl = "max-age=1800"
|
||||||
veryCachableCacheControl = "max-age=86400"
|
veryCachableCacheControl = "max-age=86400"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isVeryCachable(p string) bool {
|
func makeCachable(w http.ResponseWriter, p string, fi os.FileInfo, cachable bool) {
|
||||||
return strings.HasPrefix(p, "/fonts/") ||
|
etag := fmt.Sprintf("\"%v-%v\"", fi.Size(), fi.ModTime().UnixNano())
|
||||||
|
w.Header().Set("ETag", etag)
|
||||||
|
if !cachable {
|
||||||
|
w.Header().Set("cache-control", "no-cache")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cc := normalCacheControl
|
||||||
|
if strings.HasPrefix(p, "/fonts/") ||
|
||||||
strings.HasPrefix(p, "/scripts/") ||
|
strings.HasPrefix(p, "/scripts/") ||
|
||||||
strings.HasPrefix(p, "/css/")
|
strings.HasPrefix(p, "/css/") {
|
||||||
|
cc = veryCachableCacheControl
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Cache-Control", cc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileHandler is our custom reimplementation of http.FileServer
|
||||||
|
type fileHandler struct {
|
||||||
|
root http.FileSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fh *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (fh *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -143,13 +151,13 @@ func (fh *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
d, err := f.Stat()
|
fi, err := f.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, err)
|
httpError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.IsDir() {
|
if fi.IsDir() {
|
||||||
u := r.URL.Path
|
u := r.URL.Path
|
||||||
if u[len(u)-1] != '/' {
|
if u[len(u)-1] != '/' {
|
||||||
http.Redirect(w, r, u+"/", http.StatusPermanentRedirect)
|
http.Redirect(w, r, u+"/", http.StatusPermanentRedirect)
|
||||||
|
@ -175,22 +183,12 @@ func (fh *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
httpError(w, ErrIsDirectory)
|
httpError(w, ErrIsDirectory)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f, d = ff, dd
|
f, fi = ff, dd
|
||||||
p = index
|
p = index
|
||||||
}
|
}
|
||||||
|
|
||||||
etag := makeEtag(d)
|
makeCachable(w, p, fi, true)
|
||||||
if etag != "" {
|
http.ServeContent(w, r, fi.Name(), fi.ModTime(), f)
|
||||||
w.Header().Add("ETag", etag)
|
|
||||||
}
|
|
||||||
|
|
||||||
cc := normalCacheControl
|
|
||||||
if isVeryCachable(p) {
|
|
||||||
cc = veryCachableCacheControl
|
|
||||||
}
|
|
||||||
w.Header().Add("Cache-Control", cc)
|
|
||||||
|
|
||||||
http.ServeContent(w, r, d.Name(), d.ModTime(), f)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveFile is similar to http.ServeFile, except that it doesn't check
|
// serveFile is similar to http.ServeFile, except that it doesn't check
|
||||||
|
@ -202,25 +200,19 @@ func serveFile(w http.ResponseWriter, r *http.Request, p string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
d, err := f.Stat()
|
fi, err := f.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, err)
|
httpError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.IsDir() {
|
if fi.IsDir() {
|
||||||
httpError(w, ErrIsDirectory)
|
httpError(w, ErrIsDirectory)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
etag := makeEtag(d)
|
makeCachable(w, p, fi, true)
|
||||||
if etag != "" {
|
http.ServeContent(w, r, fi.Name(), fi.ModTime(), f)
|
||||||
w.Header().Add("ETag", etag)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Add("Cache-Control", normalCacheControl)
|
|
||||||
|
|
||||||
http.ServeContent(w, r, d.Name(), d.ModTime(), f)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseGroupName(path string) string {
|
func parseGroupName(path string) string {
|
||||||
|
@ -423,55 +415,64 @@ func recordingsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pth := r.URL.Path[12:]
|
p := "/" + r.URL.Path[12:]
|
||||||
|
|
||||||
if pth == "" {
|
if filepath.Separator != '/' &&
|
||||||
http.Error(w, "nothing to see", http.StatusNotImplemented)
|
strings.ContainsRune(p, filepath.Separator) {
|
||||||
|
http.Error(w, "bad character in filename",
|
||||||
|
http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Open(filepath.Join(disk.Directory, pth))
|
if p == "/" {
|
||||||
if err != nil {
|
http.Error(w, "nothing to see", http.StatusForbidden)
|
||||||
if os.IsNotExist(err) {
|
return
|
||||||
notFound(w)
|
|
||||||
} else {
|
|
||||||
http.Error(w, "server error",
|
|
||||||
http.StatusInternalServerError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p = path.Clean(p)
|
||||||
|
|
||||||
|
f, err := os.Open(filepath.Join(disk.Directory, p))
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
fi, err := f.Stat()
|
fi, err := f.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "server error", http.StatusInternalServerError)
|
httpError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
group := path.Dir(p[1:])
|
||||||
|
if fi.IsDir() {
|
||||||
|
u := r.URL.Path
|
||||||
|
if u[len(u)-1] != '/' {
|
||||||
|
http.Redirect(w, r, u+"/", http.StatusPermanentRedirect)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
group = p[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := checkGroupPermissions(w, r, group)
|
||||||
|
if !ok {
|
||||||
|
failAuthentication(w, "recordings/"+group)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
if pth[len(pth)-1] != '/' {
|
|
||||||
http.Redirect(w, r,
|
|
||||||
r.URL.Path+"/", http.StatusPermanentRedirect)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ok := checkGroupPermissions(w, r, path.Dir(pth))
|
|
||||||
if !ok {
|
|
||||||
failAuthentication(w, "recordings/"+path.Dir(pth))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.Method == "POST" {
|
if r.Method == "POST" {
|
||||||
handleGroupAction(w, r, path.Dir(pth))
|
handleGroupAction(w, r, group)
|
||||||
} else {
|
} else {
|
||||||
serveGroupRecordings(w, r, f, path.Dir(pth))
|
serveGroupRecordings(w, r, f, group)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ok := checkGroupPermissions(w, r, path.Dir(pth))
|
|
||||||
if !ok {
|
|
||||||
failAuthentication(w, "recordings/"+path.Dir(pth))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.ServeContent(w, r, r.URL.Path, fi.ModTime(), f)
|
|
||||||
}
|
// Ensure the file is uncachable if it's still recording
|
||||||
|
cachable := time.Since(fi.ModTime()) > time.Minute
|
||||||
|
makeCachable(w, path.Join("/recordings/", p), fi, cachable)
|
||||||
|
http.ServeContent(w, r, fi.Name(), fi.ModTime(), f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGroupAction(w http.ResponseWriter, r *http.Request, group string) {
|
func handleGroupAction(w http.ResponseWriter, r *http.Request, group string) {
|
||||||
|
@ -496,17 +497,21 @@ func handleGroupAction(w http.ResponseWriter, r *http.Request, group string) {
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strings.ContainsRune(filename, '/') ||
|
||||||
|
strings.ContainsRune(filename, filepath.Separator) {
|
||||||
|
http.Error(w, "bad character in filename",
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
err := os.Remove(
|
err := os.Remove(
|
||||||
filepath.Join(disk.Directory, group+"/"+filename),
|
filepath.Join(disk.Directory,
|
||||||
|
filepath.Join(group,
|
||||||
|
path.Clean("/"+filename),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsPermission(err) {
|
httpError(w, err)
|
||||||
http.Error(w, "unauthorized",
|
|
||||||
http.StatusForbidden)
|
|
||||||
} else {
|
|
||||||
http.Error(w, "server error",
|
|
||||||
http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.Redirect(w, r, "/recordings/"+group+"/",
|
http.Redirect(w, r, "/recordings/"+group+"/",
|
||||||
|
@ -553,7 +558,7 @@ func serveGroupRecordings(w http.ResponseWriter, r *http.Request, f *os.File, gr
|
||||||
fmt.Fprintf(w, "<!DOCTYPE html>\n<html><head>\n")
|
fmt.Fprintf(w, "<!DOCTYPE html>\n<html><head>\n")
|
||||||
fmt.Fprintf(w, "<title>Recordings for group %v</title>\n", group)
|
fmt.Fprintf(w, "<title>Recordings for group %v</title>\n", group)
|
||||||
fmt.Fprintf(w, "<link rel=\"stylesheet\" type=\"text/css\" href=\"/common.css\"/>")
|
fmt.Fprintf(w, "<link rel=\"stylesheet\" type=\"text/css\" href=\"/common.css\"/>")
|
||||||
fmt.Fprintf(w, "<head><body>\n")
|
fmt.Fprintf(w, "</head><body>\n")
|
||||||
|
|
||||||
fmt.Fprintf(w, "<table>\n")
|
fmt.Fprintf(w, "<table>\n")
|
||||||
for _, fi := range fis {
|
for _, fi := range fis {
|
||||||
|
@ -566,8 +571,9 @@ func serveGroupRecordings(w http.ResponseWriter, r *http.Request, f *os.File, gr
|
||||||
fi.Size(),
|
fi.Size(),
|
||||||
)
|
)
|
||||||
fmt.Fprintf(w,
|
fmt.Fprintf(w,
|
||||||
"<td><form action=\"/recordings/%v/?q=delete\" method=\"post\">"+
|
"<td><form action=\"/recordings/%v/\" method=\"post\">"+
|
||||||
"<button type=\"submit\" name=\"filename\" value=\"%v\">Delete</button>"+
|
"<input type=\"hidden\" name=\"filename\" value=\"%v\">"+
|
||||||
|
"<button type=\"submit\" name=\"q\" value=\"delete\">Delete</button>"+
|
||||||
"</form></td></tr>\n",
|
"</form></td></tr>\n",
|
||||||
url.PathEscape(group), fi.Name())
|
url.PathEscape(group), fi.Name())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue