Source file src/cmd/vendor/golang.org/x/mod/sumdb/server.go

     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.
     4  
     5  // Package sumdb implements the HTTP protocols for serving or accessing a module checksum database.
     6  package sumdb
     7  
     8  import (
     9  	"context"
    10  	"net/http"
    11  	"os"
    12  	"strings"
    13  
    14  	"golang.org/x/mod/internal/lazyregexp"
    15  	"golang.org/x/mod/module"
    16  	"golang.org/x/mod/sumdb/tlog"
    17  )
    18  
    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)
    24  
    25  	// ReadRecords returns the content for the n records id through id+n-1.
    26  	ReadRecords(ctx context.Context, id, n int64) ([][]byte, error)
    27  
    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)
    31  
    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)
    35  }
    36  
    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].
    40  type Server struct {
    41  	ops ServerOps
    42  }
    43  
    44  // NewServer returns a new Server using the given operations.
    45  func NewServer(ops ServerOps) *Server {
    46  	return &Server{ops: ops}
    47  }
    48  
    49  // ServerPaths are the URL paths the Server can (and should) serve.
    50  //
    51  // Typically a server will do:
    52  //
    53  //	srv := sumdb.NewServer(ops)
    54  //	for _, path := range sumdb.ServerPaths {
    55  //		http.Handle(path, srv)
    56  //	}
    57  var ServerPaths = []string{
    58  	"/lookup/",
    59  	"/latest",
    60  	"/tile/",
    61  }
    62  
    63  var modVerRE = lazyregexp.New(`^[^@]+@v[0-9]+\.[0-9]+\.[0-9]+(-[^@]*)?(\+incompatible)?$`)
    64  
    65  func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    66  	ctx := r.Context()
    67  
    68  	switch {
    69  	default:
    70  		http.NotFound(w, r)
    71  
    72  	case strings.HasPrefix(r.URL.Path, "/lookup/"):
    73  		mod := strings.TrimPrefix(r.URL.Path, "/lookup/")
    74  		if !modVerRE.MatchString(mod) {
    75  			http.Error(w, "invalid module@version syntax", http.StatusBadRequest)
    76  			return
    77  		}
    78  		i := strings.Index(mod, "@")
    79  		escPath, escVers := mod[:i], mod[i+1:]
    80  		path, err := module.UnescapePath(escPath)
    81  		if err != nil {
    82  			reportError(w, err)
    83  			return
    84  		}
    85  		vers, err := module.UnescapeVersion(escVers)
    86  		if err != nil {
    87  			reportError(w, err)
    88  			return
    89  		}
    90  		id, err := s.ops.Lookup(ctx, module.Version{Path: path, Version: vers})
    91  		if err != nil {
    92  			reportError(w, err)
    93  			return
    94  		}
    95  		records, err := s.ops.ReadRecords(ctx, id, 1)
    96  		if err != nil {
    97  			// This should never happen - the lookup says the record exists.
    98  			http.Error(w, err.Error(), http.StatusInternalServerError)
    99  			return
   100  		}
   101  		if len(records) != 1 {
   102  			http.Error(w, "invalid record count returned by ReadRecords", http.StatusInternalServerError)
   103  			return
   104  		}
   105  		msg, err := tlog.FormatRecord(id, records[0])
   106  		if err != nil {
   107  			http.Error(w, err.Error(), http.StatusInternalServerError)
   108  			return
   109  		}
   110  		signed, err := s.ops.Signed(ctx)
   111  		if err != nil {
   112  			http.Error(w, err.Error(), http.StatusInternalServerError)
   113  			return
   114  		}
   115  		w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
   116  		w.Write(msg)
   117  		w.Write(signed)
   118  
   119  	case r.URL.Path == "/latest":
   120  		data, err := s.ops.Signed(ctx)
   121  		if err != nil {
   122  			http.Error(w, err.Error(), http.StatusInternalServerError)
   123  			return
   124  		}
   125  		w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
   126  		w.Write(data)
   127  
   128  	case strings.HasPrefix(r.URL.Path, "/tile/"):
   129  		t, err := tlog.ParseTilePath(r.URL.Path[1:])
   130  		if err != nil {
   131  			http.Error(w, "invalid tile syntax", http.StatusBadRequest)
   132  			return
   133  		}
   134  		if t.L == -1 {
   135  			// Record data.
   136  			start := t.N << uint(t.H)
   137  			records, err := s.ops.ReadRecords(ctx, start, int64(t.W))
   138  			if err != nil {
   139  				reportError(w, err)
   140  				return
   141  			}
   142  			if len(records) != t.W {
   143  				http.Error(w, "invalid record count returned by ReadRecords", http.StatusInternalServerError)
   144  				return
   145  			}
   146  			var data []byte
   147  			for i, text := range records {
   148  				msg, err := tlog.FormatRecord(start+int64(i), text)
   149  				if err != nil {
   150  					http.Error(w, err.Error(), http.StatusInternalServerError)
   151  					return
   152  				}
   153  				data = append(data, msg...)
   154  			}
   155  			w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
   156  			w.Write(data)
   157  			return
   158  		}
   159  
   160  		data, err := s.ops.ReadTileData(ctx, t)
   161  		if err != nil {
   162  			reportError(w, err)
   163  			return
   164  		}
   165  		w.Header().Set("Content-Type", "application/octet-stream")
   166  		w.Write(data)
   167  	}
   168  }
   169  
   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)
   178  		return
   179  	}
   180  	http.Error(w, err.Error(), http.StatusInternalServerError)
   181  }
   182  

View as plain text