fix(gateway): undesired conversions to dag-json and friends (#9566)
* fix(gateway): do not convert unixfs/raw into dag-* unless explicit * fix(gateway): keep only dag-json|dag-cbor handling * fix: allow requesting dag-json as application/json - adds bunch of additional tests including JSON file on UnixFS - fix: dag-json codec (0x0129) can be returned as plain json - fix: json codec (0x0200) cna be retrurned as plain json * fix: using ?format|Accept with CID w/ codec works * docs(changelog): cbor and json on gateway Co-authored-by: Marcin Rataj <lidel@lidel.org>
Henrique Dias committed
Jan 21, 2023 at 04:21 UTC
c706c638fc33f2ad28110858f6e25de32a7eac7f
4 files changed
+241
-190
core/corehttp/gateway_handler.go
+11
-10
@@ -418,9 +418,9 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
418
419
// Support custom response formats passed via ?format or Accept HTTP header
420
switch responseFormat {
421
- case "":
422
- switch resolvedPath.Cid().Prefix().Codec {
423
- case uint64(mc.Json), uint64(mc.DagJson), uint64(mc.Cbor), uint64(mc.DagCbor):
421
+ case "", "application/json", "application/cbor":
422
+ switch mc.Code(resolvedPath.Cid().Prefix().Codec) {
423
+ case mc.Json, mc.DagJson, mc.Cbor, mc.DagCbor:
424
logger.Debugw("serving codec", "path", contentPath)
425
i.serveCodec(r.Context(), w, r, resolvedPath, contentPath, begin, responseFormat)
426
default:
@@ -441,14 +441,13 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
441
logger.Debugw("serving tar file", "path", contentPath)
442
i.serveTAR(r.Context(), w, r, resolvedPath, contentPath, begin, logger)
443
return
444
- case "application/json", "application/vnd.ipld.dag-json",
445
- "application/cbor", "application/vnd.ipld.dag-cbor":
444
+ case "application/vnd.ipld.dag-json", "application/vnd.ipld.dag-cbor":
445
logger.Debugw("serving codec", "path", contentPath)
446
i.serveCodec(r.Context(), w, r, resolvedPath, contentPath, begin, responseFormat)
447
return
448
default: // catch-all for unsuported application/vnd.*
449
err := fmt.Errorf("unsupported format %q", responseFormat)
451
- webError(w, "failed respond with requested content type", err, http.StatusBadRequest)
450
+ webError(w, "failed to respond with requested content type", err, http.StatusBadRequest)
451
return
452
}
453
}
@@ -878,14 +877,14 @@ func customResponseFormat(r *http.Request) (mediaType string, params map[string]
877
return "application/vnd.ipld.car", nil, nil
878
case "tar":
879
return "application/x-tar", nil, nil
881
- case "dag-json":
882
- return "application/vnd.ipld.dag-json", nil, nil
880
case "json":
881
return "application/json", nil, nil
885
- case "dag-cbor":
886
- return "application/vnd.ipld.dag-cbor", nil, nil
882
case "cbor":
883
return "application/cbor", nil, nil
884
+ case "dag-json":
885
+ return "application/vnd.ipld.dag-json", nil, nil
886
+ case "dag-cbor":
887
+ return "application/vnd.ipld.dag-cbor", nil, nil
888
}
889
}
890
// Browsers and other user agents will send Accept header with generic types like:
@@ -908,6 +907,8 @@ func customResponseFormat(r *http.Request) (mediaType string, params map[string]
907
}
908
}
909
}
910
+ // If none of special-cased content types is found, return empty string
911
+ // to indicate default, implicit UnixFS response should be prepared
912
return "", nil, nil
913
}
914
core/corehttp/gateway_handler_codec.go
+41
-42
@@ -25,22 +25,25 @@ import (
25
26
// codecToContentType maps the supported IPLD codecs to the HTTP Content
27
// Type they should have.
28
-var codecToContentType = map[uint64]string{
29
- uint64(mc.Json): "application/json",
30
- uint64(mc.Cbor): "application/cbor",
31
- uint64(mc.DagJson): "application/vnd.ipld.dag-json",
32
- uint64(mc.DagCbor): "application/vnd.ipld.dag-cbor",
28
+var codecToContentType = map[mc.Code]string{
29
+ mc.Json: "application/json",
30
+ mc.Cbor: "application/cbor",
31
+ mc.DagJson: "application/vnd.ipld.dag-json",
32
+ mc.DagCbor: "application/vnd.ipld.dag-cbor",
33
}
34
35
-// contentTypeToCodecs maps the HTTP Content Type to the respective
36
-// possible codecs. If the original data is in one of those codecs,
37
-// we stream the raw bytes. Otherwise, we encode in the last codec
38
-// of the list.
39
-var contentTypeToCodecs = map[string][]uint64{
40
- "application/json": {uint64(mc.Json), uint64(mc.DagJson)},
41
- "application/vnd.ipld.dag-json": {uint64(mc.DagJson)},
42
- "application/cbor": {uint64(mc.Cbor), uint64(mc.DagCbor)},
43
- "application/vnd.ipld.dag-cbor": {uint64(mc.DagCbor)},
35
+// contentTypeToRaw maps the HTTP Content Type to the respective codec that
36
+// allows raw response without any conversion.
37
+var contentTypeToRaw = map[string][]mc.Code{
38
+ "application/json": {mc.Json, mc.DagJson},
39
+ "application/cbor": {mc.Cbor, mc.DagCbor},
40
+}
41
+
42
+// contentTypeToCodec maps the HTTP Content Type to the respective codec. We
43
+// only add here the codecs that we want to convert-to-from.
44
+var contentTypeToCodec = map[string]mc.Code{
45
+ "application/vnd.ipld.dag-json": mc.DagJson,
46
+ "application/vnd.ipld.dag-cbor": mc.DagCbor,
47
}
48
49
// contentTypeToExtension maps the HTTP Content Type to the respective file
@@ -56,7 +59,7 @@ func (i *gatewayHandler) serveCodec(ctx context.Context, w http.ResponseWriter,
59
ctx, span := tracing.Span(ctx, "Gateway", "ServeCodec", trace.WithAttributes(attribute.String("path", resolvedPath.String()), attribute.String("requestedContentType", requestedContentType)))
60
defer span.End()
61
59
- cidCodec := resolvedPath.Cid().Prefix().Codec
62
+ cidCodec := mc.Code(resolvedPath.Cid().Prefix().Codec)
63
responseContentType := requestedContentType
64
65
// If the resolved path still has some remainder, return error for now.
@@ -90,22 +93,36 @@ func (i *gatewayHandler) serveCodec(ctx context.Context, w http.ResponseWriter,
93
// No content type is specified by the user (via Accept, or format=). However,
94
// we support this format. Let's handle it.
95
if requestedContentType == "" {
93
- isDAG := cidCodec == uint64(mc.DagJson) || cidCodec == uint64(mc.DagCbor)
96
+ isDAG := cidCodec == mc.DagJson || cidCodec == mc.DagCbor
97
acceptsHTML := strings.Contains(r.Header.Get("Accept"), "text/html")
98
download := r.URL.Query().Get("download") == "true"
99
100
if isDAG && acceptsHTML && !download {
101
i.serveCodecHTML(ctx, w, r, resolvedPath, contentPath)
102
} else {
103
+ // This covers CIDs with codec 'json' and 'cbor' as those do not have
104
+ // an explicit requested content type.
105
i.serveCodecRaw(ctx, w, r, resolvedPath, contentPath, name, modtime)
106
}
107
108
return
109
}
110
106
- // Otherwise, the user has requested a specific content type. Let's first get
107
- // the codecs that can be used with this content type.
108
- codecs, ok := contentTypeToCodecs[requestedContentType]
111
+ // If DAG-JSON or DAG-CBOR was requested using corresponding plain content type
112
+ // return raw block as-is, without conversion
113
+ skipCodecs, ok := contentTypeToRaw[requestedContentType]
114
+ if ok {
115
+ for _, skipCodec := range skipCodecs {
116
+ if skipCodec == cidCodec {
117
+ i.serveCodecRaw(ctx, w, r, resolvedPath, contentPath, name, modtime)
118
+ return
119
+ }
120
+ }
121
+ }
122
+
123
+ // Otherwise, the user has requested a specific content type (a DAG-* variant).
124
+ // Let's first get the codecs that can be used with this content type.
125
+ toCodec, ok := contentTypeToCodec[requestedContentType]
126
if !ok {
127
// This is never supposed to happen unless function is called with wrong parameters.
128
err := fmt.Errorf("unsupported content type: %s", requestedContentType)
@@ -113,27 +130,7 @@ func (i *gatewayHandler) serveCodec(ctx context.Context, w http.ResponseWriter,
130
return
131
}
132
116
- // If we need to convert, use the last codec (strict dag- variant)
117
- toCodec := codecs[len(codecs)-1]
118
-
119
- // If the requested content type has "dag-", ALWAYS go through the encoding
120
- // process in order to validate the content.
121
- if strings.Contains(requestedContentType, "dag-") {
122
- i.serveCodecConverted(ctx, w, r, resolvedPath, contentPath, toCodec, modtime)
123
- return
124
- }
125
-
126
- // Otherwise, check if the data is encoded with the requested content type.
127
- // If so, we can directly stream the raw data. serveRawBlock cannot be directly
128
- // used here as it sets different headers.
129
- for _, codec := range codecs {
130
- if resolvedPath.Cid().Prefix().Codec == codec {
131
- i.serveCodecRaw(ctx, w, r, resolvedPath, contentPath, name, modtime)
132
- return
133
- }
134
- }
135
-
136
- // Finally, if nothing of the above is true, we have to actually convert the codec.
133
+ // This handles DAG-* conversions and validations.
134
i.serveCodecConverted(ctx, w, r, resolvedPath, contentPath, toCodec, modtime)
135
}
136
@@ -165,6 +162,7 @@ func (i *gatewayHandler) serveCodecHTML(ctx context.Context, w http.ResponseWrit
162
}
163
}
164
165
+// serveCodecRaw returns the raw block without any conversion
166
func (i *gatewayHandler) serveCodecRaw(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, name string, modtime time.Time) {
167
blockCid := resolvedPath.Cid()
168
blockReader, err := i.api.Block().Get(ctx, resolvedPath)
@@ -184,7 +182,8 @@ func (i *gatewayHandler) serveCodecRaw(ctx context.Context, w http.ResponseWrite
182
_, _, _ = ServeContent(w, r, name, modtime, content)
183
}
184
187
-func (i *gatewayHandler) serveCodecConverted(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, toCodec uint64, modtime time.Time) {
185
+// serveCodecConverted returns payload converted to codec specified in toCodec
186
+func (i *gatewayHandler) serveCodecConverted(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, toCodec mc.Code, modtime time.Time) {
187
obj, err := i.api.Dag().Get(ctx, resolvedPath.Cid())
188
if err != nil {
189
webError(w, "ipfs dag get "+html.EscapeString(resolvedPath.String()), err, http.StatusInternalServerError)
@@ -199,7 +198,7 @@ func (i *gatewayHandler) serveCodecConverted(ctx context.Context, w http.Respons
198
}
199
finalNode := universal.(ipld.Node)
200
202
- encoder, err := multicodec.LookupEncoder(toCodec)
201
+ encoder, err := multicodec.LookupEncoder(uint64(toCodec))
202
if err != nil {
203
webError(w, err.Error(), err, http.StatusInternalServerError)
204
return
docs/changelogs/v0.18.md
+68
-11
@@ -64,19 +64,74 @@ Learn more in the [`Reprovider` config](https://github.com/ipfs/go-ipfs/blob/mas
64
65
##### (DAG-)JSON and (DAG-)CBOR response formats
66
67
-Implemented [IPIP-328](https://github.com/ipfs/specs/pull/328) which adds support
68
-for DAG-JSON and DAG-CBOR, as well as their non-DAG variants, to the gateway. Now,
69
-CIDs that encode JSON, CBOR, DAG-JSON and DAG-CBOR objects can be retrieved, and
70
-traversed thanks to the [special meaning of CBOR Tag 42](https://github.com/ipld/cid-cbor/).
67
+The IPFS project has reserved the corresponding media types at IANA:
68
+- [`application/vnd.ipld.dag-json`](https://www.iana.org/assignments/media-types/application/vnd.ipld.dag-json)
69
+- [`application/vnd.ipld.dag-cbor`](https://www.iana.org/assignments/media-types/application/vnd.ipld.dag-cbor)
70
72
-HTTP clients can request JSON, CBOR, DAG-JSON, and DAG-CBOR responses by either
73
-passing the query parameter `?format` or setting the `Accept` HTTP header to the
74
-following values:
71
+This release implements them as part of [IPIP-328](https://github.com/ipfs/specs/pull/328)
72
+and adds Gateway support for CIDs with `json` (0x0200), `cbor` (0x51),
73
+[`dag-json`](https://ipld.io/specs/codecs/dag-json/) (0x0129)
74
+and [`dag-cbor`](https://ipld.io/specs/codecs/dag-cbor/spec/) (0x71) codecs.
75
76
-- JSON: `?format=json`, or `Accept: application/json`
77
-- CBOR: `?format=cbor`, or `Accept: application/cbor`
78
-- DAG-JSON: `?format=dag-json`, or `Accept: application/vnd.ipld.dag-json`
79
-- DAG-JSON: `?format=dag-cbor`, or `Accept: application/vnd.ipld.dag-cbor`
76
+To specify the response `Content-Type` explicitly, the HTTP client can override
77
+the codec present in the CID by using the `format` parameter
78
+or setting the `Accept` HTTP header:
79
+
80
+- Plain JSON: `?format=json` or `Accept: application/json`
81
+- Plain CBOR: `?format=cbor` or `Accept: application/cbor`
82
+- DAG-JSON: `?format=dag-json` or `Accept: application/vnd.ipld.dag-json`
83
+- DAG-CBOR: `?format=dag-cbor` or `Accept: application/vnd.ipld.dag-cbor`
84
+
85
+In addition, when DAG-JSON or DAG-CBOR is requested with the `Accept` header
86
+set to `text/html`, the Gateway will return a basic HTML page with download
87
+options, improving the user experience in web browsers.
88
+
89
+###### Example 1: DAG-CBOR and DAG-JSON Conversion on Gateway
90
+
91
+The Gateway supports conversion between DAG-CBOR and DAG-JSON for efficient
92
+end-to-end data structure management: author in CBOR or JSON, store as binary
93
+CBOR and retrieve as JSON via HTTP:
94
+
95
+```console
96
+$ echo '{"test": "json"}' | ipfs dag put # implicit --input-codec dag-json --store-codec dag-cbor
97
+bafyreico7mjtqtqhvawro3yud5uqn6sc33nzqb7b5j2d7pdmzer5nab4t4
98
+
99
+$ ipfs block get bafyreico7mjtqtqhvawro3yud5uqn6sc33nzqb7b5j2d7pdmzer5nab4t4 | xxd
100
+00000000: a164 7465 7374 646a 736f 6e .dtestdjson
101
+
102
+$ ipfs dag get bafyreico7mjtqtqhvawro3yud5uqn6sc33nzqb7b5j2d7pdmzer5nab4t4 # implicit --output-codec dag-json
103
+{"test":"json"}
104
+
105
+$ curl "http://127.0.0.1:8080/ipfs/bafyreico7mjtqtqhvawro3yud5uqn6sc33nzqb7b5j2d7pdmzer5nab4t4?format=dag-json"
106
+{"test":"json"}
107
+```
108
+
109
+###### Example 2: Traversing CBOR DAGs
110
+
111
+Placing a CID in [CBOR Tag 42](https://github.com/ipld/cid-cbor/) enables the
112
+creation of arbitrary DAGs. The equivalent DAG-JSON notation for linking
113
+to different blocks is represented by `{ "/": "cid" }`.
114
+
115
+The Gateway supports traversing these links, enabling access to data
116
+referenced by structures other than regular UnixFS directories:
117
+
118
+```console
119
+$ echo '{"test.jpg": {"/": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"}}' | ipfs dag put
120
+bafyreihspwy3zlkzgphmec5d3xb5g5njrqwotd46lyubnelbzktnmsxkq4 # dag-cbor document linking to unixfs file
121
+
122
+$ ipfs resolve /ipfs/bafyreihspwy3zlkzgphmec5d3xb5g5njrqwotd46lyubnelbzktnmsxkq4/test.jpg
123
+/ipfs/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi
124
+
125
+$ ipfs dag stat bafyreihspwy3zlkzgphmec5d3xb5g5njrqwotd46lyubnelbzktnmsxkq4
126
+Size: 119827, NumBlocks: 2
127
+
128
+$ curl "http://127.0.0.1:8080/ipfs/bafyreihspwy3zlkzgphmec5d3xb5g5njrqwotd46lyubnelbzktnmsxkq4/test.jpg" > test.jpg
129
+```
130
+
131
+###### Example 3: UnixFS directory listing as JSON
132
+
133
+Finally, Gateway now supports the same [logical format projection](https://ipld.io/specs/codecs/dag-pb/spec/#logical-format) from
134
+DAG-PB to DAG-JSON as the `ipfs dag get` command, enabling the retrieval of directory listings as JSON instead of HTML:
135
136
```console
137
$ export DIR_CID=bafybeigccimv3zqm5g4jt363faybagywkvqbrismoquogimy7kvz2sj7sq
@@ -112,6 +167,8 @@ $ curl "http://127.0.0.1:8080/ipfs/$DIR_CID?format=dag-json" | jq
167
}
168
]
169
}
170
+$ ipfs dag get $DIR_CID
171
+{"Data":{"/":{"bytes":"CAE"}},"Links":[{"Hash":{"/":"Qmc3zqKcwzbbvw3MQm3hXdg8BQoFjGdZiGdAfXAyAGGdLi"},"Name":"1 - Barrel - Part 1 - alt.txt","Tsize":21},{"Hash":{"/":"QmdMxMx29KVYhHnaCc1icWYxQqXwUNCae6t1wS2NqruiHd"},"Name":"1 - Barrel - Part 1 - transcript.txt","Tsize":195},{"Hash":{"/":"QmawceGscqN4o8Y8Fv26UUmB454kn2bnkXV5tEQYc4jBd6"},"Name":"1 - Barrel - Part 1.png","Tsize":24862}]}
172
```
173
174
##### 🐎 Fast directory listings with DAG sizes
test/sharness/t0123-gateway-json-cbor.sh
+121
-127
@@ -12,158 +12,150 @@ test_expect_success "Add the test directory" '
12
mkdir -p rootDir/ipns &&
13
mkdir -p rootDir/api &&
14
mkdir -p rootDir/ą/ę &&
15
+ echo "{ \"test\": \"i am a plain json file\" }" > rootDir/ą/ę/t.json &&
16
echo "I am a txt file on path with utf8" > rootDir/ą/ę/file-źł.txt &&
17
echo "I am a txt file in confusing /api dir" > rootDir/api/file.txt &&
18
echo "I am a txt file in confusing /ipfs dir" > rootDir/ipfs/file.txt &&
19
echo "I am a txt file in confusing /ipns dir" > rootDir/ipns/file.txt &&
20
DIR_CID=$(ipfs add -Qr --cid-version 1 rootDir) &&
21
+ FILE_JSON_CID=$(ipfs files stat --enc=json /ipfs/$DIR_CID/ą/ę/t.json | jq -r .Hash) &&
22
FILE_CID=$(ipfs files stat --enc=json /ipfs/$DIR_CID/ą/ę/file-źł.txt | jq -r .Hash) &&
23
FILE_SIZE=$(ipfs files stat --enc=json /ipfs/$DIR_CID/ą/ę/file-źł.txt | jq -r .Size)
24
echo "$FILE_CID / $FILE_SIZE"
25
'
26
27
+## Quick regression check for JSON stored on UnixFS:
28
+## it has nothing to do with DAG-JSON and JSON codecs,
29
+## but a lot of JSON data is stored on UnixFS and is requested with or without various hints
30
+## and we want to avoid surprises like https://github.com/protocol/bifrost-infra/issues/2290
31
+test_expect_success "GET UnixFS file with JSON bytes is returned with application/json Content-Type" '
32
+ curl -sD headers "http://127.0.0.1:$GWAY_PORT/ipfs/$FILE_JSON_CID" > curl_output 2>&1 &&
33
+ curl -sD headers_accept -H "Accept: application/json" "http://127.0.0.1:$GWAY_PORT/ipfs/$FILE_JSON_CID" > curl_output_accept 2>&1 &&
34
+ ipfs cat $FILE_JSON_CID > ipfs_cat_output 2>&1 &&
35
+ test_should_contain "Content-Type: application/json" headers &&
36
+ test_should_contain "Content-Type: application/json" headers_accept &&
37
+ test_cmp ipfs_cat_output curl_output &&
38
+ test_cmp curl_output curl_output_accept
39
+'
40
+
41
+
42
## Reading UnixFS (data encoded with dag-pb codec) as DAG-CBOR and DAG-JSON
43
+## (returns representation defined in https://ipld.io/specs/codecs/dag-pb/spec/#logical-format)
44
27
-test_dag_pb_headers () {
45
+test_dag_pb_conversion () {
46
name=$1
47
format=$2
48
disposition=$3
49
32
- test_expect_success "GET UnixFS as $name with format=dag-$format has expected Content-Type" '
33
- curl -sD - "http://127.0.0.1:$GWAY_PORT/ipfs/$FILE_CID?format=dag-$format" > curl_output 2>&1 &&
34
- test_should_contain "Content-Type: application/vnd.ipld.dag-$format" curl_output &&
35
- test_should_contain "Content-Disposition: ${disposition}\; filename=\"${FILE_CID}.${format}\"" curl_output &&
36
- test_should_not_contain "Content-Type: application/$format" curl_output
50
+ test_expect_success "GET UnixFS file as $name with format=dag-$format converts to the expected Content-Type" '
51
+ curl -sD headers "http://127.0.0.1:$GWAY_PORT/ipfs/$FILE_CID?format=dag-$format" > curl_output 2>&1 &&
52
+ ipfs dag get --output-codec dag-$format $FILE_CID > ipfs_dag_get_output 2>&1 &&
53
+ test_cmp ipfs_dag_get_output curl_output &&
54
+ test_should_contain "Content-Type: application/vnd.ipld.dag-$format" headers &&
55
+ test_should_contain "Content-Disposition: ${disposition}\; filename=\"${FILE_CID}.${format}\"" headers &&
56
+ test_should_not_contain "Content-Type: application/$format" headers
57
'
58
39
- test_expect_success "GET UnixFS as $name with 'Accept: application/vnd.ipld.dag-$format' has expected Content-Type" '
59
+ test_expect_success "GET UnixFS directory as $name with format=dag-$format converts to the expected Content-Type" '
60
+ curl -sD headers "http://127.0.0.1:$GWAY_PORT/ipfs/$DIR_CID?format=dag-$format" > curl_output 2>&1 &&
61
+ ipfs dag get --output-codec dag-$format $DIR_CID > ipfs_dag_get_output 2>&1 &&
62
+ test_cmp ipfs_dag_get_output curl_output &&
63
+ test_should_contain "Content-Type: application/vnd.ipld.dag-$format" headers &&
64
+ test_should_contain "Content-Disposition: ${disposition}\; filename=\"${DIR_CID}.${format}\"" headers &&
65
+ test_should_not_contain "Content-Type: application/$format" headers
66
+ '
67
+
68
+ test_expect_success "GET UnixFS as $name with 'Accept: application/vnd.ipld.dag-$format' converts to the expected Content-Type" '
69
curl -sD - -H "Accept: application/vnd.ipld.dag-$format" "http://127.0.0.1:$GWAY_PORT/ipfs/$FILE_CID" > curl_output 2>&1 &&
70
test_should_contain "Content-Disposition: ${disposition}\; filename=\"${FILE_CID}.${format}\"" curl_output &&
71
test_should_contain "Content-Type: application/vnd.ipld.dag-$format" curl_output &&
72
test_should_not_contain "Content-Type: application/$format" curl_output
73
'
74
46
- test_expect_success "GET UnixFS as $name with format=$format has expected Content-Type" '
47
- curl -sD - "http://127.0.0.1:$GWAY_PORT/ipfs/$FILE_CID?format=$format" > curl_output 2>&1 &&
48
- test_should_contain "Content-Disposition: ${disposition}\; filename=\"${FILE_CID}.${format}\"" curl_output &&
49
- test_should_contain "Content-Type: application/$format" curl_output &&
50
- test_should_not_contain "Content-Type: application/vnd.ipld.dag-$format" curl_output
51
- '
52
-
53
- test_expect_success "GET UnixFS as $name with 'Accept: application/$format' has expected Content-Type" '
54
- curl -sD - -H "Accept: application/$format" "http://127.0.0.1:$GWAY_PORT/ipfs/$FILE_CID" > curl_output 2>&1 &&
55
- test_should_contain "Content-Disposition: ${disposition}\; filename=\"${FILE_CID}.${format}\"" curl_output &&
56
- test_should_contain "Content-Type: application/$format" curl_output &&
57
- test_should_not_contain "Content-Type: application/vnd.ipld.dag-$format" curl_output
58
- '
59
-
60
- test_expect_success "GET UnixFS as $name with 'Accept: foo, application/$format,bar' has expected Content-Type" '
61
- curl -sD - -H "Accept: foo, application/$format,text/plain" "http://127.0.0.1:$GWAY_PORT/ipfs/$FILE_CID" > curl_output 2>&1 &&
62
- test_should_contain "Content-Type: application/$format" curl_output
63
- '
64
-}
65
-
66
-test_dag_pb_headers "DAG-JSON" "json" "inline"
67
-test_dag_pb_headers "DAG-CBOR" "cbor" "attachment"
68
-
69
-test_dag_pb () {
70
- name=$1
71
- format=$2
72
-
73
- test_expect_success "GET UnixFS as $name has expected output for file" '
74
- curl -s "http://127.0.0.1:$GWAY_PORT/ipfs/$FILE_CID?format=dag-$format" > curl_output 2>&1 &&
75
- ipfs dag get --output-codec dag-$format $FILE_CID > ipfs_dag_get_output 2>&1 &&
76
- test_cmp ipfs_dag_get_output curl_output
75
+ test_expect_success "GET UnixFS as $name with 'Accept: foo, application/vnd.ipld.dag-$format,bar' converts to the expected Content-Type" '
76
+ curl -sD - -H "Accept: foo, application/vnd.ipld.dag-$format,text/plain" "http://127.0.0.1:$GWAY_PORT/ipfs/$FILE_CID" > curl_output 2>&1 &&
77
+ test_should_contain "Content-Type: application/vnd.ipld.dag-$format" curl_output
78
'
79
79
- test_expect_success "GET UnixFS as $name has expected output for directory" '
80
- curl -s "http://127.0.0.1:$GWAY_PORT/ipfs/$DIR_CID?format=dag-$format" > curl_output 2>&1 &&
81
- ipfs dag get --output-codec dag-$format $DIR_CID > ipfs_dag_get_output 2>&1 &&
82
- test_cmp ipfs_dag_get_output curl_output
80
+ test_expect_success "GET UnixFS with format=$format (not dag-$format) is no-op (no conversion)" '
81
+ curl -sD headers "http://127.0.0.1:$GWAY_PORT/ipfs/$FILE_CID?format=$format" > curl_output 2>&1 &&
82
+ ipfs cat $FILE_CID > cat_output &&
83
+ test_cmp cat_output curl_output &&
84
+ test_should_contain "Content-Type: text/plain" headers &&
85
+ test_should_not_contain "Content-Type: application/$format" headers &&
86
+ test_should_not_contain "Content-Type: application/vnd.ipld.dag-$format" headers
87
'
88
85
- test_expect_success "GET UnixFS as $name with format=dag-$format and format=$format produce same output" '
86
- curl -s "http://127.0.0.1:$GWAY_PORT/ipfs/$DIR_CID?format=dag-$format" > curl_output_1 2>&1 &&
87
- curl -s "http://127.0.0.1:$GWAY_PORT/ipfs/$DIR_CID?format=$format" > curl_output_2 2>&1 &&
88
- test_cmp curl_output_1 curl_output_2
89
+ test_expect_success "GET UnixFS with 'Accept: application/$format' (not dag-$format) is no-op (no conversion)" '
90
+ curl -sD headers -H "Accept: application/$format" "http://127.0.0.1:$GWAY_PORT/ipfs/$FILE_CID" > curl_output 2>&1 &&
91
+ ipfs cat $FILE_CID > cat_output &&
92
+ test_cmp cat_output curl_output &&
93
+ test_should_contain "Content-Type: text/plain" headers &&
94
+ test_should_not_contain "Content-Type: application/$format" headers &&
95
+ test_should_not_contain "Content-Type: application/vnd.ipld.dag-$format" headers
96
'
97
}
98
92
-test_dag_pb "DAG-JSON" "json"
93
-test_dag_pb "DAG-CBOR" "cbor"
99
+test_dag_pb_conversion "DAG-JSON" "json" "inline"
100
+test_dag_pb_conversion "DAG-CBOR" "cbor" "attachment"
101
95
-## Content-Type response based on Accept header and ?format= parameter
102
97
-test_cmp_dag_get () {
103
+# Requesting CID with plain json (0x0200) and cbor (0x51) codecs
104
+# (note these are not UnixFS, not DAG-* variants, just raw block identified by a CID with a special codec)
105
+test_plain_codec () {
106
name=$1
107
format=$2
108
disposition=$3
109
102
- test_expect_success "GET $name without Accept or format= has expected Content-Type" '
103
- CID=$(echo "{ \"test\": \"json\" }" | ipfs dag put --input-codec json --store-codec $format) &&
104
- curl -sD - "http://127.0.0.1:$GWAY_PORT/ipfs/$CID" > curl_output 2>&1 &&
105
- test_should_contain "Content-Disposition: ${disposition}\; filename=\"${CID}.${format}\"" curl_output &&
106
- test_should_contain "Content-Type: application/$format" curl_output
110
+ # no explicit format, just codec in CID
111
+ test_expect_success "GET $name without Accept or format= has expected $format Content-Type and body as-is" '
112
+ CID=$(echo "{ \"test\": \"plain json\" }" | ipfs dag put --input-codec json --store-codec $format) &&
113
+ curl -sD headers "http://127.0.0.1:$GWAY_PORT/ipfs/$CID" > curl_output 2>&1 &&
114
+ ipfs block get $CID > ipfs_block_output 2>&1 &&
115
+ test_cmp ipfs_block_output curl_output &&
116
+ test_should_contain "Content-Disposition: ${disposition}\; filename=\"${CID}.${format}\"" headers &&
117
+ test_should_contain "Content-Type: application/$format" headers
118
'
119
109
- test_expect_success "GET $name without Accept or format= produces correct output" '
110
- CID=$(echo "{ \"test\": \"json\" }" | ipfs dag put --input-codec json --store-codec $format) &&
111
- curl -s "http://127.0.0.1:$GWAY_PORT/ipfs/$CID" > curl_output 2>&1 &&
112
- ipfs dag get --output-codec $format $CID > ipfs_dag_get_output 2>&1 &&
113
- test_cmp ipfs_dag_get_output curl_output
120
+ # explicit format still gives correct output, just codec in CID
121
+ test_expect_success "GET $name with ?format= has expected $format Content-Type and body as-is" '
122
+ CID=$(echo "{ \"test\": \"plain json\" }" | ipfs dag put --input-codec json --store-codec $format) &&
123
+ curl -sD headers "http://127.0.0.1:$GWAY_PORT/ipfs/$CID?format=$format" > curl_output 2>&1 &&
124
+ ipfs block get $CID > ipfs_block_output 2>&1 &&
125
+ test_cmp ipfs_block_output curl_output &&
126
+ test_should_contain "Content-Disposition: ${disposition}\; filename=\"${CID}.${format}\"" headers &&
127
+ test_should_contain "Content-Type: application/$format" headers
128
'
129
116
- test_expect_success "GET $name with format=$format produces expected Content-Type" '
117
- CID=$(echo "{ \"test\": \"json\" }" | ipfs dag put --input-codec json --store-codec $format) &&
118
- curl -sD- "http://127.0.0.1:$GWAY_PORT/ipfs/$CID?format=$format" > curl_output 2>&1 &&
119
- test_should_contain "Content-Disposition: ${disposition}\; filename=\"${CID}.${format}\"" curl_output &&
120
- test_should_contain "Content-Type: application/$format" curl_output
130
+ # explicit format still gives correct output, just codec in CID
131
+ test_expect_success "GET $name with Accept has expected $format Content-Type and body as-is" '
132
+ CID=$(echo "{ \"test\": \"plain json\" }" | ipfs dag put --input-codec json --store-codec $format) &&
133
+ curl -sD headers -H "Accept: application/$format" "http://127.0.0.1:$GWAY_PORT/ipfs/$CID" > curl_output 2>&1 &&
134
+ ipfs block get $CID > ipfs_block_output 2>&1 &&
135
+ test_cmp ipfs_block_output curl_output &&
136
+ test_should_contain "Content-Disposition: ${disposition}\; filename=\"${CID}.${format}\"" headers &&
137
+ test_should_contain "Content-Type: application/$format" headers
138
'
139
123
- test_expect_success "GET $name with format=$format produces correct output" '
124
- CID=$(echo "{ \"test\": \"json\" }" | ipfs dag put --input-codec json --store-codec $format) &&
125
- curl -s "http://127.0.0.1:$GWAY_PORT/ipfs/$CID?format=$format" > curl_output 2>&1 &&
126
- ipfs dag get --output-codec $format $CID > ipfs_dag_get_output 2>&1 &&
127
- test_cmp ipfs_dag_get_output curl_output
128
- '
129
-
130
- test_expect_success "GET $name with format=dag-$format produces expected Content-Type" '
131
- CID=$(echo "{ \"test\": \"json\" }" | ipfs dag put --input-codec json --store-codec $format) &&
132
- curl -sD- "http://127.0.0.1:$GWAY_PORT/ipfs/$CID?format=dag-$format" > curl_output 2>&1 &&
133
- test_should_contain "Content-Disposition: ${disposition}\; filename=\"${CID}.${format}\"" curl_output &&
134
- test_should_contain "Content-Type: application/vnd.ipld.dag-$format" curl_output
135
- '
136
-
137
- test_expect_success "GET $name with format=dag-$format produces correct output" '
138
- CID=$(echo "{ \"test\": \"json\" }" | ipfs dag put --input-codec json --store-codec $format) &&
139
- curl -s "http://127.0.0.1:$GWAY_PORT/ipfs/$CID?format=dag-$format" > curl_output 2>&1 &&
140
+ # explicit dag-* format passed, attempt to parse as dag* variant
141
+ ## Note: this works only for simple JSON that can be upgraded to DAG-JSON.
142
+ test_expect_success "GET $name with format=dag-$format interprets $format as dag-* variant and produces expected Content-Type and body" '
143
+ CID=$(echo "{ \"test\": \"plain-json-that-can-also-be-dag-json\" }" | ipfs dag put --input-codec json --store-codec $format) &&
144
+ curl -sD headers "http://127.0.0.1:$GWAY_PORT/ipfs/$CID?format=dag-$format" > curl_output_param 2>&1 &&
145
ipfs dag get --output-codec dag-$format $CID > ipfs_dag_get_output 2>&1 &&
141
- test_cmp ipfs_dag_get_output curl_output
146
+ test_cmp ipfs_dag_get_output curl_output_param &&
147
+ test_should_contain "Content-Disposition: ${disposition}\; filename=\"${CID}.${format}\"" headers &&
148
+ test_should_contain "Content-Type: application/vnd.ipld.dag-$format" headers &&
149
+ curl -s -H "Accept: application/vnd.ipld.dag-$format" "http://127.0.0.1:$GWAY_PORT/ipfs/$CID" > curl_output_accept 2>&1 &&
150
+ test_cmp curl_output_param curl_output_accept
151
'
143
-}
144
-
145
-test_cmp_dag_get "JSON" "json" "inline"
146
-test_cmp_dag_get "CBOR" "cbor" "attachment"
147
-
152
149
-## Lossless conversion between JSON and CBOR
150
-
151
-test_expect_success "GET JSON as CBOR produces DAG-CBOR output" '
152
- CID=$(echo "{ \"test\": \"json\" }" | ipfs dag put --input-codec json --store-codec json) &&
153
- curl -s "http://127.0.0.1:$GWAY_PORT/ipfs/$CID?format=cbor" > curl_output 2>&1 &&
154
- ipfs dag get --output-codec dag-cbor $CID > ipfs_dag_get_output 2>&1 &&
155
- test_cmp ipfs_dag_get_output curl_output
156
-'
157
-
158
-test_expect_success "GET CBOR as JSON produces DAG-JSON output" '
159
- CID=$(echo "{ \"test\": \"json\" }" | ipfs dag put --input-codec json --store-codec cbor) &&
160
- curl -s "http://127.0.0.1:$GWAY_PORT/ipfs/$CID?format=json" > curl_output 2>&1 &&
161
- ipfs dag get --output-codec dag-json $CID > ipfs_dag_get_output 2>&1 &&
162
- test_cmp ipfs_dag_get_output curl_output
163
-'
153
+}
154
155
+test_plain_codec "plain JSON codec" "json" "inline"
156
+test_plain_codec "plain CBOR codec" "cbor" "attachment"
157
166
-## Pathing, traversal
158
+## Pathing, traversal over DAG-JSON and DAG-CBOR
159
160
DAG_CBOR_TRAVERSAL_CID="bafyreibs4utpgbn7uqegmd2goqz4bkyflre2ek2iwv743fhvylwi4zeeim"
161
DAG_JSON_TRAVERSAL_CID="baguqeeram5ujjqrwheyaty3w5gdsmoz6vittchvhk723jjqxk7hakxkd47xq"
@@ -204,17 +196,9 @@ test_expect_success "GET DAG-CBOR traverses multiple links" '
196
test_cmp expected actual
197
'
198
207
-# test_expect_success "GET DAG-PB has expected output" '
208
-# curl -s "http://127.0.0.1:$GWAY_PORT/ipfs/$DAG_PB_CID?format=dag-json" > curl_output 2>&1 &&
209
-# jq --sort-keys . curl_output > actual &&
210
-# test_cmp ../t0123-gateway-json-cbor/dag-pb.json actual
211
-# '
212
-
213
-
214
-## NATIVE TESTS:
199
+## NATIVE TESTS for DAG-JSON (0x0129) and DAG-CBOR (0x71):
200
## DAG- regression tests for core behaviors when native DAG-(CBOR|JSON) is requested
201
217
-
202
test_native_dag () {
203
name=$1
204
format=$2
@@ -237,10 +221,10 @@ test_native_dag () {
221
test_cmp expected curl_ipfs_dag_param_output
222
'
223
240
- test_expect_success "GET $name from /ipfs with format=$format returns the same payload as format=dag-$format" '
224
+ test_expect_success "GET $name from /ipfs for application/$format returns the same payload as format=dag-$format" '
225
curl -sX GET "http://127.0.0.1:$GWAY_PORT/ipfs/$CID?format=dag-$format" -o expected &&
242
- curl -sX GET "http://127.0.0.1:$GWAY_PORT/ipfs/$CID?format=dag-$format" -o curl_ipfs_dag_param_output &&
243
- test_cmp expected curl_ipfs_dag_param_output
226
+ curl -sX GET "http://127.0.0.1:$GWAY_PORT/ipfs/$CID?format=$format" -o plain_output &&
227
+ test_cmp expected plain_output
228
'
229
230
test_expect_success "GET $name from /ipfs with application/vnd.ipld.dag-$format returns the same payload as the raw block" '
@@ -249,6 +233,23 @@ test_native_dag () {
233
test_cmp expected_block curl_ipfs_dag_block_accept_output
234
'
235
236
+ # Make sure DAG-* can be requested as plain JSON or CBOR and response has plain Content-Type for interop purposes
237
+
238
+ test_expect_success "GET $name with format=$format returns same payload as format=dag-$format but with plain Content-Type" '
239
+ curl -s "http://127.0.0.1:$GWAY_PORT/ipfs/$CID?format=dag-$format" -o expected &&
240
+ curl -sD plain_headers "http://127.0.0.1:$GWAY_PORT/ipfs/$CID?format=$format" -o plain_output &&
241
+ test_should_contain "Content-Type: application/$format" plain_headers &&
242
+ test_cmp expected plain_output
243
+ '
244
+
245
+ test_expect_success "GET $name with Accept: application/$format returns same payload as application/vnd.ipld.dag-$format but with plain Content-Type" '
246
+ curl -s -H "Accept: application/vnd.ipld.dag-$format" "http://127.0.0.1:$GWAY_PORT/ipfs/$CID" > expected &&
247
+ curl -sD plain_headers -H "Accept: application/$format" "http://127.0.0.1:$GWAY_PORT/ipfs/$CID" > plain_output &&
248
+ test_should_contain "Content-Type: application/$format" plain_headers &&
249
+ test_cmp expected plain_output
250
+ '
251
+
252
+
253
# Make sure expected HTTP headers are returned with the dag- block
254
255
test_expect_success "GET response for application/vnd.ipld.dag-$format has expected Content-Type" '
@@ -302,18 +303,11 @@ test_native_dag () {
303
test_should_contain "Content-Type: application/vnd.ipld.dag-$format" output &&
304
test_should_contain "Content-Length: " output
305
'
305
- test_expect_success "HEAD $name with an explicit JSON format returns HTTP 200" '
306
- curl -I "http://127.0.0.1:$GWAY_PORT/ipfs/$CID?format=json" -o output &&
307
- test_should_contain "HTTP/1.1 200 OK" output &&
308
- test_should_contain "Etag: \"$CID.json\"" output &&
309
- test_should_contain "Content-Type: application/json" output &&
310
- test_should_contain "Content-Length: " output
311
- '
312
- test_expect_success "HEAD dag-pb with ?format=$format returns HTTP 200" '
313
- curl -I "http://127.0.0.1:$GWAY_PORT/ipfs/$FILE_CID?format=$format" -o output &&
306
+ test_expect_success "HEAD $name with an explicit DAG-JSON format returns HTTP 200" '
307
+ curl -I "http://127.0.0.1:$GWAY_PORT/ipfs/$CID?format=dag-json" -o output &&
308
test_should_contain "HTTP/1.1 200 OK" output &&
315
- test_should_contain "Etag: \"$FILE_CID.$format\"" output &&
316
- test_should_contain "Content-Type: application/$format" output &&
309
+ test_should_contain "Etag: \"$CID.dag-json\"" output &&
310
+ test_should_contain "Content-Type: application/vnd.ipld.dag-json" output &&
311
test_should_contain "Content-Length: " output
312
'
313
test_expect_success "HEAD $name with only-if-cached for missing block returns HTTP 412 Precondition Failed" '