Repository: docker/docker-py

Issue:
split_port() does not properly handle ":80" or "127.0.0.1:" properly
Initially reported as https://github.com/docker/compose/issues/1887 

Example:

``` python
    def test_port_only_with_colon(self):
        self.assertRaises(ValueError,
                          lambda: split_port(":80"))

    def test_host_only_with_colon(self):
        self.assertRaises(ValueError,
                          lambda: split_port("localhost:"))
```

Results:

```
======================================================================
ERROR: test_host_only_with_colon (__main__.UtilsTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests/utils_test.py", line 428, in test_host_only_with_colon
    lambda: split_port("localhost:"))
  File "/usr/lib/python2.7/unittest/case.py", line 473, in assertRaises
    callableObj(*args, **kwargs)
  File "tests/utils_test.py", line 428, in <lambda>
    lambda: split_port("localhost:"))
  File "/home/mark/Projects/docker-py/docker/utils/ports/ports.py", line 77, in split_port
    if len(internal_range) != len(external_range):
TypeError: object of type 'NoneType' has no len()

======================================================================
ERROR: test_port_only_with_colon (__main__.UtilsTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests/utils_test.py", line 424, in test_port_only_with_colon
    lambda: split_port(":80"))
  File "/usr/lib/python2.7/unittest/case.py", line 473, in assertRaises
    callableObj(*args, **kwargs)
  File "tests/utils_test.py", line 424, in <lambda>
    lambda: split_port(":80"))
  File "/home/mark/Projects/docker-py/docker/utils/ports/ports.py", line 77, in split_port
    if len(internal_range) != len(external_range):
TypeError: object of type 'NoneType' has no len()
```



Discussion:

Resolving patch:
diff --git a/docker/utils/ports/ports.py b/docker/utils/ports/ports.py
--- a/docker/utils/ports/ports.py
+++ b/docker/utils/ports/ports.py
@@ -1,5 +1,4 @@
 
-
 def add_port_mapping(port_bindings, internal_port, external):
     if internal_port in port_bindings:
         port_bindings[internal_port].append(external)
@@ -33,9 +32,8 @@ def to_port_range(port):
     if "/" in port:
         parts = port.split("/")
         if len(parts) != 2:
-            raise ValueError('Invalid port "%s", should be '
-                             '[[remote_ip:]remote_port[-remote_port]:]'
-                             'port[/protocol]' % port)
+            _raise_invalid_port(port)
+
         port, protocol = parts
         protocol = "/" + protocol
 
@@ -52,11 +50,17 @@ def to_port_range(port):
                      'port or startport-endport' % port)
 
 
+def _raise_invalid_port(port):
+    raise ValueError('Invalid port "%s", should be '
+                     '[[remote_ip:]remote_port[-remote_port]:]'
+                     'port[/protocol]' % port)
+
+
 def split_port(port):
     parts = str(port).split(':')
+
     if not 1 <= len(parts) <= 3:
-        raise ValueError('Invalid port "%s", should be '
-                         '[[remote_ip:]remote_port:]port[/protocol]' % port)
+        _raise_invalid_port(port)
 
     if len(parts) == 1:
         internal_port, = parts
@@ -66,6 +70,10 @@ def split_port(port):
 
         internal_range = to_port_range(internal_port)
         external_range = to_port_range(external_port)
+
+        if internal_range is None or external_range is None:
+            _raise_invalid_port(port)
+
         if len(internal_range) != len(external_range):
             raise ValueError('Port ranges don\'t match in length')
 


Test patch:
diff --git a/tests/utils_test.py b/tests/utils_test.py
--- a/tests/utils_test.py
+++ b/tests/utils_test.py
@@ -419,6 +419,14 @@ def test_port_and_range_invalid(self):
         self.assertRaises(ValueError,
                           lambda: split_port("0.0.0.0:1000:2000-2002/tcp"))
 
+    def test_port_only_with_colon(self):
+        self.assertRaises(ValueError,
+                          lambda: split_port(":80"))
+
+    def test_host_only_with_colon(self):
+        self.assertRaises(ValueError,
+                          lambda: split_port("localhost:"))
+
     def test_build_port_bindings_with_one_port(self):
         port_bindings = build_port_bindings(["127.0.0.1:1000:1000"])
         self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")])