|
1 | 1 | import base64
|
| 2 | +import json |
2 | 3 | import os
|
3 | 4 | import os.path
|
4 |
| -import json |
5 | 5 | import shlex
|
6 |
| -from distutils.version import StrictVersion |
| 6 | +import string |
7 | 7 | from datetime import datetime
|
| 8 | +from distutils.version import StrictVersion |
8 | 9 |
|
9 | 10 | import six
|
10 | 11 |
|
|
13 | 14 |
|
14 | 15 | if six.PY2:
|
15 | 16 | from urllib import splitnport
|
| 17 | + from urlparse import urlparse |
16 | 18 | else:
|
17 |
| - from urllib.parse import splitnport |
| 19 | + from urllib.parse import splitnport, urlparse |
18 | 20 |
|
19 | 21 | DEFAULT_HTTP_HOST = "127.0.0.1"
|
20 |
| -DEFAULT_UNIX_SOCKET = "http+unix://var/run/docker.sock" |
| 22 | +DEFAULT_UNIX_SOCKET = "http+unix:///var/run/docker.sock" |
21 | 23 | DEFAULT_NPIPE = 'npipe:////./pipe/docker_engine'
|
22 | 24 |
|
23 | 25 | BYTE_UNITS = {
|
@@ -212,75 +214,93 @@ def parse_repository_tag(repo_name):
|
212 | 214 | return repo_name, None
|
213 | 215 |
|
214 | 216 |
|
215 |
| -# Based on utils.go:ParseHost http://tinyurl.com/nkahcfh |
216 |
| -# fd:// protocol unsupported (for obvious reasons) |
217 |
| -# Added support for http and https |
218 |
| -# Protocol translation: tcp -> http, unix -> http+unix |
219 | 217 | def parse_host(addr, is_win32=False, tls=False):
|
220 |
| - proto = "http+unix" |
221 |
| - port = None |
222 | 218 | path = ''
|
| 219 | + port = None |
| 220 | + host = None |
223 | 221 |
|
| 222 | + # Sensible defaults |
224 | 223 | if not addr and is_win32:
|
225 |
| - addr = DEFAULT_NPIPE |
226 |
| - |
| 224 | + return DEFAULT_NPIPE |
227 | 225 | if not addr or addr.strip() == 'unix://':
|
228 | 226 | return DEFAULT_UNIX_SOCKET
|
229 | 227 |
|
230 | 228 | addr = addr.strip()
|
231 |
| - if addr.startswith('http://'): |
232 |
| - addr = addr.replace('http://', 'tcp://') |
233 |
| - if addr.startswith('http+unix://'): |
234 |
| - addr = addr.replace('http+unix://', 'unix://') |
235 | 229 |
|
236 |
| - if addr == 'tcp://': |
| 230 | + parsed_url = urlparse(addr) |
| 231 | + proto = parsed_url.scheme |
| 232 | + if not proto or any([x not in string.ascii_letters + '+' for x in proto]): |
| 233 | + # https://bugs.python.org/issue754016 |
| 234 | + parsed_url = urlparse('//' + addr, 'tcp') |
| 235 | + proto = 'tcp' |
| 236 | + |
| 237 | + if proto == 'fd': |
| 238 | + raise errors.DockerException('fd protocol is not implemented') |
| 239 | + |
| 240 | + # These protos are valid aliases for our library but not for the |
| 241 | + # official spec |
| 242 | + if proto == 'http' or proto == 'https': |
| 243 | + tls = proto == 'https' |
| 244 | + proto = 'tcp' |
| 245 | + elif proto == 'http+unix': |
| 246 | + proto = 'unix' |
| 247 | + |
| 248 | + if proto not in ('tcp', 'unix', 'npipe', 'ssh'): |
237 | 249 | raise errors.DockerException(
|
238 |
| - "Invalid bind address format: {0}".format(addr) |
| 250 | + "Invalid bind address protocol: {}".format(addr) |
| 251 | + ) |
| 252 | + |
| 253 | + if proto == 'tcp' and not parsed_url.netloc: |
| 254 | + # "tcp://" is exceptionally disallowed by convention; |
| 255 | + # omitting a hostname for other protocols is fine |
| 256 | + raise errors.DockerException( |
| 257 | + 'Invalid bind address format: {}'.format(addr) |
239 | 258 | )
|
240 |
| - elif addr.startswith('unix://'): |
241 |
| - addr = addr[7:] |
242 |
| - elif addr.startswith('tcp://'): |
243 |
| - proto = 'http{0}'.format('s' if tls else '') |
244 |
| - addr = addr[6:] |
245 |
| - elif addr.startswith('https://'): |
246 |
| - proto = "https" |
247 |
| - addr = addr[8:] |
248 |
| - elif addr.startswith('npipe://'): |
249 |
| - proto = 'npipe' |
250 |
| - addr = addr[8:] |
251 |
| - elif addr.startswith('fd://'): |
252 |
| - raise errors.DockerException("fd protocol is not implemented") |
253 |
| - else: |
254 |
| - if "://" in addr: |
255 |
| - raise errors.DockerException( |
256 |
| - "Invalid bind address protocol: {0}".format(addr) |
257 |
| - ) |
258 |
| - proto = "https" if tls else "http" |
259 | 259 |
|
260 |
| - if proto in ("http", "https"): |
261 |
| - address_parts = addr.split('/', 1) |
262 |
| - host = address_parts[0] |
263 |
| - if len(address_parts) == 2: |
264 |
| - path = '/' + address_parts[1] |
265 |
| - host, port = splitnport(host) |
| 260 | + if any([ |
| 261 | + parsed_url.params, parsed_url.query, parsed_url.fragment, |
| 262 | + parsed_url.password |
| 263 | + ]): |
| 264 | + raise errors.DockerException( |
| 265 | + 'Invalid bind address format: {}'.format(addr) |
| 266 | + ) |
266 | 267 |
|
267 |
| - if port is None: |
268 |
| - raise errors.DockerException( |
269 |
| - "Invalid port: {0}".format(addr) |
270 |
| - ) |
| 268 | + if parsed_url.path and proto == 'ssh': |
| 269 | + raise errors.DockerException( |
| 270 | + 'Invalid bind address format: no path allowed for this protocol:' |
| 271 | + ' {}'.format(addr) |
| 272 | + ) |
| 273 | + else: |
| 274 | + path = parsed_url.path |
| 275 | + if proto == 'unix' and parsed_url.hostname is not None: |
| 276 | + # For legacy reasons, we consider unix://path |
| 277 | + # to be valid and equivalent to unix:///path |
| 278 | + path = '/'.join((parsed_url.hostname, path)) |
| 279 | + |
| 280 | + if proto in ('tcp', 'ssh'): |
| 281 | + # parsed_url.hostname strips brackets from IPv6 addresses, |
| 282 | + # which can be problematic hence our use of splitnport() instead. |
| 283 | + host, port = splitnport(parsed_url.netloc) |
| 284 | + if port is None or port < 0: |
| 285 | + if proto != 'ssh': |
| 286 | + raise errors.DockerException( |
| 287 | + 'Invalid bind address format: port is required:' |
| 288 | + ' {}'.format(addr) |
| 289 | + ) |
| 290 | + port = 22 |
271 | 291 |
|
272 | 292 | if not host:
|
273 | 293 | host = DEFAULT_HTTP_HOST
|
274 |
| - else: |
275 |
| - host = addr |
276 | 294 |
|
277 |
| - if proto in ("http", "https") and port == -1: |
278 |
| - raise errors.DockerException( |
279 |
| - "Bind address needs a port: {0}".format(addr)) |
| 295 | + # Rewrite schemes to fit library internals (requests adapters) |
| 296 | + if proto == 'tcp': |
| 297 | + proto = 'http{}'.format('s' if tls else '') |
| 298 | + elif proto == 'unix': |
| 299 | + proto = 'http+unix' |
280 | 300 |
|
281 |
| - if proto == "http+unix" or proto == 'npipe': |
282 |
| - return "{0}://{1}".format(proto, host).rstrip('/') |
283 |
| - return "{0}://{1}:{2}{3}".format(proto, host, port, path).rstrip('/') |
| 301 | + if proto in ('http+unix', 'npipe'): |
| 302 | + return "{}://{}".format(proto, path).rstrip('/') |
| 303 | + return '{0}://{1}:{2}{3}'.format(proto, host, port, path).rstrip('/') |
284 | 304 |
|
285 | 305 |
|
286 | 306 | def parse_devices(devices):
|
|
0 commit comments