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 defer accessFile.Close()
67 break
68 }
69
70 if !os.IsNotExist(err) {
71 http.Error(w, err.Error(), http.StatusInternalServerError)
72 return
73 }
74 if accessDir == "." {
75 http.Error(w, "failed to locate access file", http.StatusInternalServerError)
76 return
77 }
78 accessDir = path.Dir(accessDir)
79 }
80
81 data, err := io.ReadAll(accessFile)
82 if err != nil {
83 http.Error(w, err.Error(), http.StatusInternalServerError)
84 return
85 }
86
87 var token accessToken
88 if err := json.Unmarshal(data, &token); err != nil {
89 logger.Print(err)
90 http.Error(w, "malformed access file", http.StatusInternalServerError)
91 return
92 }
93 if username, password, ok := req.BasicAuth(); !ok || username != token.Username || password != token.Password {
94 code := token.StatusCode
95 if code == 0 {
96 code = http.StatusUnauthorized
97 }
98 if code == http.StatusUnauthorized {
99 w.Header().Add("WWW-Authenticate", fmt.Sprintf("basic realm=%s", accessDir))
100 }
101 http.Error(w, token.Message, code)
102 return
103 }
104
105 http.FileServer(fs).ServeHTTP(w, req)
106 })
107
108 return handler, nil
109 }
110
View as plain text