github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/url/parse_docker.go (about) 1 package url 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 8 "github.com/mutagen-io/mutagen/pkg/url/forwarding" 9 ) 10 11 // dockerURLPrefix is the lowercase version of the Docker URL prefix. 12 const dockerURLPrefix = "docker://" 13 14 // DockerEnvironmentVariables is a list of Docker environment variables that 15 // should be locked in to Docker URLs at parse time. 16 var DockerEnvironmentVariables = []string{ 17 "DOCKER_HOST", 18 "DOCKER_TLS", // This flag is not documented, but is supported by Docker. 19 "DOCKER_TLS_VERIFY", 20 "DOCKER_CERT_PATH", 21 "DOCKER_CONTEXT", 22 "DOCKER_CONFIG", // This flag is not documented, but is supported by Docker. 23 "DOCKER_API_VERSION", 24 } 25 26 // dockerParameterNames is a list of supported Docker command line parameters. 27 var dockerParameterNames = []string{ 28 "config", 29 "context", 30 "host", 31 "tls", 32 "tlscacert", 33 "tlscert", 34 "tlskey", 35 "tlsverify", 36 } 37 38 // isDockerURL checks whether or not a URL is a Docker URL. It requires the 39 // presence of a Docker protocol prefix. 40 func isDockerURL(raw string) bool { 41 return strings.HasPrefix(strings.ToLower(raw), dockerURLPrefix) 42 } 43 44 // parseDocker parses a Docker URL. 45 func parseDocker(raw string, kind Kind, first bool) (*URL, error) { 46 // Strip off the prefix. 47 raw = raw[len(dockerURLPrefix):] 48 49 // Determine the character that splits the container name from the path or 50 // forwarding endpoint component. 51 var splitCharacter rune 52 if kind == Kind_Synchronization { 53 splitCharacter = '/' 54 } else if kind == Kind_Forwarding { 55 splitCharacter = ':' 56 } else { 57 panic("unhandled URL kind") 58 } 59 60 // Parse off the username. If we hit a '/', then we've reached the end of a 61 // container specification and there was no username. Similarly, if we hit 62 // the end of the string without seeing an '@', then there's also no 63 // username specified. Ideally we'd want also to break on any character that 64 // isn't allowed in a username, but that isn't well-defined, even for POSIX 65 // (it's effectively determined by a configurable regular expression - 66 // NAME_REGEX). 67 var username string 68 for i, r := range raw { 69 if r == splitCharacter { 70 break 71 } else if r == '@' { 72 username = raw[:i] 73 raw = raw[i+1:] 74 break 75 } 76 } 77 78 // Split what remains into the container and the path (or forwarding 79 // endpoint, depending on the URL kind). Ideally we'd want to be a bit more 80 // stringent here about what characters we accept in container names, 81 // potentially breaking early with an error if we see a "disallowed" 82 // character, but we're better off just allowing Docker to reject container 83 // names that it doesn't like. 84 var container, path string 85 for i, r := range raw { 86 if r == splitCharacter { 87 container = raw[:i] 88 path = raw[i:] 89 break 90 } 91 } 92 if container == "" { 93 return nil, errors.New("empty container name") 94 } else if path == "" { 95 if kind == Kind_Synchronization { 96 return nil, errors.New("missing path") 97 } else if kind == Kind_Forwarding { 98 return nil, errors.New("missing forwarding endpoint") 99 } else { 100 panic("unhandled URL kind") 101 } 102 } 103 104 // Perform path processing based on URL kind. 105 if kind == Kind_Synchronization { 106 // If the path starts with "/~", then we assume that it's supposed to be 107 // a home-directory-relative path and remove the slash. At this point we 108 // already know that the path starts with "/" since we retained that as 109 // part of the path in the split operation above. 110 if len(path) > 1 && path[1] == '~' { 111 path = path[1:] 112 } 113 114 // If the path is of the form "/" + Windows path, then assume it's 115 // supposed to be a Windows path. This is a heuristic, but a reasonable 116 // one. We do this on all systems (not just on Windows as with SSH URLs) 117 // because users can connect to Windows containers from non-Windows 118 // systems. At this point we already know that the path starts with "/" 119 // since we retained that as part of the path in the split operation 120 // above. 121 if isWindowsPath(path[1:]) { 122 path = path[1:] 123 } 124 } else if kind == Kind_Forwarding { 125 // For forwarding paths, we need to trim the split character at the 126 // beginning. 127 path = path[1:] 128 129 // Parse the forwarding endpoint URL to ensure that it's valid. 130 if _, _, err := forwarding.Parse(path); err != nil { 131 return nil, fmt.Errorf("invalid forwarding endpoint URL: %w", err) 132 } 133 } else { 134 panic("unhandled URL kind") 135 } 136 137 // Store any Docker environment variables that we need to preserve. We only 138 // store variables that are actually present, because Docker behavior will 139 // vary depending on whether a variable is unset vs. set but empty. 140 environment := make(map[string]string) 141 for _, variable := range DockerEnvironmentVariables { 142 if value, present := getEnvironmentVariable(variable, kind, first); present { 143 environment[variable] = value 144 } 145 } 146 147 // Success. 148 return &URL{ 149 Kind: kind, 150 Protocol: Protocol_Docker, 151 User: username, 152 Host: container, 153 Path: path, 154 Environment: environment, 155 }, nil 156 }