1 // Copyright 2019 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package sumdb implements the HTTP protocols for serving or accessing a module checksum database.
14 "golang.org/x/mod/internal/lazyregexp"
15 "golang.org/x/mod/module"
16 "golang.org/x/mod/sumdb/tlog"
19 // A ServerOps provides the external operations
20 // (underlying database access and so on) needed by the Server.
21 type ServerOps interface {
22 // Signed returns the signed hash of the latest tree.
23 Signed(ctx context.Context) ([]byte, error)
25 // ReadRecords returns the content for the n records id through id+n-1.
26 ReadRecords(ctx context.Context, id, n int64) ([][]byte, error)
28 // Lookup looks up a record for the given module,
29 // returning the record ID.
30 Lookup(ctx context.Context, m module.Version) (int64, error)
32 // ReadTileData reads the content of tile t.
33 // It is only invoked for hash tiles (t.L ≥ 0).
34 ReadTileData(ctx context.Context, t tlog.Tile) ([]byte, error)
37 // A Server is the checksum database HTTP server,
38 // which implements http.Handler and should be invoked
39 // to serve the paths listed in ServerPaths.
44 // NewServer returns a new Server using the given operations.
45 func NewServer(ops ServerOps) *Server {
46 return &Server{ops: ops}
49 // ServerPaths are the URL paths the Server can (and should) serve.
51 // Typically a server will do:
53 // srv := sumdb.NewServer(ops)
54 // for _, path := range sumdb.ServerPaths {
55 // http.Handle(path, srv)
58 var ServerPaths = []string{
64 var modVerRE = lazyregexp.New(`^[^@]+@v[0-9]+\.[0-9]+\.[0-9]+(-[^@]*)?(\+incompatible)?$`)
66 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
73 case strings.HasPrefix(r.URL.Path, "/lookup/"):
74 mod := strings.TrimPrefix(r.URL.Path, "/lookup/")
75 if !modVerRE.MatchString(mod) {
76 http.Error(w, "invalid module@version syntax", http.StatusBadRequest)
79 i := strings.Index(mod, "@")
80 escPath, escVers := mod[:i], mod[i+1:]
81 path, err := module.UnescapePath(escPath)
86 vers, err := module.UnescapeVersion(escVers)
91 id, err := s.ops.Lookup(ctx, module.Version{Path: path, Version: vers})
96 records, err := s.ops.ReadRecords(ctx, id, 1)
98 // This should never happen - the lookup says the record exists.
99 http.Error(w, err.Error(), http.StatusInternalServerError)
102 if len(records) != 1 {
103 http.Error(w, "invalid record count returned by ReadRecords", http.StatusInternalServerError)
106 msg, err := tlog.FormatRecord(id, records[0])
108 http.Error(w, err.Error(), http.StatusInternalServerError)
111 signed, err := s.ops.Signed(ctx)
113 http.Error(w, err.Error(), http.StatusInternalServerError)
116 w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
120 case r.URL.Path == "/latest":
121 data, err := s.ops.Signed(ctx)
123 http.Error(w, err.Error(), http.StatusInternalServerError)
126 w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
129 case strings.HasPrefix(r.URL.Path, "/tile/"):
130 t, err := tlog.ParseTilePath(r.URL.Path[1:])
132 http.Error(w, "invalid tile syntax", http.StatusBadRequest)
137 start := t.N << uint(t.H)
138 records, err := s.ops.ReadRecords(ctx, start, int64(t.W))
143 if len(records) != t.W {
144 http.Error(w, "invalid record count returned by ReadRecords", http.StatusInternalServerError)
148 for i, text := range records {
149 msg, err := tlog.FormatRecord(start+int64(i), text)
151 http.Error(w, err.Error(), http.StatusInternalServerError)
153 data = append(data, msg...)
155 w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
160 data, err := s.ops.ReadTileData(ctx, t)
165 w.Header().Set("Content-Type", "application/octet-stream")
170 // reportError reports err to w.
171 // If it's a not-found, the reported error is 404.
172 // Otherwise it is an internal server error.
173 // The caller must only call reportError in contexts where
174 // a not-found err should be reported as 404.
175 func reportError(w http.ResponseWriter, err error) {
176 if os.IsNotExist(err) {
177 http.Error(w, err.Error(), http.StatusNotFound)
180 http.Error(w, err.Error(), http.StatusInternalServerError)