Put takes the body's exact size and always sends it as Content-Length; refuse a link under one second (issue 1592)
mesh/merge-gate head efd3d224e131 merged into trunk c9f978d3308b — pass, with a note: builds mesh-sdk (+41 dependent(s)) → ace, g14, novox, shanks; no bus …
mesh/repo-check head efd3d224e131 merged into trunk c9f978d3308b — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (typescript)…
mesh/delivery delivered
mesh/merge-gate head efd3d224e131 merged into trunk c9f978d3308b — pass, with a note: builds mesh-sdk (+41 dependent(s)) → ace, g14, novox, shanks; no bus …
mesh/repo-check head efd3d224e131 merged into trunk c9f978d3308b — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (typescript)…
mesh/delivery delivered
This commit is contained in:
+27
-45
@@ -47,7 +47,7 @@ type Request struct {
|
||||
SecretKey string
|
||||
// Region defaults to us-east-1.
|
||||
Region string
|
||||
// Expires is how long the link lives: more than zero, at most ten minutes.
|
||||
// Expires is how long the link lives: at least one second, at most ten minutes.
|
||||
Expires time.Duration
|
||||
// Now is the signing time; the clock's when zero.
|
||||
Now time.Time
|
||||
@@ -58,8 +58,8 @@ func Presign(r Request) (string, error) {
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodPut {
|
||||
return "", fmt.Errorf("objectlink: method %q: a link is for GET or PUT (ADR 0468 §3)", r.Method)
|
||||
}
|
||||
if r.Expires <= 0 || r.Expires > MaxExpires {
|
||||
return "", fmt.Errorf("objectlink: a link lives more than 0 and at most %s, not %s (ADR 0468 §3)", MaxExpires, r.Expires)
|
||||
if r.Expires < time.Second || r.Expires > MaxExpires {
|
||||
return "", fmt.Errorf("objectlink: a link lives at least 1s and at most %s, not %s (ADR 0468 §3)", MaxExpires, r.Expires)
|
||||
}
|
||||
if r.Bucket == "" || r.Key == "" || r.AccessKey == "" || r.SecretKey == "" {
|
||||
return "", errors.New("objectlink: a bucket, a key and a credential are needed to sign a link")
|
||||
@@ -171,66 +171,48 @@ func tooBig(limit int64) error {
|
||||
return fmt.Errorf("objectlink: the document is over %d bytes (ADR 0468 §3)", limit)
|
||||
}
|
||||
|
||||
// limited counts and hashes what passes and fails the read that would pass the limit.
|
||||
type limited struct {
|
||||
r io.Reader
|
||||
limit int64
|
||||
n int64
|
||||
hash interface{ Write([]byte) (int, error) }
|
||||
// exact counts and hashes what passes, and fails a stream that is longer than declared, or ends before it.
|
||||
type exact struct {
|
||||
r io.Reader
|
||||
size int64
|
||||
n int64
|
||||
hash interface{ Write([]byte) (int, error) }
|
||||
}
|
||||
|
||||
func (l *limited) Read(p []byte) (int, error) {
|
||||
func (l *exact) Read(p []byte) (int, error) {
|
||||
n, err := l.r.Read(p)
|
||||
l.n += int64(n)
|
||||
if l.n > l.limit {
|
||||
return 0, tooBig(l.limit)
|
||||
if l.n > l.size {
|
||||
return 0, fmt.Errorf("objectlink: the body is longer than the declared %d bytes", l.size)
|
||||
}
|
||||
l.hash.Write(p[:n])
|
||||
if err == io.EOF && l.n < l.size {
|
||||
return n, fmt.Errorf("objectlink: the body ended at %d bytes, short of the declared %d", l.n, l.size)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// sizeOf is the body's length where it is known without reading it, else -1.
|
||||
func sizeOf(body io.Reader) int64 {
|
||||
switch b := body.(type) {
|
||||
case interface{ Len() int }:
|
||||
return int64(b.Len())
|
||||
case io.Seeker:
|
||||
cur, err := b.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
end, err := b.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
if _, err := b.Seek(cur, io.SeekStart); err != nil {
|
||||
return -1
|
||||
}
|
||||
return end - cur
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Put streams body to a link made for PUT. It never holds the body in memory, and refuses more than limit bytes
|
||||
// (MaxDocument when limit is not above zero): a length known beforehand is refused before anything is sent, and one
|
||||
// that is not is refused when the stream passes the limit. A body whose length is known (a Len method, or a Seeker) is
|
||||
// sent with it, as stores want; any other is sent chunked.
|
||||
func Put(ctx context.Context, client *http.Client, link string, body io.Reader, limit int64) (Result, error) {
|
||||
// Put streams body to a link made for PUT, never holding it in memory. The caller gives the body's exact size, which
|
||||
// is always sent as the Content-Length (a store answers 411 to a PUT without one). A negative size is refused, and so
|
||||
// is one over limit (MaxDocument when limit is not above zero, ADR 0468 §3), both before any request. A body that
|
||||
// yields more or fewer bytes than size fails the request, so a PUT whose body does not end as declared stores nothing.
|
||||
func Put(ctx context.Context, client *http.Client, link string, body io.Reader, size, limit int64) (Result, error) {
|
||||
if limit <= 0 {
|
||||
limit = MaxDocument
|
||||
}
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
size := sizeOf(body)
|
||||
if size < 0 {
|
||||
return Result{}, errors.New("objectlink: put needs the size of the body; a negative or unknown size is refused")
|
||||
}
|
||||
if size > limit {
|
||||
return Result{}, tooBig(limit)
|
||||
}
|
||||
hash := sha256.New()
|
||||
// Cancelled on the way out, so a refused stream tears its connection down and the store does not wait for the rest.
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
counted := &limited{r: body, limit: limit, hash: hash}
|
||||
counted := &exact{r: body, size: size, hash: hash}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, link, io.NopCloser(counted))
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
@@ -242,15 +224,15 @@ func Put(ctx context.Context, client *http.Client, link string, body io.Reader,
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
cancel()
|
||||
if strings.Contains(err.Error(), tooBig(limit).Error()) {
|
||||
return Result{}, tooBig(limit)
|
||||
}
|
||||
return Result{}, fmt.Errorf("objectlink: put: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
return Result{}, storeError(resp)
|
||||
}
|
||||
if counted.n != size {
|
||||
return Result{}, fmt.Errorf("objectlink: put: sent %d bytes of the declared %d", counted.n, size)
|
||||
}
|
||||
return Result{Size: counted.n, SHA256: hex.EncodeToString(hash.Sum(nil))}, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user