Repository: kivy/kivy

Issue:
[UrlRequest]  ':' inside Url will cause invalid literal error when it isn't marking the port number
If you have a Url like this:

url =" https://myAppID:javascript-key=myJavaScriptKey@api.parse.com/1/classes/Door"

Urlrequest will cast following error:

invalid literal for int() with base 10: "javascript-key=myJavaScriptKey@api.parse.com"

This makes it so a lot of REST APIs can't be called from kivy not just from the parse.com site

This is caused because Urlrequest will see the ':' and will expect a port number. 

See lines 242-247 in Urlrequest:

 # correctly determine host/port
        port = None
        host = parse.netloc.split(':')
        if len(host) > 1:
            port = int(host[1])
        host = host[0]
## <bountysource-plugin>

Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/5259483-urlrequest-inside-url-will-cause-invalid-literal-error-when-it-isn-t-marking-the-port-number?utm_campaign=plugin&utm_content=tracker%2F42681&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F42681&utm_medium=issues&utm_source=github).
</bountysource-plugin>



Discussion:
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Tested with:

```python
from kivy.app import App
from kivy.lang import Builder


kv = """
AnchorLayout:
    canvas:
        Color:
            rgba: 1, 1, 1, 1
        Rectangle:
            pos: self.pos
            size: self.size
    AsyncImage:
        size_hint: None, None
        size: dp(56), dp(56)
        # http://duckduckgo.com
        source: 'http://54.229.105.92:80/assets/icons/meta/DDG-icon_256x256.png'
        anim_delay: 0.0333333333333333
"""


class TestApp(App):
    def build(self):
        return Builder.load_string(kv)

if __name__ == '__main__':
    TestApp().run()
```
Confused by this. How does that testcase catch the indicated error? 
@hackalog Ah, I've misread the issue! Thanks. I thought the problem was in `<url>:<port>`, yet apparently it's `<url>:` what was desired. I'll try to look into it.

Resolving patch:
diff --git a/kivy/network/urlrequest.py b/kivy/network/urlrequest.py
--- a/kivy/network/urlrequest.py
+++ b/kivy/network/urlrequest.py
@@ -56,6 +56,7 @@ def bug_posted(req, result):
 
 '''
 
+from base64 import b64encode
 from collections import deque
 from threading import Thread
 from json import loads
@@ -245,6 +246,25 @@ def run(self):
         if self in g_requests:
             g_requests.remove(self)
 
+    def _parse_url(self, url):
+        parse = urlparse(url)
+        host = parse.hostname
+        port = parse.port
+        userpass = None
+
+        # append user + pass to hostname if specified
+        if parse.username and parse.password:
+            userpass = {
+                "Authorization": "Basic {}".format(b64encode(
+                    "{}:{}".format(
+                        parse.username,
+                        parse.password
+                    ).encode('utf-8')
+                ).decode('utf-8'))
+            }
+
+        return host, port, userpass, parse
+
     def _fetch_url(self, url, body, headers, q):
         # Parse and fetch the current url
         trigger = self._trigger_result
@@ -264,18 +284,16 @@ def _fetch_url(self, url, body, headers, q):
                 id(self), headers))
 
         # parse url
-        parse = urlparse(url)
+        host, port, userpass, parse = self._parse_url(url)
+        if userpass and not headers:
+            headers = userpass
+        elif userpass and headers:
+            key = list(userpass.keys())[0]
+            headers[key] = userpass[key]
 
         # translate scheme to connection class
         cls = self.get_connection_for_scheme(parse.scheme)
 
-        # correctly determine host/port
-        port = None
-        host = parse.netloc.split(':')
-        if len(host) > 1:
-            port = int(host[1])
-        host = host[0]
-
         # reconstruct path to pass on the request
         path = parse.path
         if parse.params:


Test patch:
diff --git a/kivy/tests/test_urlrequest.py b/kivy/tests/test_urlrequest.py
--- a/kivy/tests/test_urlrequest.py
+++ b/kivy/tests/test_urlrequest.py
@@ -14,6 +14,7 @@
 
 from kivy.network.urlrequest import UrlRequest
 from time import sleep
+from base64 import b64encode
 from kivy.clock import Clock
 import os
 
@@ -67,3 +68,97 @@ def test_callbacks(self):
 
         self.assertEqual(self.queue[0][2][0], 0)
         self.assertEqual(self.queue[-2][2][0], self.queue[-2][2][1])
+
+    def test_auth_header(self):
+        if os.environ.get('NONETWORK'):
+            return
+        self.queue = []
+        head = {
+            "Authorization": "Basic {}".format(b64encode(
+                "{}:{}".format('user', 'passwd').encode('utf-8')
+            ).decode('utf-8'))
+        }
+        req = UrlRequest(
+            'http://httpbin.org/basic-auth/user/passwd',
+            on_success=self._on_success,
+            on_progress=self._on_progress,
+            on_error=self._on_error,
+            on_redirect=self._on_redirect,
+            req_headers=head,
+            debug=True
+        )
+
+        # don't use wait, but maximum 10s timeout
+        for i in range(50):
+            Clock.tick()
+            sleep(.5)
+            if req.is_finished:
+                break
+
+        self.assertTrue(req.is_finished)
+
+        # we should have 2 progress minimum and one success
+        self.assertTrue(len(self.queue) >= 3)
+
+        # ensure the callback is called from this thread (main).
+        tid = _thread.get_ident()
+        self.assertEqual(self.queue[0][0], tid)
+        self.assertEqual(self.queue[-2][0], tid)
+        self.assertEqual(self.queue[-1][0], tid)
+
+        self.assertEqual(self.queue[0][1], 'progress')
+        self.assertEqual(self.queue[-2][1], 'progress')
+        self.assertIn(self.queue[-1][1], ('success', 'redirect'))
+        self.assertEqual(
+            self.queue[-1][2],
+            ({'authenticated': True, 'user': 'user'}, )
+        )
+
+        self.assertEqual(self.queue[0][2][0], 0)
+        self.assertEqual(self.queue[-2][2][0], self.queue[-2][2][1])
+
+    def test_auth_auto(self):
+        if os.environ.get('NONETWORK'):
+            return
+        self.queue = []
+        req = UrlRequest(
+            'http://user:passwd@httpbin.org/basic-auth/user/passwd',
+            on_success=self._on_success,
+            on_progress=self._on_progress,
+            on_error=self._on_error,
+            on_redirect=self._on_redirect,
+            debug=True
+        )
+
+        # don't use wait, but maximum 10s timeout
+        for i in range(50):
+            Clock.tick()
+            sleep(.5)
+            if req.is_finished:
+                break
+
+        self.assertTrue(req.is_finished)
+
+        # we should have 2 progress minimum and one success
+        self.assertTrue(len(self.queue) >= 3)
+
+        # ensure the callback is called from this thread (main).
+        tid = _thread.get_ident()
+        self.assertEqual(self.queue[0][0], tid)
+        self.assertEqual(self.queue[-2][0], tid)
+        self.assertEqual(self.queue[-1][0], tid)
+
+        self.assertEqual(self.queue[0][1], 'progress')
+        self.assertEqual(self.queue[-2][1], 'progress')
+        self.assertIn(self.queue[-1][1], ('success', 'redirect'))
+        self.assertEqual(
+            self.queue[-1][2],
+            ({'authenticated': True, 'user': 'user'}, )
+        )
+
+        self.assertEqual(self.queue[0][2][0], 0)
+        self.assertEqual(self.queue[-2][2][0], self.queue[-2][2][1])
+
+
+if __name__ == '__main__':
+    unittest.main()