147 lines
3.3 KiB
Go
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
|
|
}
|