@@ -30,16 +30,7 @@ func (i *handler) serveCAR(ctx context.Context, w http.ResponseWriter, r *http.R
3030 ctx , cancel := context .WithCancel (ctx )
3131 defer cancel ()
3232
33- switch rq .responseParams ["version" ] {
34- case "" : // noop, client does not care about version
35- case "1" : // noop, we support this
36- default :
37- err := fmt .Errorf ("unsupported CAR version: only version=1 is supported" )
38- i .webError (w , r , err , http .StatusBadRequest )
39- return false
40- }
41-
42- params , err := getCarParams (r , rq .responseParams )
33+ params , err := buildCarParams (r , rq .responseParams )
4334 if err != nil {
4435 i .webError (w , r , err , http .StatusBadRequest )
4536 return false
@@ -90,7 +81,7 @@ func (i *handler) serveCAR(ctx context.Context, w http.ResponseWriter, r *http.R
9081 // sub-DAGs and IPLD selectors: https://github.com/ipfs/go-ipfs/issues/8769
9182 w .Header ().Set ("Accept-Ranges" , "none" )
9283
93- w .Header ().Set ("Content-Type" , getContentTypeFromCarParams (params ))
84+ w .Header ().Set ("Content-Type" , buildContentTypeFromCarParams (params ))
9485 w .Header ().Set ("X-Content-Type-Options" , "nosniff" ) // no funny business in the browsers :^)
9586
9687 _ , copyErr := io .Copy (w , carFile )
@@ -113,7 +104,15 @@ func (i *handler) serveCAR(ctx context.Context, w http.ResponseWriter, r *http.R
113104 return true
114105}
115106
116- func getCarParams (r * http.Request , formatParams map [string ]string ) (* CarParams , error ) {
107+ // buildCarParams returns CarParams based on the request, any optional parameters
108+ // passed in URL, Accept header and the implicit defaults specific to boxo
109+ // implementation, such as block order and duplicates status.
110+ //
111+ // If any of the optional content type parameters (e.g., CAR order or
112+ // duplicates) are unspecified or empty, the function will automatically infer
113+ // default values.
114+ func buildCarParams (r * http.Request , contentTypeParams map [string ]string ) (CarParams , error ) {
115+ // URL query parameters
117116 queryParams := r .URL .Query ()
118117 rangeStr , hasRange := queryParams .Get (carRangeBytesKey ), queryParams .Has (carRangeBytesKey )
119118 scopeStr , hasScope := queryParams .Get (carTerminalElementTypeKey ), queryParams .Has (carTerminalElementTypeKey )
@@ -123,7 +122,7 @@ func getCarParams(r *http.Request, formatParams map[string]string) (*CarParams,
123122 rng , err := NewDagByteRange (rangeStr )
124123 if err != nil {
125124 err = fmt .Errorf ("invalid application/vnd.ipld.car entity-bytes URL parameter: %w" , err )
126- return nil , err
125+ return CarParams {} , err
127126 }
128127 params .Range = & rng
129128 }
@@ -134,55 +133,72 @@ func getCarParams(r *http.Request, formatParams map[string]string) (*CarParams,
134133 params .Scope = s
135134 default :
136135 err := fmt .Errorf ("unsupported application/vnd.ipld.car dag-scope URL parameter: %q" , scopeStr )
137- return nil , err
136+ return CarParams {} , err
138137 }
139138 } else {
140139 params .Scope = DagScopeAll
141140 }
142141
143- switch order := DagOrder (formatParams ["order" ]); order {
144- case DagOrderUnknown , DagOrderDFS :
145- params .Order = order
146- case "" :
147- params .Order = DagOrderUnknown
148- default :
149- return nil , fmt .Errorf ("unsupported application/vnd.ipld.car content type order parameter: %q" , order )
150- }
151-
152- switch dups := formatParams ["dups" ]; dups {
153- case "y" :
154- v := true
155- params .Duplicates = & v
156- case "n" :
157- v := false
158- params .Duplicates = & v
159- case "" :
160- // Acceptable, we do not set anything.
142+ // application/vnd.ipld.car content type parameters from Accept header
143+
144+ // version of CAR format
145+ switch contentTypeParams ["version" ] {
146+ case "" : // noop, client does not care about version
147+ case "1" : // noop, we support this
161148 default :
162- return nil , fmt .Errorf ("unsupported application/vnd.ipld.car content type dups parameter: %q" , dups )
149+ return CarParams {}, fmt .Errorf ("unsupported application/vnd.ipld.car version: only version=1 is supported" )
150+ }
151+
152+ // optional order from IPIP-412
153+ if order := DagOrder (contentTypeParams ["order" ]); order != DagOrderUnspecified {
154+ switch order {
155+ case DagOrderUnknown , DagOrderDFS :
156+ params .Order = order
157+ default :
158+ return CarParams {}, fmt .Errorf ("unsupported application/vnd.ipld.car content type order parameter: %q" , order )
159+ }
160+ } else {
161+ // when order is not specified, we use DFS as the implicit default
162+ // as this has always been the default behavior and we should not break
163+ // legacy clients
164+ params .Order = DagOrderDFS
165+ }
166+
167+ // optional dups from IPIP-412
168+ if dups := NewDuplicateBlocksPolicy (contentTypeParams ["dups" ]); dups != DuplicateBlocksUnspecified {
169+ switch dups {
170+ case DuplicateBlocksExcluded , DuplicateBlocksIncluded :
171+ params .Duplicates = dups
172+ default :
173+ return CarParams {}, fmt .Errorf ("unsupported application/vnd.ipld.car content type dups parameter: %q" , dups )
174+ }
175+ } else {
176+ // when duplicate block preference is not specified, we set it to
177+ // false, as this has always been the default behavior, we should
178+ // not break legacy clients, and responses to requests made via ?format=car
179+ // should benefit from block deduplication
180+ params .Duplicates = DuplicateBlocksExcluded
181+
163182 }
164183
165- return & params , nil
184+ return params , nil
166185}
167186
168- func getContentTypeFromCarParams (params * CarParams ) string {
187+ // buildContentTypeFromCarParams returns a string for Content-Type header.
188+ // It does not change any values, CarParams are respected as-is.
189+ func buildContentTypeFromCarParams (params CarParams ) string {
169190 h := strings.Builder {}
170191 h .WriteString (carResponseFormat )
171- h .WriteString ("; version=1; order= " )
192+ h .WriteString ("; version=1" )
172193
173- if params .Order != "" {
194+ if params .Order != DagOrderUnspecified {
195+ h .WriteString ("; order=" )
174196 h .WriteString (string (params .Order ))
175- } else {
176- h .WriteString (string (DagOrderUnknown ))
177197 }
178198
179- if params .Duplicates != nil {
199+ if params .Duplicates != DuplicateBlocksUnspecified {
180200 h .WriteString ("; dups=" )
181- if * params .Duplicates {
182- h .WriteString ("y" )
183- } else {
184- h .WriteString ("n" )
185- }
201+ h .WriteString (params .Duplicates .String ())
186202 }
187203
188204 return h .String ()
@@ -209,17 +225,28 @@ func getCarRootCidAndLastSegment(imPath ImmutablePath) (cid.Cid, string, error)
209225 return rootCid , lastSegment , err
210226}
211227
212- func getCarEtag (imPath ImmutablePath , params * CarParams , rootCid cid.Cid ) string {
228+ func getCarEtag (imPath ImmutablePath , params CarParams , rootCid cid.Cid ) string {
213229 data := imPath .String ()
214230 if params .Scope != DagScopeAll {
215- data += "." + string (params .Scope )
231+ data += string (params .Scope )
232+ }
233+
234+ // 'order' from IPIP-412 impact Etag only if set to something else
235+ // than DFS (which is the implicit default)
236+ if params .Order != DagOrderDFS {
237+ data += string (params .Order )
238+ }
239+
240+ // 'dups' from IPIP-412 impact Etag only if 'y'
241+ if dups := params .Duplicates .String (); dups == "y" {
242+ data += dups
216243 }
217244
218245 if params .Range != nil {
219246 if params .Range .From != 0 || params .Range .To != nil {
220- data += "." + strconv .FormatInt (params .Range .From , 10 )
247+ data += strconv .FormatInt (params .Range .From , 10 )
221248 if params .Range .To != nil {
222- data += "." + strconv .FormatInt (* params .Range .To , 10 )
249+ data += strconv .FormatInt (* params .Range .To , 10 )
223250 }
224251 }
225252 }
0 commit comments