@specific.dev/spectest 0.131.6 → 0.131.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/s3-runtime/README.md +16 -0
- package/dist/components/s3-runtime/main.go +5 -1
- package/dist/components/s3-runtime/main_test.go +282 -0
- package/dist/components/s3-runtime/multipart.patch +352 -0
- package/dist/components/s3-runtime/test.sh +12 -0
- package/dist/components/s3.js +2 -2
- package/package.json +1 -1
- package/src/components/s3-runtime/README.md +16 -0
- package/src/components/s3-runtime/main.go +5 -1
- package/src/components/s3-runtime/main_test.go +282 -0
- package/src/components/s3-runtime/multipart.patch +352 -0
- package/src/components/s3-runtime/test.sh +12 -0
- package/src/components/s3.ts +2 -2
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
--- a/vendor/github.com/johannesboyne/gofakes3/gofakes3.go
|
|
2
|
+
+++ b/vendor/github.com/johannesboyne/gofakes3/gofakes3.go
|
|
3
|
+
@@ -933,6 +933,14 @@
|
|
4
|
+
return err
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
+ if meta["X-Amz-Checksum-Algorithm"] == "SHA256" {
|
|
8
|
+
+ if kind := meta["X-Amz-Checksum-Type"]; kind != "" && kind != "COMPOSITE" {
|
|
9
|
+
+ return ErrInvalidArgument
|
|
10
|
+
+ }
|
|
11
|
+
+ meta["X-Amz-Checksum-Type"] = "COMPOSITE"
|
|
12
|
+
+ w.Header().Set("x-amz-checksum-algorithm", "SHA256")
|
|
13
|
+
+ w.Header().Set("x-amz-checksum-type", "COMPOSITE")
|
|
14
|
+
+ }
|
|
15
|
+
uploadID, err := g.uploader.CreateMultipartUpload(bucket, object, meta)
|
|
16
|
+
if err != nil {
|
|
17
|
+
return err
|
|
18
|
+
@@ -983,12 +991,20 @@
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
+ var checksum *sha256PartReader
|
|
23
|
+
+ if expected := r.Header.Get("x-amz-checksum-sha256"); expected != "" {
|
|
24
|
+
+ checksum = newSHA256PartReader(rdr, expected)
|
|
25
|
+
+ rdr = checksum
|
|
26
|
+
+ }
|
|
27
|
+
etag, err := g.uploader.UploadPart(bucket, object, uploadID, int(partNumber), r.ContentLength, rdr)
|
|
28
|
+
if err != nil {
|
|
29
|
+
return err
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
w.Header().Add("ETag", etag)
|
|
33
|
+
+ if checksum != nil {
|
|
34
|
+
+ w.Header().Set("x-amz-checksum-sha256", checksum.sum())
|
|
35
|
+
+ }
|
|
36
|
+
return nil
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@@ -1009,6 +1025,13 @@
|
|
40
|
+
return err
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
+ var err error
|
|
44
|
+
+ in.Conditions, err = parsePutConditions(r.Header)
|
|
45
|
+
+ if err != nil {
|
|
46
|
+
+ return err
|
|
47
|
+
+ }
|
|
48
|
+
+ in.ChecksumSHA256 = r.Header.Get("x-amz-checksum-sha256")
|
|
49
|
+
+ in.ChecksumType = r.Header.Get("x-amz-checksum-type")
|
|
50
|
+
versionID, etag, err := g.uploader.CompleteMultipartUpload(bucket, object, uploadID, &in)
|
|
51
|
+
if err != nil {
|
|
52
|
+
return err
|
|
53
|
+
@@ -1031,10 +1054,12 @@
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return g.xmlEncoder(w).Encode(&CompleteMultipartUploadResult{
|
|
57
|
+
- ETag: etag,
|
|
58
|
+
- Bucket: bucket,
|
|
59
|
+
- Key: object,
|
|
60
|
+
- Location: location,
|
|
61
|
+
+ ETag: etag,
|
|
62
|
+
+ Bucket: bucket,
|
|
63
|
+
+ Key: object,
|
|
64
|
+
+ Location: location,
|
|
65
|
+
+ ChecksumSHA256: in.ChecksumSHA256,
|
|
66
|
+
+ ChecksumType: in.ChecksumType,
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
--- a/vendor/github.com/johannesboyne/gofakes3/uploader.go
|
|
71
|
+
+++ b/vendor/github.com/johannesboyne/gofakes3/uploader.go
|
|
72
|
+
@@ -3,6 +3,8 @@
|
|
73
|
+
import (
|
|
74
|
+
"bytes"
|
|
75
|
+
"crypto/md5"
|
|
76
|
+
+ "crypto/sha256"
|
|
77
|
+
+ "encoding/base64"
|
|
78
|
+
"encoding/hex"
|
|
79
|
+
"fmt"
|
|
80
|
+
"io"
|
|
81
|
+
@@ -428,13 +430,26 @@
|
|
82
|
+
return "", "", ErrInvalidPart
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
- if !input.partsAreSorted() {
|
|
86
|
+
+ if len(input.Parts) == 0 || !input.partsAreSorted() {
|
|
87
|
+
return "", "", ErrInvalidPartOrder
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
var size int64
|
|
91
|
+
|
|
92
|
+
- for _, inPart := range input.Parts {
|
|
93
|
+
+ composite := sha256.New()
|
|
94
|
+
+ withSHA256 := mpu.Meta["X-Amz-Checksum-Algorithm"] == "SHA256"
|
|
95
|
+
+ if withSHA256 && input.ChecksumType != "" && input.ChecksumType != "COMPOSITE" {
|
|
96
|
+
+ return "", "", ErrBadDigest
|
|
97
|
+
+ }
|
|
98
|
+
+ previous := 0
|
|
99
|
+
+ for i, inPart := range input.Parts {
|
|
100
|
+
+ if inPart.PartNumber <= previous {
|
|
101
|
+
+ return "", "", ErrInvalidPartOrder
|
|
102
|
+
+ }
|
|
103
|
+
+ previous = inPart.PartNumber
|
|
104
|
+
+ if withSHA256 && inPart.PartNumber != i+1 {
|
|
105
|
+
+ return "", "", ErrInvalidPartOrder
|
|
106
|
+
+ }
|
|
107
|
+
if inPart.PartNumber >= mpuPartsLen || mpu.parts[inPart.PartNumber] == nil {
|
|
108
|
+
return "", "", ErrorMessagef(ErrInvalidPart, "unexpected part number %d in complete request", inPart.PartNumber)
|
|
109
|
+
}
|
|
110
|
+
@@ -444,6 +459,13 @@
|
|
111
|
+
return "", "", ErrorMessagef(ErrInvalidPart, "unexpected part etag for number %d in complete request", inPart.PartNumber)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
+ if withSHA256 {
|
|
115
|
+
+ digest := sha256.Sum256(upPart.Body)
|
|
116
|
+
+ if inPart.ChecksumSHA256 != base64.StdEncoding.EncodeToString(digest[:]) {
|
|
117
|
+
+ return "", "", ErrBadDigest
|
|
118
|
+
+ }
|
|
119
|
+
+ composite.Write(digest[:])
|
|
120
|
+
+ }
|
|
121
|
+
size += int64(len(upPart.Body))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@@ -461,7 +483,20 @@
|
|
125
|
+
|
|
126
|
+
etag = fmt.Sprintf(`"%s-%d"`, hex.EncodeToString(hash.Sum(nil)), len(input.Parts))
|
|
127
|
+
|
|
128
|
+
- result, err := u.storage.PutObject(bucket, object, mpu.Meta, bytes.NewReader(body), int64(len(body)), nil)
|
|
129
|
+
+ if withSHA256 {
|
|
130
|
+
+ checksum := fmt.Sprintf("%s-%d", base64.StdEncoding.EncodeToString(composite.Sum(nil)), len(input.Parts))
|
|
131
|
+
+ if input.ChecksumSHA256 != "" && input.ChecksumSHA256 != checksum {
|
|
132
|
+
+ return "", "", ErrBadDigest
|
|
133
|
+
+ }
|
|
134
|
+
+ input.ChecksumSHA256 = checksum
|
|
135
|
+
+ input.ChecksumType = "COMPOSITE"
|
|
136
|
+
+ mpu.Meta["X-Amz-Checksum-Sha256"] = checksum
|
|
137
|
+
+ mpu.Meta["X-Amz-Checksum-Type"] = "COMPOSITE"
|
|
138
|
+
+ } else {
|
|
139
|
+
+ input.ChecksumSHA256 = ""
|
|
140
|
+
+ input.ChecksumType = ""
|
|
141
|
+
+ }
|
|
142
|
+
+ result, err := u.storage.PutObject(bucket, object, mpu.Meta, bytes.NewReader(body), int64(len(body)), input.Conditions)
|
|
143
|
+
if err != nil {
|
|
144
|
+
return "", "", err
|
|
145
|
+
}
|
|
146
|
+
--- a/vendor/github.com/johannesboyne/gofakes3/messages.go
|
|
147
|
+
+++ b/vendor/github.com/johannesboyne/gofakes3/messages.go
|
|
148
|
+
@@ -48,12 +48,16 @@
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
type CompletedPart struct {
|
|
152
|
+
- PartNumber int `xml:"PartNumber"`
|
|
153
|
+
- ETag string `xml:"ETag"`
|
|
154
|
+
+ ChecksumSHA256 string `xml:"ChecksumSHA256,omitempty"`
|
|
155
|
+
+ PartNumber int `xml:"PartNumber"`
|
|
156
|
+
+ ETag string `xml:"ETag"`
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
type CompleteMultipartUploadRequest struct {
|
|
160
|
+
- Parts []CompletedPart `xml:"Part"`
|
|
161
|
+
+ Conditions *PutConditions `xml:"-"`
|
|
162
|
+
+ ChecksumSHA256 string `xml:"-"`
|
|
163
|
+
+ ChecksumType string `xml:"-"`
|
|
164
|
+
+ Parts []CompletedPart `xml:"Part"`
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
func (c CompleteMultipartUploadRequest) partsAreSorted() bool {
|
|
168
|
+
@@ -70,10 +74,12 @@
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
type CompleteMultipartUploadResult struct {
|
|
172
|
+
- Location string `xml:"Location"`
|
|
173
|
+
- Bucket string `xml:"Bucket"`
|
|
174
|
+
- Key string `xml:"Key"`
|
|
175
|
+
- ETag string `xml:"ETag"`
|
|
176
|
+
+ ChecksumSHA256 string `xml:"ChecksumSHA256,omitempty"`
|
|
177
|
+
+ ChecksumType string `xml:"ChecksumType,omitempty"`
|
|
178
|
+
+ Location string `xml:"Location"`
|
|
179
|
+
+ Bucket string `xml:"Bucket"`
|
|
180
|
+
+ Key string `xml:"Key"`
|
|
181
|
+
+ ETag string `xml:"ETag"`
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
type Content struct {
|
|
185
|
+
--- a/vendor/github.com/johannesboyne/gofakes3/checksum.go
|
|
186
|
+
+++ b/vendor/github.com/johannesboyne/gofakes3/checksum.go
|
|
187
|
+
@@ -0,0 +1,32 @@
|
|
188
|
+
+package gofakes3
|
|
189
|
+
+
|
|
190
|
+
+import (
|
|
191
|
+
+ "crypto/sha256"
|
|
192
|
+
+ "encoding/base64"
|
|
193
|
+
+ "hash"
|
|
194
|
+
+ "io"
|
|
195
|
+
+)
|
|
196
|
+
+
|
|
197
|
+
+// Validate before UploadPart commits bytes to the existing multipart store.
|
|
198
|
+
+type sha256PartReader struct {
|
|
199
|
+
+ input io.Reader
|
|
200
|
+
+ digest hash.Hash
|
|
201
|
+
+ expected string
|
|
202
|
+
+}
|
|
203
|
+
+
|
|
204
|
+
+func newSHA256PartReader(input io.Reader, expected string) *sha256PartReader {
|
|
205
|
+
+ return &sha256PartReader{input: input, digest: sha256.New(), expected: expected}
|
|
206
|
+
+}
|
|
207
|
+
+
|
|
208
|
+
+func (r *sha256PartReader) sum() string {
|
|
209
|
+
+ return base64.StdEncoding.EncodeToString(r.digest.Sum(nil))
|
|
210
|
+
+}
|
|
211
|
+
+
|
|
212
|
+
+func (r *sha256PartReader) Read(p []byte) (int, error) {
|
|
213
|
+
+ n, err := r.input.Read(p)
|
|
214
|
+
+ r.digest.Write(p[:n])
|
|
215
|
+
+ if err == io.EOF && r.sum() != r.expected {
|
|
216
|
+
+ return n, ErrBadDigest
|
|
217
|
+
+ }
|
|
218
|
+
+ return n, err
|
|
219
|
+
+}
|
|
220
|
+
--- a/vendor/github.com/johannesboyne/gofakes3/backend.go
|
|
221
|
+
+++ b/vendor/github.com/johannesboyne/gofakes3/backend.go
|
|
222
|
+
@@ -3,6 +3,7 @@
|
|
223
|
+
import (
|
|
224
|
+
"encoding/hex"
|
|
225
|
+
"errors"
|
|
226
|
+
+ "strings"
|
|
227
|
+
|
|
228
|
+
"io"
|
|
229
|
+
"time"
|
|
230
|
+
@@ -434,6 +435,11 @@
|
|
231
|
+
// carry over metadata if it exists
|
|
232
|
+
if existingObj != nil {
|
|
233
|
+
for k, v := range existingObj.Metadata {
|
|
234
|
+
+ // Integrity metadata belongs to the bytes being written, never a
|
|
235
|
+
+ // previous version of this key. CopyObject supplies its own metadata.
|
|
236
|
+
+ if strings.HasPrefix(k, "X-Amz-Checksum-") {
|
|
237
|
+
+ continue
|
|
238
|
+
+ }
|
|
239
|
+
// new metadata overwrites old but keep the rest
|
|
240
|
+
// TODO: check how metadata can be deleted?!
|
|
241
|
+
if _, ok := meta[k]; !ok {
|
|
242
|
+
--- a/vendor/github.com/johannesboyne/gofakes3/gofakes3.go
|
|
243
|
+
+++ b/vendor/github.com/johannesboyne/gofakes3/gofakes3.go
|
|
244
|
+
@@ -960,6 +960,10 @@
|
|
245
|
+
return ErrInvalidPart
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
+ if r.Header.Get("x-amz-copy-source") != "" {
|
|
249
|
+
+ return g.copyMultipartUploadPart(bucket, object, uploadID, int(partNumber), w, r)
|
|
250
|
+
+ }
|
|
251
|
+
+
|
|
252
|
+
size, err := strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64)
|
|
253
|
+
if err != nil || size <= 0 {
|
|
254
|
+
return ErrMissingContentLength
|
|
255
|
+
--- /dev/null
|
|
256
|
+
+++ b/vendor/github.com/johannesboyne/gofakes3/upload_part_copy.go
|
|
257
|
+
@@ -0,0 +1,95 @@
|
|
258
|
+
+package gofakes3
|
|
259
|
+
+
|
|
260
|
+
+import (
|
|
261
|
+
+ "crypto/sha256"
|
|
262
|
+
+ "encoding/base64"
|
|
263
|
+
+ "encoding/xml"
|
|
264
|
+
+ "io"
|
|
265
|
+
+ "net/http"
|
|
266
|
+
+ "net/url"
|
|
267
|
+
+ "strings"
|
|
268
|
+
+)
|
|
269
|
+
+
|
|
270
|
+
+// Copy from one backend snapshot: a separate HEAD followed by GET could race
|
|
271
|
+
+// an overwrite and copy bytes that no longer match the caller's condition.
|
|
272
|
+
+func (g *GoFakeS3) copyMultipartUploadPart(bucket, object string, uploadID UploadID, number int, w http.ResponseWriter, r *http.Request) error {
|
|
273
|
+
+ source, query, _ := strings.Cut(r.Header.Get("x-amz-copy-source"), "?")
|
|
274
|
+
+ source, err := url.PathUnescape(source)
|
|
275
|
+
+ if err != nil {
|
|
276
|
+
+ return ErrInvalidArgument
|
|
277
|
+
+ }
|
|
278
|
+
+ parts := strings.SplitN(strings.TrimPrefix(source, "/"), "/", 2)
|
|
279
|
+
+ if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
|
280
|
+
+ return ErrInvalidArgument
|
|
281
|
+
+ }
|
|
282
|
+
+ params, err := url.ParseQuery(query)
|
|
283
|
+
+ if err != nil {
|
|
284
|
+
+ return ErrInvalidArgument
|
|
285
|
+
+ }
|
|
286
|
+
+ for key := range params {
|
|
287
|
+
+ if key != "versionId" {
|
|
288
|
+
+ return ErrInvalidArgument
|
|
289
|
+
+ }
|
|
290
|
+
+ }
|
|
291
|
+
+ // Do not silently ignore conditions this compatibility subset cannot apply.
|
|
292
|
+
+ for _, key := range []string{"x-amz-copy-source-if-modified-since", "x-amz-copy-source-if-unmodified-since"} {
|
|
293
|
+
+ if r.Header.Get(key) != "" {
|
|
294
|
+
+ return ErrNotImplemented
|
|
295
|
+
+ }
|
|
296
|
+
+ }
|
|
297
|
+
+ var src *Object
|
|
298
|
+
+ if version := params.Get("versionId"); version != "" {
|
|
299
|
+
+ if g.versioned == nil {
|
|
300
|
+
+ return ErrNotImplemented
|
|
301
|
+
+ }
|
|
302
|
+
+ src, err = g.versioned.GetObjectVersion(parts[0], parts[1], VersionID(version), nil)
|
|
303
|
+
+ } else {
|
|
304
|
+
+ src, err = g.storage.GetObject(parts[0], parts[1], nil)
|
|
305
|
+
+ }
|
|
306
|
+
+ if err != nil {
|
|
307
|
+
+ return err
|
|
308
|
+
+ }
|
|
309
|
+
+ if src == nil || src.Contents == nil {
|
|
310
|
+
+ return ErrInternal
|
|
311
|
+
+ }
|
|
312
|
+
+ defer src.Contents.Close()
|
|
313
|
+
+ if src.IsDeleteMarker {
|
|
314
|
+
+ return ErrInvalidArgument
|
|
315
|
+
+ }
|
|
316
|
+
+ tag := FormatETag(src.Hash)
|
|
317
|
+
+ if expected := r.Header.Get("x-amz-copy-source-if-match"); expected != "" && expected != tag {
|
|
318
|
+
+ return ErrPreconditionFailed
|
|
319
|
+
+ }
|
|
320
|
+
+ if expected := r.Header.Get("x-amz-copy-source-if-none-match"); expected != "" && expected == tag {
|
|
321
|
+
+ return ErrPreconditionFailed
|
|
322
|
+
+ }
|
|
323
|
+
+ start, size := int64(0), src.Size
|
|
324
|
+
+ if value := r.Header.Get("x-amz-copy-source-range"); value != "" {
|
|
325
|
+
+ span, err := parseRangeHeader(value)
|
|
326
|
+
+ if err != nil || span == nil || span.FromEnd || span.End == RangeNoEnd || span.End >= src.Size {
|
|
327
|
+
+ return ErrInvalidRange
|
|
328
|
+
+ }
|
|
329
|
+
+ start, size = span.Start, span.End-span.Start+1
|
|
330
|
+
+ }
|
|
331
|
+
+ if size <= 0 {
|
|
332
|
+
+ return ErrInvalidRange
|
|
333
|
+
+ }
|
|
334
|
+
+ if _, err := io.CopyN(io.Discard, src.Contents, start); err != nil {
|
|
335
|
+
+ return err
|
|
336
|
+
+ }
|
|
337
|
+
+ digest := sha256.New()
|
|
338
|
+
+ body := io.TeeReader(io.LimitReader(src.Contents, size), digest)
|
|
339
|
+
+ etag, err := g.uploader.UploadPart(bucket, object, uploadID, number, size, body)
|
|
340
|
+
+ if err != nil {
|
|
341
|
+
+ return err
|
|
342
|
+
+ }
|
|
343
|
+
+ if src.VersionID != "" {
|
|
344
|
+
+ w.Header().Set("x-amz-copy-source-version-id", string(src.VersionID))
|
|
345
|
+
+ }
|
|
346
|
+
+ return g.xmlEncoder(w).Encode(struct {
|
|
347
|
+
+ XMLName xml.Name `xml:"CopyPartResult"`
|
|
348
|
+
+ ETag string `xml:"ETag"`
|
|
349
|
+
+ LastModified ContentTime `xml:"LastModified"`
|
|
350
|
+
+ ChecksumSHA256 string `xml:"ChecksumSHA256"`
|
|
351
|
+
+ }{ETag: etag, LastModified: NewContentTime(g.timeSource.Now()), ChecksumSHA256: base64.StdEncoding.EncodeToString(digest.Sum(nil))})
|
|
352
|
+
+}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Exercise the exact vendored compatibility patches used by the runtime image.
|
|
3
|
+
set -eu
|
|
4
|
+
source_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
5
|
+
test_dir=$(mktemp -d)
|
|
6
|
+
trap 'rm -rf "$test_dir"' EXIT HUP INT TERM
|
|
7
|
+
cp "$source_dir"/*.go "$source_dir/go.mod" "$source_dir/go.sum" "$source_dir"/*.patch "$test_dir/"
|
|
8
|
+
cd "$test_dir"
|
|
9
|
+
go mod vendor
|
|
10
|
+
patch -p1 < metadata.patch
|
|
11
|
+
patch -p1 < multipart.patch
|
|
12
|
+
go test -mod=vendor -race ./...
|
package/src/components/s3.ts
CHANGED
|
@@ -52,11 +52,11 @@ const S3_DOCKERFILE = `
|
|
|
52
52
|
FROM golang:1.27.2-alpine@sha256:f92b6ef800e499660581efdabdf25d9d817a9d124eaf900924f0504e7e27e12d AS build
|
|
53
53
|
RUN apk add --no-cache patch
|
|
54
54
|
WORKDIR /src
|
|
55
|
-
${["go.mod", "go.sum", "main.go", "metadata.patch"].map((file) => {
|
|
55
|
+
${["go.mod", "go.sum", "main.go", "metadata.patch", "multipart.patch"].map((file) => {
|
|
56
56
|
const data = readFileSync(new URL(`./s3-runtime/${file}`, import.meta.url)).toString("base64");
|
|
57
57
|
return `RUN printf '%s' '${data}' | base64 -d > ${file}`;
|
|
58
58
|
}).join("\n")}
|
|
59
|
-
RUN go mod vendor && patch -p1 < metadata.patch
|
|
59
|
+
RUN go mod vendor && patch -p1 < metadata.patch && patch -p1 < multipart.patch
|
|
60
60
|
RUN CGO_ENABLED=0 go build -mod=vendor -trimpath -ldflags='-s -w' -o /s3-server .
|
|
61
61
|
FROM scratch
|
|
62
62
|
COPY --from=build /s3-server /s3-server
|