1
2
3
4
5 package vcweb
6
7 import (
8 "encoding/json"
9 "fmt"
10 "io"
11 "log"
12 "net/http"
13 "os"
14 "path"
15 "strings"
16 )
17
18
19
20
21
22
23
24
25 type authHandler struct{}
26
27 type accessToken struct {
28 Username, Password string
29 StatusCode int
30 Message string
31 }
32
33 func (h *authHandler) Available() bool { return true }
34
35 func (h *authHandler) Handler(dir string, env []string, logger *log.Logger) (http.Handler, error) {
36 fs := http.Dir(dir)
37
38 handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
39 urlPath := req.URL.Path
40 if urlPath != "" && strings.HasPrefix(path.Base(urlPath), ".") {
41 http.Error(w, "filename contains leading dot", http.StatusBadRequest)
42 return
43 }
44
45 f, err := fs.Open(urlPath)
46 if err != nil {
47 if os.IsNotExist(err) {
48 http.NotFound(w, req)
49 } else {
50 http.Error(w, err.Error(), http.StatusInternalServerError)
51 }
52 return
53 }
54
55 accessDir := urlPath
56 if fi, err := f.Stat(); err == nil && !fi.IsDir() {
57 accessDir = path.Dir(urlPath)
58 }
59 f.Close()
60
61 var accessFile http.File
62 for {
63 var err error
64 accessFile, err = fs.Open(path.Join(accessDir, ".access"))
65 if err == nil {
66 break
67 }
68
69 if !os.IsNotExist(err) {
70 http.Error(w, err.Error(), http.StatusInternalServerError)
71 return
72 }
73 if accessDir == "." {
74 http.Error(w, "failed to locate access file", http.StatusInternalServerError)
75 return
76 }
77 accessDir = path.Dir(accessDir)
78 }
79
80 data, err := io.ReadAll(accessFile)
81 if err != nil {
82 http.Error(w, err.Error(), http.StatusInternalServerError)
83 return
84 }
85
86 var token accessToken
87 if err := json.Unmarshal(data, &token); err != nil {
88 logger.Print(err)
89 http.Error(w, "malformed access file", http.StatusInternalServerError)
90 return
91 }
92 if username, password, ok := req.BasicAuth(); !ok || username != token.Username || password != token.Password {
93 code := token.StatusCode
94 if code == 0 {
95 code = http.StatusUnauthorized
96 }
97 if code == http.StatusUnauthorized {
98 w.Header().Add("WWW-Authenticate", fmt.Sprintf("basic realm=%s", accessDir))
99 }
100 http.Error(w, token.Message, code)
101 return
102 }
103
104 http.FileServer(fs).ServeHTTP(w, req)
105 })
106
107 return handler, nil
108 }
109
View as plain text