...
1 package handlers
2
3 import (
4 "encoding/json"
5 "fmt"
6 "io"
7 "net/http"
8 "net/url"
9 "strconv"
10
11 "github.com/docker/distribution/registry/api/errcode"
12 v2 "github.com/docker/distribution/registry/api/v2"
13 "github.com/docker/distribution/registry/storage/driver"
14
15 "github.com/gorilla/handlers"
16 )
17
18 const defaultReturnedEntries = 100
19
20 func catalogDispatcher(ctx *Context, r *http.Request) http.Handler {
21 catalogHandler := &catalogHandler{
22 Context: ctx,
23 }
24
25 return handlers.MethodHandler{
26 "GET": http.HandlerFunc(catalogHandler.GetCatalog),
27 }
28 }
29
30 type catalogHandler struct {
31 *Context
32 }
33
34 type catalogAPIResponse struct {
35 Repositories []string `json:"repositories"`
36 }
37
38 func (ch *catalogHandler) GetCatalog(w http.ResponseWriter, r *http.Request) {
39 var moreEntries = true
40
41 q := r.URL.Query()
42 lastEntry := q.Get("last")
43
44 entries := defaultReturnedEntries
45 maximumConfiguredEntries := ch.App.Config.Catalog.MaxEntries
46
47
48 if n := q.Get("n"); n != "" {
49 parsedMax, err := strconv.Atoi(n)
50 if err == nil {
51 if parsedMax > maximumConfiguredEntries {
52 ch.Errors = append(ch.Errors, v2.ErrorCodePaginationNumberInvalid.WithDetail(map[string]int{"n": parsedMax}))
53 return
54 } else if parsedMax >= 0 {
55 entries = parsedMax
56 }
57 }
58 }
59
60
61
62 if entries < 0 || entries > maximumConfiguredEntries {
63 entries = maximumConfiguredEntries
64 }
65
66 repos := make([]string, entries)
67 filled := 0
68
69
70 if entries == 0 {
71 moreEntries = false
72 } else {
73 returnedRepositories, err := ch.App.registry.Repositories(ch.Context, repos, lastEntry)
74 if err != nil {
75 _, pathNotFound := err.(driver.PathNotFoundError)
76 if err != io.EOF && !pathNotFound {
77 ch.Errors = append(ch.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
78 return
79 }
80
81 moreEntries = false
82 }
83 filled = returnedRepositories
84 }
85
86 w.Header().Set("Content-Type", "application/json; charset=utf-8")
87
88
89 if moreEntries {
90 lastEntry = repos[filled-1]
91 urlStr, err := createLinkEntry(r.URL.String(), entries, lastEntry)
92 if err != nil {
93 ch.Errors = append(ch.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
94 return
95 }
96 w.Header().Set("Link", urlStr)
97 }
98
99 enc := json.NewEncoder(w)
100 if err := enc.Encode(catalogAPIResponse{
101 Repositories: repos[0:filled],
102 }); err != nil {
103 ch.Errors = append(ch.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
104 return
105 }
106 }
107
108
109
110 func createLinkEntry(origURL string, maxEntries int, lastEntry string) (string, error) {
111 calledURL, err := url.Parse(origURL)
112 if err != nil {
113 return "", err
114 }
115
116 v := url.Values{}
117 v.Add("n", strconv.Itoa(maxEntries))
118 v.Add("last", lastEntry)
119
120 calledURL.RawQuery = v.Encode()
121
122 calledURL.Fragment = ""
123 urlStr := fmt.Sprintf("<%s>; rel=\"next\"", calledURL.String())
124
125 return urlStr, nil
126 }
127
View as plain text