whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package sumdb implements the HTTP protocols for serving or accessing a module checksum database.
|
||||
package sumdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/internal/lazyregexp"
|
||||
"golang.org/x/mod/module"
|
||||
"golang.org/x/mod/sumdb/tlog"
|
||||
)
|
||||
|
||||
// A ServerOps provides the external operations
|
||||
// (underlying database access and so on) needed by the [Server].
|
||||
type ServerOps interface {
|
||||
// Signed returns the signed hash of the latest tree.
|
||||
Signed(ctx context.Context) ([]byte, error)
|
||||
|
||||
// ReadRecords returns the content for the n records id through id+n-1.
|
||||
ReadRecords(ctx context.Context, id, n int64) ([][]byte, error)
|
||||
|
||||
// Lookup looks up a record for the given module,
|
||||
// returning the record ID.
|
||||
Lookup(ctx context.Context, m module.Version) (int64, error)
|
||||
|
||||
// ReadTileData reads the content of tile t.
|
||||
// It is only invoked for hash tiles (t.L ≥ 0).
|
||||
ReadTileData(ctx context.Context, t tlog.Tile) ([]byte, error)
|
||||
}
|
||||
|
||||
// A Server is the checksum database HTTP server,
|
||||
// which implements http.Handler and should be invoked
|
||||
// to serve the paths listed in [ServerPaths].
|
||||
type Server struct {
|
||||
ops ServerOps
|
||||
}
|
||||
|
||||
// NewServer returns a new Server using the given operations.
|
||||
func NewServer(ops ServerOps) *Server {
|
||||
return &Server{ops: ops}
|
||||
}
|
||||
|
||||
// ServerPaths are the URL paths the Server can (and should) serve.
|
||||
//
|
||||
// Typically a server will do:
|
||||
//
|
||||
// srv := sumdb.NewServer(ops)
|
||||
// for _, path := range sumdb.ServerPaths {
|
||||
// http.Handle(path, srv)
|
||||
// }
|
||||
var ServerPaths = []string{
|
||||
"/lookup/",
|
||||
"/latest",
|
||||
"/tile/",
|
||||
}
|
||||
|
||||
var modVerRE = lazyregexp.New(`^[^@]+@v[0-9]+\.[0-9]+\.[0-9]+(-[^@]*)?(\+incompatible)?$`)
|
||||
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
switch {
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
|
||||
case strings.HasPrefix(r.URL.Path, "/lookup/"):
|
||||
mod := strings.TrimPrefix(r.URL.Path, "/lookup/")
|
||||
if !modVerRE.MatchString(mod) {
|
||||
http.Error(w, "invalid module@version syntax", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
i := strings.Index(mod, "@")
|
||||
escPath, escVers := mod[:i], mod[i+1:]
|
||||
path, err := module.UnescapePath(escPath)
|
||||
if err != nil {
|
||||
reportError(w, err)
|
||||
return
|
||||
}
|
||||
vers, err := module.UnescapeVersion(escVers)
|
||||
if err != nil {
|
||||
reportError(w, err)
|
||||
return
|
||||
}
|
||||
id, err := s.ops.Lookup(ctx, module.Version{Path: path, Version: vers})
|
||||
if err != nil {
|
||||
reportError(w, err)
|
||||
return
|
||||
}
|
||||
records, err := s.ops.ReadRecords(ctx, id, 1)
|
||||
if err != nil {
|
||||
// This should never happen - the lookup says the record exists.
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if len(records) != 1 {
|
||||
http.Error(w, "invalid record count returned by ReadRecords", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
msg, err := tlog.FormatRecord(id, records[0])
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
signed, err := s.ops.Signed(ctx)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
|
||||
w.Write(msg)
|
||||
w.Write(signed)
|
||||
|
||||
case r.URL.Path == "/latest":
|
||||
data, err := s.ops.Signed(ctx)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
|
||||
w.Write(data)
|
||||
|
||||
case strings.HasPrefix(r.URL.Path, "/tile/"):
|
||||
t, err := tlog.ParseTilePath(r.URL.Path[1:])
|
||||
if err != nil {
|
||||
http.Error(w, "invalid tile syntax", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if t.L == -1 {
|
||||
// Record data.
|
||||
start := t.N << uint(t.H)
|
||||
records, err := s.ops.ReadRecords(ctx, start, int64(t.W))
|
||||
if err != nil {
|
||||
reportError(w, err)
|
||||
return
|
||||
}
|
||||
if len(records) != t.W {
|
||||
http.Error(w, "invalid record count returned by ReadRecords", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var data []byte
|
||||
for i, text := range records {
|
||||
msg, err := tlog.FormatRecord(start+int64(i), text)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data = append(data, msg...)
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
|
||||
w.Write(data)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := s.ops.ReadTileData(ctx, t)
|
||||
if err != nil {
|
||||
reportError(w, err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Write(data)
|
||||
}
|
||||
}
|
||||
|
||||
// reportError reports err to w.
|
||||
// If it's a not-found, the reported error is 404.
|
||||
// Otherwise it is an internal server error.
|
||||
// The caller must only call reportError in contexts where
|
||||
// a not-found err should be reported as 404.
|
||||
func reportError(w http.ResponseWriter, err error) {
|
||||
if os.IsNotExist(err) {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
Reference in New Issue
Block a user