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  //
    58  var ServerPaths = []string{
    59  	"/lookup/",
    60  	"/latest",
    61  	"/tile/",
    62  }
    63  
    64  var modVerRE = lazyregexp.New(`^[^@]+@v[0-9]+\.[0-9]+\.[0-9]+(-[^@]*)?(\+incompatible)?$`)
    65  
    66  func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    67  	ctx := r.Context()
    68  
    69  	switch {
    70  	default:
    71  		http.NotFound(w, r)
    72  
    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)
    77  			return
    78  		}
    79  		i := strings.Index(mod, "@")
    80  		escPath, escVers := mod[:i], mod[i+1:]
    81  		path, err := module.UnescapePath(escPath)
    82  		if err != nil {
    83  			reportError(w, err)
    84  			return
    85  		}
    86  		vers, err := module.UnescapeVersion(escVers)
    87  		if err != nil {
    88  			reportError(w, err)
    89  			return
    90  		}
    91  		id, err := s.ops.Lookup(ctx, module.Version{Path: path, Version: vers})
    92  		if err != nil {
    93  			reportError(w, err)
    94  			return
    95  		}
    96  		records, err := s.ops.ReadRecords(ctx, id, 1)
    97  		if err != nil {
    98  			// This should never happen - the lookup says the record exists.
    99  			http.Error(w, err.Error(), http.StatusInternalServerError)
   100  			return
   101  		}
   102  		if len(records) != 1 {
   103  			http.Error(w, "invalid record count returned by ReadRecords", http.StatusInternalServerError)
   104  			return
   105  		}
   106  		msg, err := tlog.FormatRecord(id, records[0])
   107  		if err != nil {
   108  			http.Error(w, err.Error(), http.StatusInternalServerError)
   109  			return
   110  		}
   111  		signed, err := s.ops.Signed(ctx)
   112  		if err != nil {
   113  			http.Error(w, err.Error(), http.StatusInternalServerError)
   114  			return
   115  		}
   116  		w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
   117  		w.Write(msg)
   118  		w.Write(signed)
   119  
   120  	case r.URL.Path == "/latest":
   121  		data, err := s.ops.Signed(ctx)
   122  		if err != nil {
   123  			http.Error(w, err.Error(), http.StatusInternalServerError)
   124  			return
   125  		}
   126  		w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
   127  		w.Write(data)
   128  
   129  	case strings.HasPrefix(r.URL.Path, "/tile/"):
   130  		t, err := tlog.ParseTilePath(r.URL.Path[1:])
   131  		if err != nil {
   132  			http.Error(w, "invalid tile syntax", http.StatusBadRequest)
   133  			return
   134  		}
   135  		if t.L == -1 {
   136  			// Record data.
   137  			start := t.N << uint(t.H)
   138  			records, err := s.ops.ReadRecords(ctx, start, int64(t.W))
   139  			if err != nil {
   140  				reportError(w, err)
   141  				return
   142  			}
   143  			if len(records) != t.W {
   144  				http.Error(w, "invalid record count returned by ReadRecords", http.StatusInternalServerError)
   145  				return
   146  			}
   147  			var data []byte
   148  			for i, text := range records {
   149  				msg, err := tlog.FormatRecord(start+int64(i), text)
   150  				if err != nil {
   151  					http.Error(w, err.Error(), http.StatusInternalServerError)
   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