fix: Buffer simple uploads for R2 Content-Length requirement
R2 PutObject requires Content-Length header. Buffer files <100MB into memory before uploading so the SDK can set the header. Also fix HTML pattern regex escaping for slug input. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e938f6d9a9
commit
4c22439574
|
|
@ -1,6 +1,7 @@
|
|||
package r2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -48,11 +49,19 @@ func (c *Client) Upload(ctx context.Context, key, contentType string, size int64
|
|||
}
|
||||
|
||||
func (c *Client) uploadSimple(ctx context.Context, key, contentType string, body io.Reader) error {
|
||||
_, err := c.s3.PutObject(ctx, &s3.PutObjectInput{
|
||||
Bucket: &c.bucket,
|
||||
Key: &key,
|
||||
Body: body,
|
||||
ContentType: &contentType,
|
||||
// R2 requires Content-Length; buffer the body so we can set it.
|
||||
// Only used for files < 100MB, so memory usage is bounded.
|
||||
data, err := io.ReadAll(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read body: %w", err)
|
||||
}
|
||||
size := int64(len(data))
|
||||
_, err = c.s3.PutObject(ctx, &s3.PutObjectInput{
|
||||
Bucket: &c.bucket,
|
||||
Key: &key,
|
||||
Body: bytes.NewReader(data),
|
||||
ContentType: &contentType,
|
||||
ContentLength: &size,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
|
@ -78,12 +87,16 @@ func (c *Client) uploadMultipart(ctx context.Context, key, contentType string, b
|
|||
break
|
||||
}
|
||||
|
||||
partData := make([]byte, n)
|
||||
copy(partData, buf[:n])
|
||||
partLen := int64(n)
|
||||
upload, err := c.s3.UploadPart(ctx, &s3.UploadPartInput{
|
||||
Bucket: &c.bucket,
|
||||
Key: &key,
|
||||
UploadId: uploadID,
|
||||
PartNumber: &partNum,
|
||||
Body: io.NopCloser(io.LimitReader(bytesReader(buf[:n]), int64(n))),
|
||||
Bucket: &c.bucket,
|
||||
Key: &key,
|
||||
UploadId: uploadID,
|
||||
PartNumber: &partNum,
|
||||
Body: bytes.NewReader(partData),
|
||||
ContentLength: &partLen,
|
||||
})
|
||||
if err != nil {
|
||||
c.s3.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{
|
||||
|
|
@ -147,24 +160,3 @@ func (c *Client) Delete(ctx context.Context, key string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// bytesReader wraps a byte slice as an io.Reader.
|
||||
type bytesReaderImpl struct {
|
||||
data []byte
|
||||
pos int
|
||||
}
|
||||
|
||||
func bytesReader(b []byte) io.Reader {
|
||||
// Make a copy so the buffer can be reused
|
||||
cp := make([]byte, len(b))
|
||||
copy(cp, b)
|
||||
return &bytesReaderImpl{data: cp}
|
||||
}
|
||||
|
||||
func (r *bytesReaderImpl) Read(p []byte) (int, error) {
|
||||
if r.pos >= len(r.data) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n := copy(p, r.data[r.pos:])
|
||||
r.pos += n
|
||||
return n, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
<label for="slug">Custom URL</label>
|
||||
<div class="slug-input">
|
||||
<span class="slug-prefix">/f/</span>
|
||||
<input type="text" id="slug" placeholder="my-file" pattern="[a-zA-Z0-9][a-zA-Z0-9._-]*" maxlength="64">
|
||||
<input type="text" id="slug" placeholder="my-file" pattern="[a-zA-Z0-9][a-zA-Z0-9.\-_]*" maxlength="64">
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-group">
|
||||
|
|
|
|||
Loading…
Reference in New Issue