...

Source file src/github.com/docker/distribution/registry/api/v2/urls.go

Documentation: github.com/docker/distribution/registry/api/v2

     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  // URLBuilder creates registry API urls from a single base endpoint. It can be
    14  // used to create urls for use in a registry client or server.
    15  //
    16  // All urls will be created from the given base, including the api version.
    17  // For example, if a root of "/foo/" is provided, urls generated will be fall
    18  // under "/foo/v2/...". Most application will only provide a schema, host and
    19  // port, such as "https://localhost:5000/".
    20  type URLBuilder struct {
    21  	root     *url.URL // url root (ie http://localhost/)
    22  	router   *mux.Router
    23  	relative bool
    24  }
    25  
    26  // NewURLBuilder creates a URLBuilder with provided root url object.
    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  // NewURLBuilderFromString workes identically to NewURLBuilder except it takes
    36  // a string argument for the root, returning an error if it is not a valid
    37  // url.
    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  // NewURLBuilderFromRequest uses information from an *http.Request to
    48  // construct the root url.
    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  	// Handle fowarded headers
    62  	// Prefer "Forwarded" header as defined by rfc7239 if given
    63  	// see https://tools.ietf.org/html/rfc7239
    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  			// According to the Apache mod_proxy docs, X-Forwarded-Host can be a
    80  			// comma-separated list of hosts, to which each proxy appends the
    81  			// requested host. We want to grab the first from this comma-separated
    82  			// list.
    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  		// N.B. index+1 is important because we want to include the trailing /
   100  		u.Path = requestPath[0 : index+1]
   101  	}
   102  
   103  	return NewURLBuilder(u, relative)
   104  }
   105  
   106  // BuildBaseURL constructs a base url for the API, typically just "/v2/".
   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  // BuildCatalogURL constructs a url get a catalog of repositories
   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  // BuildTagsURL constructs a url to list the tags in the named repository.
   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  // BuildManifestURL constructs a url for the manifest identified by name and
   143  // reference. The argument reference may be either a tag or digest.
   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  // BuildBlobURL constructs the url for the blob identified by name and dgst.
   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  // BuildBlobUploadURL constructs a url to begin a blob upload in the
   178  // repository identified by name.
   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  // BuildBlobUploadChunkURL constructs a url for the upload identified by uuid,
   191  // including any url values. This should generally not be used by clients, as
   192  // this url is provided by server implementations during the blob upload
   193  // process.
   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  // clondedRoute returns a clone of the named route from the router. Routes
   206  // must be cloned to avoid modifying them during url generation.
   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) // clone the route
   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  // appendValuesURL appends the parameters to the url.
   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