Files
opentimestamps/remote_calendar.go
2023-09-19 21:18:40 -03:00

147 lines
3.3 KiB
Go

package opentimestamps
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"net/http"
"net/http/httputil"
"strings"
"github.com/sirupsen/logrus"
)
const userAgent = "go-opentimestamps"
const dumpResponse = false
type RemoteCalendar struct {
baseURL string
client *http.Client
log *logrus.Logger
}
func NewRemoteCalendar(baseURL string) (*RemoteCalendar, error) {
// FIXME remove this
if baseURL == "localhost" {
baseURL = "http://localhost:14788"
}
// TODO validate url
if !strings.HasSuffix(baseURL, "/") {
baseURL += "/"
}
return &RemoteCalendar{
baseURL,
http.DefaultClient,
logrus.New(),
}, nil
}
// Check response status, return informational error message if
// status is not `200 OK`.
func checkStatusOK(resp *http.Response) error {
if resp.StatusCode == http.StatusOK {
return nil
}
errMsg := fmt.Sprintf("unexpected response: %q", resp.Status)
if resp.Body == nil {
return fmt.Errorf("%s (body=nil)", errMsg)
}
defer resp.Body.Close()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("%s (bodyErr=%v)", errMsg, err)
} else {
return fmt.Errorf("%s (body=%q)", errMsg, bodyBytes)
}
}
func (c *RemoteCalendar) do(r *http.Request) (*http.Response, error) {
r.Header.Add("Accept", "application/vnd.opentimestamps.v1")
r.Header.Add("User-Agent", userAgent)
c.log.Debugf("> %s %s", r.Method, r.URL)
resp, err := c.client.Do(r)
if err != nil {
c.log.Errorf("> %s %s error: %v", r.Method, r.URL, err)
return resp, err
}
c.log.Debugf("< %s %s - %v", r.Method, r.URL, resp.Status)
if dumpResponse {
bytes, err := httputil.DumpResponse(resp, true)
if err == nil {
c.log.Debugf("response dump:%s ", bytes)
}
}
return resp, err
}
func (c *RemoteCalendar) url(path string) string {
return c.baseURL + path
}
func (c *RemoteCalendar) Submit(digest [32]byte) (*Timestamp, error) {
body := bytes.NewBuffer(digest[:])
req, err := http.NewRequest("POST", c.url("digest"), body)
if err != nil {
return nil, err
}
resp, err := c.do(req)
if err != nil {
return nil, err
}
if resp.Body != nil {
defer resp.Body.Close()
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("expected 200, got %v", resp.Status)
}
return NewTimestampFromReader(resp.Body, digest[:])
}
func (c *RemoteCalendar) GetTimestamp(commitment []byte) (*Timestamp, error) {
url := c.url("timestamp/" + hex.EncodeToString(commitment))
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
resp, err := c.do(req)
if err != nil {
return nil, err
}
if err := checkStatusOK(resp); err != nil {
return nil, err
}
if resp.Body != nil {
defer resp.Body.Close()
}
return NewTimestampFromReader(resp.Body, commitment)
}
type PendingTimestamp struct {
Timestamp *Timestamp
PendingAttestation *pendingAttestation
}
func (p PendingTimestamp) Upgrade() (*Timestamp, error) {
cal, err := NewRemoteCalendar(p.PendingAttestation.uri)
if err != nil {
return nil, err
}
return cal.GetTimestamp(p.Timestamp.Message)
}
func PendingTimestamps(ts *Timestamp) (res []PendingTimestamp) {
ts.Walk(func(ts *Timestamp) {
for _, att := range ts.Attestations {
p, ok := att.(*pendingAttestation)
if !ok {
continue
}
attCopy := *p
res = append(res, PendingTimestamp{ts, &attCopy})
}
})
return
}