@@ -16,8 +16,8 @@ package ocirequest
1616
1717import (
1818 "encoding/base64"
19- "errors"
2019 "fmt"
20+ "net/http"
2121 "net/url"
2222 "strconv"
2323 "strings"
@@ -27,27 +27,20 @@ import (
2727 "cuelabs.dev/go/oci/ociregistry/ociref"
2828)
2929
30- // ParseError represents an error that can happen when parsing.
31- // The Err field holds one of the possible error values below.
32- type ParseError struct {
33- Err error
34- }
30+ var (
31+ errBadlyFormedDigest = ociregistry . NewError ( "badly formed digest" , ociregistry . ErrDigestInvalid . Code (), nil )
32+ errMethodNotAllowed = httpErrorf ( http . StatusMethodNotAllowed , "method not allowed" )
33+ errNotFound = httpErrorf ( http . StatusNotFound , "page not found" )
34+ )
3535
36- func ( e * ParseError ) Error () string {
37- return e . Err . Error ( )
36+ func badRequestf ( f string , a ... any ) error {
37+ return httpErrorf ( http . StatusBadRequest , f , a ... )
3838}
3939
40- func ( e * ParseError ) Unwrap ( ) error {
41- return e . Err
40+ func httpErrorf ( statusCode int , f string , a ... any ) error {
41+ return ociregistry . NewHTTPError ( fmt . Errorf ( f , a ... ), statusCode , nil , nil )
4242}
4343
44- var (
45- ErrNotFound = errors .New ("page not found" )
46- ErrBadlyFormedDigest = errors .New ("badly formed digest" )
47- ErrMethodNotAllowed = errors .New ("method not allowed" )
48- ErrBadRequest = errors .New ("bad request" )
49- )
50-
5144type Request struct {
5245 Kind Kind
5346
@@ -98,17 +91,22 @@ type Request struct {
9891 // Valid for:
9992 // ReqTagsList
10093 // ReqCatalog
101- // ReqReferrers
10294 ListN int
10395
104- // listLast holds the item to start just after
96+ // ListLast holds the item to start just after
10597 // when listing.
10698 //
10799 // Valid for:
108100 // ReqTagsList
109101 // ReqCatalog
110- // ReqReferrers
111102 ListLast string
103+
104+ // ArtifactType holds the artifact type to filter by when
105+ // listing.
106+ //
107+ // Valid for:
108+ // ReqReferrersList
109+ ArtifactType string
112110}
113111
114112type Kind int
@@ -185,22 +183,14 @@ const (
185183// Parse parses the given HTTP method and URL as an OCI registry request.
186184// It understands the endpoints described in the [distribution spec].
187185//
188- // If it returns an error, it will be of type *ParseError .
186+ // If it returns an error, it will be of type [ociregistry.Error] or [ociregistry.HTTPError] .
189187//
190188// [distribution spec]: https://github.com/opencontainers/distribution-spec/blob/main/spec.md#endpoints
191189func Parse (method string , u * url.URL ) (* Request , error ) {
192- req , err := parse (method , u )
193- if err != nil {
194- return nil , & ParseError {err }
195- }
196- return req , nil
197- }
198-
199- func parse (method string , u * url.URL ) (* Request , error ) {
200190 path := u .Path
201191 urlq , err := url .ParseQuery (u .RawQuery )
202192 if err != nil {
203- return nil , err
193+ return nil , badRequestf ( "invalid query parameters: %v" , err )
204194 }
205195
206196 var rreq Request
@@ -214,7 +204,7 @@ func parse(method string, u *url.URL) (*Request, error) {
214204 }
215205 if path == "_catalog" {
216206 if method != "GET" {
217- return nil , ErrMethodNotAllowed
207+ return nil , errMethodNotAllowed
218208 }
219209 rreq .Kind = ReqCatalogList
220210 setListQueryParams (& rreq , urlq )
@@ -230,7 +220,7 @@ func parse(method string, u *url.URL) (*Request, error) {
230220 return nil , ociregistry .ErrNameInvalid
231221 }
232222 if method != "POST" {
233- return nil , ErrMethodNotAllowed
223+ return nil , errMethodNotAllowed
234224 }
235225 if d := urlq .Get ("mount" ); d != "" {
236226 // end-11
@@ -257,7 +247,7 @@ func parse(method string, u *url.URL) (*Request, error) {
257247 // end-4b
258248 rreq .Digest = d
259249 if ! ociref .IsValidDigest (d ) {
260- return nil , ErrBadlyFormedDigest
250+ return nil , errBadlyFormedDigest
261251 }
262252 rreq .Kind = ReqBlobUploadBlob
263253 return & rreq , nil
@@ -268,17 +258,17 @@ func parse(method string, u *url.URL) (*Request, error) {
268258 }
269259 path , last , ok := cutLast (path , "/" )
270260 if ! ok {
271- return nil , ErrNotFound
261+ return nil , errNotFound
272262 }
273263 path , lastButOne , ok := cutLast (path , "/" )
274264 if ! ok {
275- return nil , ErrNotFound
265+ return nil , errNotFound
276266 }
277267 switch lastButOne {
278268 case "blobs" :
279269 rreq .Repo = path
280270 if ! ociref .IsValidDigest (last ) {
281- return nil , ErrBadlyFormedDigest
271+ return nil , errBadlyFormedDigest
282272 }
283273 if ! ociref .IsValidRepository (rreq .Repo ) {
284274 return nil , ociregistry .ErrNameInvalid
@@ -292,30 +282,30 @@ func parse(method string, u *url.URL) (*Request, error) {
292282 case "DELETE" :
293283 rreq .Kind = ReqBlobDelete
294284 default :
295- return nil , ErrMethodNotAllowed
285+ return nil , errMethodNotAllowed
296286 }
297287 return & rreq , nil
298288 case "uploads" :
299289 // Note: this section is all specific to ociserver and
300290 // isn't part of the OCI registry spec.
301291 repo , ok := strings .CutSuffix (path , "/blobs" )
302292 if ! ok {
303- return nil , ErrNotFound
293+ return nil , errNotFound
304294 }
305295 rreq .Repo = repo
306296 if ! ociref .IsValidRepository (rreq .Repo ) {
307297 return nil , ociregistry .ErrNameInvalid
308298 }
309299 uploadID64 := last
310300 if uploadID64 == "" {
311- return nil , ErrNotFound
301+ return nil , errNotFound
312302 }
313303 uploadID , err := base64 .RawURLEncoding .DecodeString (uploadID64 )
314304 if err != nil {
315- return nil , fmt . Errorf ("invalid upload ID %q (cannot decode)" , uploadID64 )
305+ return nil , badRequestf ("invalid upload ID %q (cannot decode)" , uploadID64 )
316306 }
317307 if ! utf8 .Valid (uploadID ) {
318- return nil , fmt . Errorf ("upload ID %q decoded to invalid utf8" , uploadID64 )
308+ return nil , badRequestf ("upload ID %q decoded to invalid utf8" , uploadID64 )
319309 }
320310 rreq .UploadID = string (uploadID )
321311
@@ -328,10 +318,10 @@ func parse(method string, u *url.URL) (*Request, error) {
328318 rreq .Kind = ReqBlobCompleteUpload
329319 rreq .Digest = urlq .Get ("digest" )
330320 if ! ociref .IsValidDigest (rreq .Digest ) {
331- return nil , ErrBadlyFormedDigest
321+ return nil , errBadlyFormedDigest
332322 }
333323 default :
334- return nil , ErrMethodNotAllowed
324+ return nil , errMethodNotAllowed
335325 }
336326 return & rreq , nil
337327 case "manifests" :
@@ -345,7 +335,7 @@ func parse(method string, u *url.URL) (*Request, error) {
345335 case ociref .IsValidTag (last ):
346336 rreq .Tag = last
347337 default :
348- return nil , ErrNotFound
338+ return nil , errNotFound
349339 }
350340 switch method {
351341 case "GET" :
@@ -357,19 +347,19 @@ func parse(method string, u *url.URL) (*Request, error) {
357347 case "DELETE" :
358348 rreq .Kind = ReqManifestDelete
359349 default :
360- return nil , ErrMethodNotAllowed
350+ return nil , errMethodNotAllowed
361351 }
362352 return & rreq , nil
363353
364354 case "tags" :
365355 if last != "list" {
366- return nil , ErrNotFound
356+ return nil , errNotFound
367357 }
368358 if err := setListQueryParams (& rreq , urlq ); err != nil {
369359 return nil , err
370360 }
371361 if method != "GET" {
372- return nil , ErrMethodNotAllowed
362+ return nil , errMethodNotAllowed
373363 }
374364 rreq .Repo = path
375365 if ! ociref .IsValidRepository (rreq .Repo ) {
@@ -379,31 +369,32 @@ func parse(method string, u *url.URL) (*Request, error) {
379369 return & rreq , nil
380370 case "referrers" :
381371 if ! ociref .IsValidDigest (last ) {
382- return nil , ErrBadlyFormedDigest
372+ return nil , errBadlyFormedDigest
383373 }
384374 if method != "GET" {
385- return nil , ErrMethodNotAllowed
375+ return nil , errMethodNotAllowed
386376 }
387377 rreq .Repo = path
388378 if ! ociref .IsValidRepository (rreq .Repo ) {
389379 return nil , ociregistry .ErrNameInvalid
390380 }
391- // TODO is there any kind of pagination for referrers?
392- // We'll set ListN to be future-proof.
381+ // Unlike other list-oriented endpoints, there appears to be no defined way for the client
382+ // to indicate the desired number of results, but set ListN anyway to be future-proof.
393383 rreq .ListN = - 1
394384 rreq .Digest = last
385+ rreq .ArtifactType = urlq .Get ("artifactType" )
395386 rreq .Kind = ReqReferrersList
396387 return & rreq , nil
397388 }
398- return nil , ErrNotFound
389+ return nil , errNotFound
399390}
400391
401392func setListQueryParams (rreq * Request , urlq url.Values ) error {
402393 rreq .ListN = - 1
403394 if nstr := urlq .Get ("n" ); nstr != "" {
404395 n , err := strconv .Atoi (nstr )
405396 if err != nil {
406- return fmt . Errorf ( " n is not a valid integer: %w" , ErrBadRequest )
397+ return badRequestf ( "query parameter n is not a valid integer" )
407398 }
408399 rreq .ListN = n
409400 }
0 commit comments