Repository: Pylons/pyramid

Issue:
add_static_view() Does Not Detect Protocol-less Urls
In short, passing the following to add_static_view does not behave as expected:

```
config.add_static_view('//static.example.com', 'mypackage:static')
```

It does not detect that as a valid url path, and therefore generates long, broken urls. Ideally, this should generate urls like:

```
request.static_url('mypackate:static/x.png')
# it should return '//static.example.com/x.png'
# instead it returns 'http://example.com//static.example.com/x.png
```

In this way, the templates do not need to be rendered differently for HTTP vs HTTPS connections.



Discussion:
@mankyd: I do believe I have fixed the issue in pull request #838 if you could test against that I would be grateful!


Resolving patch:
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -1793,6 +1793,10 @@ def add_static_view(self, name, path, **kw):
         qualified URL (e.g. starts with ``http://`` or similar).  In this
         mode, the ``name`` is used as the prefix of the full URL when
         generating a URL using :meth:`pyramid.request.Request.static_url`.
+        Furthermore, if a protocol-relative URL (e.g. ``//example.com/images``)
+        is used as the ``name`` argument, the generated URL will use the
+        protocol of the request (http or https, respectively).
+
         For example, if ``add_static_view`` is called like so:
 
         .. code-block:: python
@@ -1801,20 +1805,14 @@ def add_static_view(self, name, path, **kw):
 
         Subsequently, the URLs generated by
         :meth:`pyramid.request.Request.static_url` for that static view will
-        be prefixed with ``http://example.com/images``:
+        be prefixed with ``http://example.com/images`` (the external webserver
+        listening on ``example.com`` must be itself configured to respond
+        properly to such a request.):
 
         .. code-block:: python
 
            static_url('mypackage:images/logo.png', request)
 
-        When ``add_static_view`` is called with a ``name`` argument that is
-        the URL ``http://example.com/images``, subsequent calls to
-        :meth:`pyramid.request.Request.static_url` with paths that start with
-        the ``path`` argument passed to ``add_static_view`` will generate a
-        URL something like ``http://example.com/logo.png``.  The external
-        webserver listening on ``example.com`` must be itself configured to
-        respond properly to such a request.
-
         See :ref:`static_assets_section` for more information.
         """
         spec = self._make_spec(path)
@@ -1858,6 +1856,12 @@ def generate(self, path, request, **kw):
                     kw['subpath'] = subpath
                     return request.route_url(route_name, **kw)
                 else:
+                    parsed = url_parse(url)
+                    if not parsed.scheme:
+                        # parsed.scheme is readonly, so we have to parse again
+                        # to change the scheme, sigh.
+                        url = urlparse.urlunparse(url_parse(
+                            url, scheme=request.environ['wsgi.url_scheme']))
                     subpath = url_quote(subpath)
                     return urljoin(url, subpath)
 
@@ -1886,7 +1890,7 @@ def add(self, config, name, spec, **extra):
             # make sure it ends with a slash
             name = name + '/'
 
-        if url_parse(name)[0]:
+        if url_parse(name).netloc:
             # it's a URL
             # url, spec, route_name
             url = name


Test patch:
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -3737,6 +3737,13 @@ def test_add_url_noendslash(self):
         expected = [('http://example.com/', 'anotherpackage:path/', None)]
         self._assertRegistrations(config, expected)
 
+    def test_add_url_noscheme(self):
+        inst = self._makeOne()
+        config = self._makeConfig()
+        inst.add(config, '//example.com', 'anotherpackage:path')
+        expected = [('//example.com/', 'anotherpackage:path/', None)]
+        self._assertRegistrations(config, expected)
+
     def test_add_viewname(self):
         from pyramid.security import NO_PERMISSION_REQUIRED
         from pyramid.static import static_view
diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py
--- a/pyramid/tests/test_url.py
+++ b/pyramid/tests/test_url.py
@@ -583,6 +583,21 @@ def test_static_url_abspath_integration_with_staticurlinfo(self):
         self.assertEqual(result,
                          'http://example.com:5432/absstatic/test_url.py')
 
+    def test_static_url_noscheme_uses_scheme_from_request(self):
+        import os
+        from pyramid.interfaces import IStaticURLInfo
+        from pyramid.config.views import StaticURLInfo
+        info = StaticURLInfo()
+        here = os.path.abspath(os.path.dirname(__file__))
+        info.add(self.config, '//subdomain.example.com/static', here)
+        request = self._makeOne({'wsgi.url_scheme': 'https'})
+        registry = request.registry
+        registry.registerUtility(info, IStaticURLInfo)
+        abspath = os.path.join(here, 'test_url.py')
+        result = request.static_url(abspath)
+        self.assertEqual(result,
+                         'https://subdomain.example.com/static/test_url.py')
+
     def test_static_path_abspath(self):
         from pyramid.interfaces import IStaticURLInfo
         request = self._makeOne()