Repository: streamlit/streamlit

Issue:
Incorrect `ahref` value in "Snapshot uploaded" dialog
From Danny:

![image](https://user-images.githubusercontent.com/709022/70193869-63f34380-16b5-11ea-8af9-2cc6c8e66a88.png)

If a custom server URL is specified, it's shown correctly in the app snapshot text, but the actual link is incorrect.


Discussion:
The issue turned out to be that the `[s3] url=...` config entry was missing the http:// prefix.

The config looked like this:

```
[s3]
url="people.yelpcorp.com/~dqn/st"  # <-- wrong
```

instead of this:

```
[s3]
url="https://people.yelpcorp.com/~dqn/st"
```

We should probably validate the URL at config loading time?

Resolving patch:
diff --git a/lib/streamlit/config.py b/lib/streamlit/config.py
--- a/lib/streamlit/config.py
+++ b/lib/streamlit/config.py
@@ -24,6 +24,7 @@
 import os
 import toml
 import collections
+import urllib
 
 import click
 from blinker import Signal
@@ -503,11 +504,11 @@ def _browser_server_port():
 
 _create_option(
     "mapbox.token",
-    description="""Configure Streamlit to use a custom Mapbox 
-                token for elements like st.deck_gl_chart and st.map. If you 
-                don't do this you'll be using Streamlit's own token, 
-                which has limitations and is not guaranteed to always work. 
-                To get a token for yourself, create an account at 
+    description="""Configure Streamlit to use a custom Mapbox
+                token for elements like st.deck_gl_chart and st.map. If you
+                don't do this you'll be using Streamlit's own token,
+                which has limitations and is not guaranteed to always work.
+                To get a token for yourself, create an account at
                 https://mapbox.com. It's free! (for moderate usage levels)""",
     default_val="pk.eyJ1IjoidGhpYWdvdCIsImEiOiJjamh3bm85NnkwMng4M3"
     "dydnNveWwzeWNzIn0.vCBDzNsEF2uFSFk2AM0WZQ",
@@ -903,6 +904,19 @@ def _check_conflicts():
             "either both be set or both be unset."
         )
 
+        if is_manually_set("s3.url"):
+            # If s3.url is set, ensure that it's an absolute URL.
+            # An absolute URL starts with either `scheme://` or `//` --
+            # if the configured URL does not start with either prefix,
+            # prepend it with `//` to make it absolute. (If we don't do this,
+            # and the user enters something like `url=myhost.com/reports`, the
+            # browser will assume this is a relative URL, and will prepend
+            # the hostname of the Streamlit instance to the configured URL.)
+            s3_url = get_option("s3.url")
+            parsed = urllib.parse.urlparse(s3_url)
+            if parsed.netloc == "":
+                _set_option("s3.url", "//" + s3_url, get_where_defined("s3.url"))
+
 
 def _set_development_mode():
     development.is_development_mode = get_option("global.developmentMode")


Test patch:
diff --git a/lib/tests/streamlit/config_test.py b/lib/tests/streamlit/config_test.py
--- a/lib/tests/streamlit/config_test.py
+++ b/lib/tests/streamlit/config_test.py
@@ -336,7 +336,7 @@ def test_clean(self):
         result = config._clean(" clean    this         text  ")
         self.assertEqual("clean this text", result)
 
-    def test_check_conflicts_2(self):
+    def test_check_conflicts_server_port(self):
         config._set_option("global.developmentMode", True, "test")
         config._set_option("server.port", 1234, "test")
         with pytest.raises(AssertionError) as e:
@@ -346,7 +346,7 @@ def test_check_conflicts_2(self):
             "server.port does not work when global.developmentMode is true.",
         )
 
-    def test_check_conflicts_2a(self):
+    def test_check_conflicts_browser_serverport(self):
         config._set_option("global.developmentMode", True, "test")
         config._set_option("browser.serverPort", 1234, "test")
         with pytest.raises(AssertionError) as e:
@@ -356,7 +356,7 @@ def test_check_conflicts_2a(self):
             "browser.serverPort does not work when global.developmentMode is " "true.",
         )
 
-    def test_check_conflicts_3(self):
+    def test_check_conflicts_s3_sharing_mode(self):
         with pytest.raises(AssertionError) as e:
             config._set_option("global.sharingMode", "s3", "test")
             config._set_option("s3.bucket", None, "<default>")
@@ -366,7 +366,7 @@ def test_check_conflicts_3(self):
             'When global.sharingMode is set to "s3", s3.bucket must also be set',
         )
 
-    def test_check_conflicts_4(self):
+    def test_check_conflicts_s3_credentials(self):
         with pytest.raises(AssertionError) as e:
             config._set_option("global.sharingMode", "s3", "test")
             config._set_option("s3.bucket", "some.bucket", "test")
@@ -378,6 +378,25 @@ def test_check_conflicts_4(self):
             "In config.toml, s3.accessKeyId and s3.secretAccessKey must either both be set or both be unset.",
         )
 
+    def test_check_conflicts_s3_absolute_url(self):
+        """Test that non-absolute s3.url values get made absolute"""
+        config._set_option("global.sharingMode", "s3", "test")
+        config._set_option("s3.bucket", "some.bucket", "test")
+        config._set_option("s3.accessKeyId", "some.key", "test")
+        config._set_option("s3.secretAccessKey", "some.key", "test")
+
+        # This absolute URL should *not* be modified in check_conflicts:
+        absolute_url = "https://absolute.url"
+        config._set_option("s3.url", absolute_url, "test")
+        config._check_conflicts()
+        self.assertEqual(absolute_url, config.get_option("s3.url"))
+
+        # This non-absolute URL *should* be modified with a '//' prefix:
+        relative_url = "relative.url"
+        config._set_option("s3.url", relative_url, "test")
+        config._check_conflicts()
+        self.assertEqual("//" + relative_url, config.get_option("s3.url"))
+
     def test_maybe_convert_to_number(self):
         self.assertEqual(1234, config._maybe_convert_to_number("1234"))
         self.assertEqual(1234.5678, config._maybe_convert_to_number("1234.5678"))
@@ -616,4 +635,4 @@ def test_load_global_local_config(self):
             self.assertEqual(u"local_accessKeyId", config.get_option("s3.accessKeyId"))
 
     def test_upload_file_default_values(self):
-        self.assertEqual(50, config.get_option("server.maxUploadSize"))
\ No newline at end of file
+        self.assertEqual(50, config.get_option("server.maxUploadSize"))