1 package v2
2
3 import (
4 "fmt"
5 "net/http"
6 "net/url"
7 "strings"
8
9 "github.com/distribution/reference"
10 "github.com/gorilla/mux"
11 )
12
13
14
15
16
17
18
19
20 type URLBuilder struct {
21 root *url.URL
22 router *mux.Router
23 relative bool
24 }
25
26
27 func NewURLBuilder(root *url.URL, relative bool) *URLBuilder {
28 return &URLBuilder{
29 root: root,
30 router: Router(),
31 relative: relative,
32 }
33 }
34
35
36
37
38 func NewURLBuilderFromString(root string, relative bool) (*URLBuilder, error) {
39 u, err := url.Parse(root)
40 if err != nil {
41 return nil, err
42 }
43
44 return NewURLBuilder(u, relative), nil
45 }
46
47
48
49 func NewURLBuilderFromRequest(r *http.Request, relative bool) *URLBuilder {
50 var (
51 scheme = "http"
52 host = r.Host
53 )
54
55 if r.TLS != nil {
56 scheme = "https"
57 } else if len(r.URL.Scheme) > 0 {
58 scheme = r.URL.Scheme
59 }
60
61
62
63
64 if forwarded := r.Header.Get("Forwarded"); len(forwarded) > 0 {
65 forwardedHeader, _, err := parseForwardedHeader(forwarded)
66 if err == nil {
67 if fproto := forwardedHeader["proto"]; len(fproto) > 0 {
68 scheme = fproto
69 }
70 if fhost := forwardedHeader["host"]; len(fhost) > 0 {
71 host = fhost
72 }
73 }
74 } else {
75 if forwardedProto := r.Header.Get("X-Forwarded-Proto"); len(forwardedProto) > 0 {
76 scheme = forwardedProto
77 }
78 if forwardedHost := r.Header.Get("X-Forwarded-Host"); len(forwardedHost) > 0 {
79
80
81
82
83 hosts := strings.SplitN(forwardedHost, ",", 2)
84 host = strings.TrimSpace(hosts[0])
85 }
86 }
87
88 basePath := routeDescriptorsMap[RouteNameBase].Path
89
90 requestPath := r.URL.Path
91 index := strings.Index(requestPath, basePath)
92
93 u := &url.URL{
94 Scheme: scheme,
95 Host: host,
96 }
97
98 if index > 0 {
99
100 u.Path = requestPath[0 : index+1]
101 }
102
103 return NewURLBuilder(u, relative)
104 }
105
106
107 func (ub *URLBuilder) BuildBaseURL() (string, error) {
108 route := ub.cloneRoute(RouteNameBase)
109
110 baseURL, err := route.URL()
111 if err != nil {
112 return "", err
113 }
114
115 return baseURL.String(), nil
116 }
117
118
119 func (ub *URLBuilder) BuildCatalogURL(values ...url.Values) (string, error) {
120 route := ub.cloneRoute(RouteNameCatalog)
121
122 catalogURL, err := route.URL()
123 if err != nil {
124 return "", err
125 }
126
127 return appendValuesURL(catalogURL, values...).String(), nil
128 }
129
130
131 func (ub *URLBuilder) BuildTagsURL(name reference.Named) (string, error) {
132 route := ub.cloneRoute(RouteNameTags)
133
134 tagsURL, err := route.URL("name", name.Name())
135 if err != nil {
136 return "", err
137 }
138
139 return tagsURL.String(), nil
140 }
141
142
143
144 func (ub *URLBuilder) BuildManifestURL(ref reference.Named) (string, error) {
145 route := ub.cloneRoute(RouteNameManifest)
146
147 tagOrDigest := ""
148 switch v := ref.(type) {
149 case reference.Tagged:
150 tagOrDigest = v.Tag()
151 case reference.Digested:
152 tagOrDigest = v.Digest().String()
153 default:
154 return "", fmt.Errorf("reference must have a tag or digest")
155 }
156
157 manifestURL, err := route.URL("name", ref.Name(), "reference", tagOrDigest)
158 if err != nil {
159 return "", err
160 }
161
162 return manifestURL.String(), nil
163 }
164
165
166 func (ub *URLBuilder) BuildBlobURL(ref reference.Canonical) (string, error) {
167 route := ub.cloneRoute(RouteNameBlob)
168
169 layerURL, err := route.URL("name", ref.Name(), "digest", ref.Digest().String())
170 if err != nil {
171 return "", err
172 }
173
174 return layerURL.String(), nil
175 }
176
177
178
179 func (ub *URLBuilder) BuildBlobUploadURL(name reference.Named, values ...url.Values) (string, error) {
180 route := ub.cloneRoute(RouteNameBlobUpload)
181
182 uploadURL, err := route.URL("name", name.Name())
183 if err != nil {
184 return "", err
185 }
186
187 return appendValuesURL(uploadURL, values...).String(), nil
188 }
189
190
191
192
193
194 func (ub *URLBuilder) BuildBlobUploadChunkURL(name reference.Named, uuid string, values ...url.Values) (string, error) {
195 route := ub.cloneRoute(RouteNameBlobUploadChunk)
196
197 uploadURL, err := route.URL("name", name.Name(), "uuid", uuid)
198 if err != nil {
199 return "", err
200 }
201
202 return appendValuesURL(uploadURL, values...).String(), nil
203 }
204
205
206
207 func (ub *URLBuilder) cloneRoute(name string) clonedRoute {
208 route := new(mux.Route)
209 root := new(url.URL)
210
211 *route = *ub.router.GetRoute(name)
212 *root = *ub.root
213
214 return clonedRoute{Route: route, root: root, relative: ub.relative}
215 }
216
217 type clonedRoute struct {
218 *mux.Route
219 root *url.URL
220 relative bool
221 }
222
223 func (cr clonedRoute) URL(pairs ...string) (*url.URL, error) {
224 routeURL, err := cr.Route.URL(pairs...)
225 if err != nil {
226 return nil, err
227 }
228
229 if cr.relative {
230 return routeURL, nil
231 }
232
233 if routeURL.Scheme == "" && routeURL.User == nil && routeURL.Host == "" {
234 routeURL.Path = routeURL.Path[1:]
235 }
236
237 url := cr.root.ResolveReference(routeURL)
238 url.Scheme = cr.root.Scheme
239 return url, nil
240 }
241
242
243 func appendValuesURL(u *url.URL, values ...url.Values) *url.URL {
244 merged := u.Query()
245
246 for _, v := range values {
247 for k, vv := range v {
248 merged[k] = append(merged[k], vv...)
249 }
250 }
251
252 u.RawQuery = merged.Encode()
253 return u
254 }
255
View as plain text