aboutsummaryrefslogtreecommitdiff
path: root/internal/bunny/api_upload.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/bunny/api_upload.go')
-rw-r--r--internal/bunny/api_upload.go50
1 files changed, 50 insertions, 0 deletions
diff --git a/internal/bunny/api_upload.go b/internal/bunny/api_upload.go
new file mode 100644
index 0000000..f2af534
--- /dev/null
+++ b/internal/bunny/api_upload.go
@@ -0,0 +1,50 @@
+package bunny
+
+import (
+ "bytes"
+ "context"
+ "crypto/sha256"
+ "fmt"
+ "io"
+ "net/http"
+ "unsafe"
+)
+
+// Upload performs a raw PUT with AccessKey and Checksum headers, applying the
+// retry policy: a 429 with Retry-After is retried up to maxRetries times,
+// honoring the header; every other non-2xx (429 without Retry-After, 5xx,
+// other 4xx) and any network error fails. The Checksum header is the
+// hex-encoded SHA256 of body, computed here so callers need not know Bunny's
+// integrity scheme.
+func (api *API) Upload(ctx context.Context, path string, body []byte) error {
+ sum := sha256.Sum256(body)
+ checksum := upperHexEncode(sum[:])
+
+ // bytes.NewReader lets net/http set req.GetBody automatically, so do can
+ // replay the body on each retry attempt.
+ req, err := http.NewRequestWithContext(ctx, http.MethodPut, api.url(path), bytes.NewReader(body))
+ if err != nil {
+ return fmt.Errorf("build request: %w", err)
+ }
+ req.Header.Set("Checksum", checksum)
+ req.ContentLength = int64(len(body))
+
+ resp, err := api.do(ctx, req)
+ if err != nil {
+ return err
+ }
+ io.Copy(io.Discard, resp.Body)
+ resp.Body.Close()
+ return nil
+}
+
+func upperHexEncode(src []byte) string {
+ const alphabet = "0123456789ABCDEF"
+
+ dst := make([]byte, len(src)*2)
+ for i, b := range src {
+ dst[i*2] = alphabet[b>>4]
+ dst[i*2+1] = alphabet[b&0x0f]
+ }
+ return unsafe.String(unsafe.SliceData(dst), len(dst))
+}