blob: b70f1d5ce53d13d27431515b5c9f687953644f86 [file] [log] [blame]
David K. Bainbridge528b3182017-01-23 08:51:59 -08001// Copyright 2016 Canonical Ltd.
2// Licensed under the LGPLv3, see LICENCE file for details.
3
4package utils
5
6import (
7 "strings"
8
9 "github.com/juju/errors"
10)
11
12// RelativeURLPath returns a relative URL path that is lexically
13// equivalent to targpath when interpreted by url.URL.ResolveReference.
14// On success, the returned path will always be non-empty and relative
15// to basePath, even if basePath and targPath share no elements.
16//
17// It is assumed that both basePath and targPath are normalized
18// (have no . or .. elements).
19//
20// An error is returned if basePath or targPath are not absolute paths.
21func RelativeURLPath(basePath, targPath string) (string, error) {
22 if !strings.HasPrefix(basePath, "/") {
23 return "", errors.New("non-absolute base URL")
24 }
25 if !strings.HasPrefix(targPath, "/") {
26 return "", errors.New("non-absolute target URL")
27 }
28 baseParts := strings.Split(basePath, "/")
29 targParts := strings.Split(targPath, "/")
30
31 // For the purposes of dotdot, the last element of
32 // the paths are irrelevant. We save the last part
33 // of the target path for later.
34 lastElem := targParts[len(targParts)-1]
35 baseParts = baseParts[0 : len(baseParts)-1]
36 targParts = targParts[0 : len(targParts)-1]
37
38 // Find the common prefix between the two paths:
39 var i int
40 for ; i < len(baseParts); i++ {
41 if i >= len(targParts) || baseParts[i] != targParts[i] {
42 break
43 }
44 }
45 dotdotCount := len(baseParts) - i
46 targOnly := targParts[i:]
47 result := make([]string, 0, dotdotCount+len(targOnly)+1)
48 for i := 0; i < dotdotCount; i++ {
49 result = append(result, "..")
50 }
51 result = append(result, targOnly...)
52 result = append(result, lastElem)
53 final := strings.Join(result, "/")
54 if final == "" {
55 // If the final result is empty, the last element must
56 // have been empty, so the target was slash terminated
57 // and there were no previous elements, so "."
58 // is appropriate.
59 final = "."
60 }
61 return final, nil
62}