package instance import ( "context" "fmt" "net/http" "time" "git.sigsum.org/sigsum-go/pkg/log" "git.sigsum.org/sigsum-go/pkg/types" ) // Handler implements the http.Handler interface, and contains a reference // to a sigsum server instance as well as a function that uses it. type Handler struct { Instance *Instance Endpoint types.Endpoint Method string Handler func(context.Context, *Instance, http.ResponseWriter, *http.Request) (int, error) } // Path returns a path that should be configured for this handler func (h Handler) Path() string { if len(h.Instance.Prefix) == 0 { return h.Endpoint.Path("", "sigsum", "v0") } return h.Endpoint.Path("", h.Instance.Prefix, "sigsum", "v0") } // ServeHTTP is part of the http.Handler interface func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { start := time.Now() code := 0 defer func() { end := time.Now().Sub(start).Seconds() sc := fmt.Sprintf("%d", code) rspcnt.Inc(h.Instance.LogID, string(h.Endpoint), sc) latency.Observe(end, h.Instance.LogID, string(h.Endpoint), sc) }() reqcnt.Inc(h.Instance.LogID, string(h.Endpoint)) code = h.verifyMethod(w, r) if code != 0 { return } code = h.handle(w, r) } // verifyMethod checks that an appropriate HTTP method is used. Error handling // is based on RFC 7231, see Sections 6.5.5 (Status 405) and 6.5.1 (Status 400). func (h *Handler) verifyMethod(w http.ResponseWriter, r *http.Request) int { if h.Method == r.Method { return 0 } code := http.StatusBadRequest if ok := h.Instance.checkHTTPMethod(r.Method); ok { w.Header().Set("Allow", h.Method) code = http.StatusMethodNotAllowed } http.Error(w, fmt.Sprintf("error=%s", http.StatusText(code)), code) return code } // handle handles an HTTP request for which the HTTP method is already verified func (h Handler) handle(w http.ResponseWriter, r *http.Request) int { deadline := time.Now().Add(h.Instance.Deadline) ctx, cancel := context.WithDeadline(r.Context(), deadline) defer cancel() code, err := h.Handler(ctx, h.Instance, w, r) if err != nil { log.Debug("%s/%s: %v", h.Instance.Prefix, h.Endpoint, err) http.Error(w, fmt.Sprintf("error=%s", err.Error()), code) } return code } func addLeaf(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { log.Debug("handling add-leaf request") req, err := i.leafRequestFromHTTP(ctx, r) if err != nil { return http.StatusBadRequest, err } if err := i.Client.AddLeaf(ctx, req); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil } func addCosignature(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { log.Debug("handling add-cosignature request") req, err := i.cosignatureRequestFromHTTP(r) if err != nil { return http.StatusBadRequest, err } vk := i.Witnesses[req.KeyHash] if err := i.Stateman.AddCosignature(ctx, &vk, &req.Cosignature); err != nil { return http.StatusBadRequest, err } return http.StatusOK, nil } func getTreeHeadToCosign(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { log.Debug("handling get-tree-head-to-cosign request") sth, err := i.Stateman.ToCosignTreeHead(ctx) if err != nil { return http.StatusInternalServerError, err } if err := sth.ToASCII(w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil } func getTreeHeadCosigned(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { log.Debug("handling get-tree-head-cosigned request") cth, err := i.Stateman.CosignedTreeHead(ctx) if err != nil { return http.StatusInternalServerError, err } if err := cth.ToASCII(w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil } func getConsistencyProof(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { log.Debug("handling get-consistency-proof request") req, err := i.consistencyProofRequestFromHTTP(r) if err != nil { return http.StatusBadRequest, err } // XXX: check tree size of latest thing we signed? proof, err := i.Client.GetConsistencyProof(ctx, req) if err != nil { return http.StatusInternalServerError, err } if err := proof.ToASCII(w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil } func getInclusionProof(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { log.Debug("handling get-inclusion-proof request") req, err := i.inclusionProofRequestFromHTTP(r) if err != nil { return http.StatusBadRequest, err } // XXX: check tree size of latest thing we signed? proof, err := i.Client.GetInclusionProof(ctx, req) if err != nil { return http.StatusInternalServerError, err } if err := proof.ToASCII(w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil } func getLeaves(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { log.Debug("handling get-leaves request") req, err := i.leavesRequestFromHTTP(r) if err != nil { return http.StatusBadRequest, err } // XXX: check tree size of latest thing we signed? leaves, err := i.Client.GetLeaves(ctx, req) if err != nil { return http.StatusInternalServerError, err } for _, leaf := range *leaves { if err := leaf.ToASCII(w); err != nil { return http.StatusInternalServerError, err } } return http.StatusOK, nil }