Skip to content

Commit 3548cfe

Browse files
committed
feat(gateway): add AllowCodecConversion config option
Add AllowCodecConversion to gateway.Config to control codec conversion behavior per IPIP-0524. When false (default), the gateway returns 406 Not Acceptable if the requested format doesn't match the block's codec. When true, conversions between codecs are performed for backward compatibility. Codec conversion tests moved here from gateway-conformance since conversions are now an optional implementation feature, not a spec requirement. Gateway-conformance now tests for 406 responses. Ref: ipfs/specs#524 Ref: ipfs/gateway-conformance#254
1 parent 0842ad2 commit 3548cfe

File tree

5 files changed

+85
-3
lines changed

5 files changed

+85
-3
lines changed

gateway/gateway.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@ type Config struct {
5252
// [Trustless Gateway]: https://specs.ipfs.tech/http-gateways/trustless-gateway/
5353
DeserializedResponses bool
5454

55+
// AllowCodecConversion enables automatic conversion between codecs when
56+
// the requested format differs from the block's native codec. For example,
57+
// converting dag-pb (UnixFS) to dag-json.
58+
//
59+
// When false (default), the gateway returns 406 Not Acceptable if the
60+
// requested format doesn't match the block's codec. This follows the
61+
// behavior specified in IPIP-0524.
62+
//
63+
// When true, the gateway attempts to convert between legacy IPLD formats.
64+
// This is provided for backwards compatibility but is not required by
65+
// the gateway specification.
66+
AllowCodecConversion bool
67+
5568
// NoDNSLink configures the gateway to _not_ perform DNS TXT record lookups in
5669
// response to requests with values in `Host` HTTP header. This flag can be
5770
// overridden per FQDN in PublicGateways. To be used with WithHostname.

gateway/gateway_test.go

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,7 @@ func TestHeaders(t *testing.T) {
521521
},
522522
},
523523
DeserializedResponses: true,
524+
AllowCodecConversion: true, // Test tests various format conversions
524525
})
525526

526527
runTest := func(name, path, accept, host, expectedContentLocationHdr string) {
@@ -1073,7 +1074,8 @@ func TestDeserializedResponses(t *testing.T) {
10731074
backend, root := newMockBackend(t, "fixtures.car")
10741075

10751076
ts := newTestServerWithConfig(t, backend, Config{
1076-
NoDNSLink: false,
1077+
NoDNSLink: false,
1078+
AllowCodecConversion: true, // Test expects codec conversions to work
10771079
PublicGateways: map[string]*PublicGateway{
10781080
"trustless.com": {
10791081
Paths: []string{"/ipfs", "/ipns"},
@@ -1152,7 +1154,8 @@ func TestDeserializedResponses(t *testing.T) {
11521154
backend.namesys["/ipns/trusted.com"] = newMockNamesysItem(path.FromCid(root), 0)
11531155

11541156
ts := newTestServerWithConfig(t, backend, Config{
1155-
NoDNSLink: false,
1157+
NoDNSLink: false,
1158+
AllowCodecConversion: true, // Test expects codec conversions to work
11561159
PublicGateways: map[string]*PublicGateway{
11571160
"trustless.com": {
11581161
Paths: []string{"/ipfs", "/ipns"},
@@ -1186,6 +1189,58 @@ func TestDeserializedResponses(t *testing.T) {
11861189
})
11871190
}
11881191

1192+
func TestAllowCodecConversion(t *testing.T) {
1193+
t.Parallel()
1194+
1195+
// Use dag-cbor fixture
1196+
backend, dagCborRoot := newMockBackend(t, "path_gateway_dag/dag-cbor-traversal.car")
1197+
1198+
t.Run("AllowCodecConversion=false returns 406 for codec mismatch", func(t *testing.T) {
1199+
t.Parallel()
1200+
1201+
ts := newTestServerWithConfig(t, backend, Config{
1202+
DeserializedResponses: true,
1203+
AllowCodecConversion: false, // IPIP-0524 behavior
1204+
})
1205+
1206+
// Request dag-json for a dag-cbor block - should return 406
1207+
req := mustNewRequest(t, http.MethodGet, ts.URL+"/ipfs/"+dagCborRoot.String()+"?format=dag-json", nil)
1208+
res := mustDoWithoutRedirect(t, req)
1209+
defer res.Body.Close()
1210+
assert.Equal(t, http.StatusNotAcceptable, res.StatusCode)
1211+
})
1212+
1213+
t.Run("AllowCodecConversion=false allows matching codec", func(t *testing.T) {
1214+
t.Parallel()
1215+
1216+
ts := newTestServerWithConfig(t, backend, Config{
1217+
DeserializedResponses: true,
1218+
AllowCodecConversion: false, // IPIP-0524 behavior
1219+
})
1220+
1221+
// Request dag-cbor for a dag-cbor block - should return 200
1222+
req := mustNewRequest(t, http.MethodGet, ts.URL+"/ipfs/"+dagCborRoot.String()+"?format=dag-cbor", nil)
1223+
res := mustDoWithoutRedirect(t, req)
1224+
defer res.Body.Close()
1225+
assert.Equal(t, http.StatusOK, res.StatusCode)
1226+
})
1227+
1228+
t.Run("AllowCodecConversion=true allows codec conversion", func(t *testing.T) {
1229+
t.Parallel()
1230+
1231+
ts := newTestServerWithConfig(t, backend, Config{
1232+
DeserializedResponses: true,
1233+
AllowCodecConversion: true, // Legacy behavior
1234+
})
1235+
1236+
// Request dag-json for a dag-cbor block - should return 200 with conversion
1237+
req := mustNewRequest(t, http.MethodGet, ts.URL+"/ipfs/"+dagCborRoot.String()+"?format=dag-json", nil)
1238+
res := mustDoWithoutRedirect(t, req)
1239+
defer res.Body.Close()
1240+
assert.Equal(t, http.StatusOK, res.StatusCode)
1241+
})
1242+
}
1243+
11891244
type errorMockBackend struct {
11901245
err error
11911246
}

gateway/handler_codec.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,20 @@ func (i *handler) renderCodec(ctx context.Context, w http.ResponseWriter, r *htt
148148
return false
149149
}
150150

151-
// This handles DAG-* conversions and validations.
151+
// IPIP-0524: Check if codec conversion is allowed
152+
if !i.config.AllowCodecConversion && toCodec != cidCodec {
153+
// Conversion not allowed and codecs don't match - return 406
154+
err := fmt.Errorf("format %q requested but block has codec %q: codec conversion is not supported", rq.responseFormat, cidCodec.String())
155+
i.webError(w, r, err, http.StatusNotAcceptable)
156+
return false
157+
}
158+
159+
// If codecs match, serve raw (no conversion needed)
160+
if toCodec == cidCodec {
161+
return i.serveCodecRaw(ctx, w, r, blockSize, blockData, rq.contentPath, modtime, rq.begin)
162+
}
163+
164+
// AllowCodecConversion is true - perform DAG-* conversion
152165
return i.serveCodecConverted(ctx, w, r, blockCid, blockData, rq.contentPath, toCodec, modtime, rq.begin)
153166
}
154167

318 Bytes
Binary file not shown.

gateway/utilities_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ func newTestServerAndNode(t *testing.T, fixturesFile string) (*httptest.Server,
234234
func newTestServer(t *testing.T, backend IPFSBackend) *httptest.Server {
235235
return newTestServerWithConfig(t, backend, Config{
236236
DeserializedResponses: true,
237+
AllowCodecConversion: true, // Enable for backwards compatibility in tests
237238
MetricsRegistry: prometheus.NewRegistry(),
238239
})
239240
}

0 commit comments

Comments
 (0)