Repository: pypi/warehouse

Issue:
non-integer page params should result in a 4xx not a 500
https://github.com/pypa/warehouse/blob/master/warehouse/views.py#L195

Right now if you pass anything that's not an int, you get a 500.



Discussion:
Just for information, here's the traceback generated:

```
ValueError: invalid literal for int() with base 10: '25\'"'
  File "warehouse/raven.py", line 41, in raven_tween
    return handler(request)
  File "pyramid/tweens.py", line 62, in excview_tween
    reraise(*attrs['exc_info'])
  File "pyramid/compat.py", line 148, in reraise
    raise value
  File "pyramid/tweens.py", line 22, in excview_tween
    response = handler(request)
  File "pyramid_tm/__init__.py", line 101, in tm_tween
    reraise(*exc_info)
  File "pyramid_tm/compat.py", line 15, in reraise
    raise value
  File "pyramid_tm/__init__.py", line 83, in tm_tween
    response = handler(request)
  File "warehouse/cache/http.py", line 69, in conditional_http_tween
    response = handler(request)
  File "pyramid/router.py", line 158, in handle_request
    view_name
  File "pyramid/view.py", line 547, in _call_view
    response = view_callable(context, request)
  File "warehouse/sessions.py", line 291, in wrapped
    return view(context, request)
  File "warehouse/csrf.py", line 38, in wrapped
    return view(context, request)
  File "pyramid/viewderivers.py", line 512, in csrf_view
    return view(context, request)
  File "warehouse/cache/origin/__init__.py", line 81, in wrapped
    return view(context, request)
  File "pyramid/viewderivers.py", line 442, in rendered_view
    result = view(context, request)
  File "pyramid/viewderivers.py", line 147, in _requestonly_view
    response = view(request)
  File "warehouse/views.py", line 195, in search
    page_num = int(request.params.get("page", 1))
```


Resolving patch:
diff --git a/warehouse/views.py b/warehouse/views.py
--- a/warehouse/views.py
+++ b/warehouse/views.py
@@ -14,6 +14,7 @@
 
 from pyramid.httpexceptions import (
     HTTPException, HTTPSeeOther, HTTPMovedPermanently, HTTPNotFound,
+    HTTPBadRequest,
 )
 from pyramid.view import (
     notfound_view_config, forbidden_view_config, view_config,
@@ -192,7 +193,11 @@ def search(request):
     if request.params.getall("c"):
         query = query.filter("terms", classifiers=request.params.getall("c"))
 
-    page_num = int(request.params.get("page", 1))
+    try:
+        page_num = int(request.params.get("page", 1))
+    except ValueError:
+        raise HTTPBadRequest("'page' must be an integer.")
+
     page = ElasticsearchPage(
         query,
         page=page_num,


Test patch:
diff --git a/tests/unit/test_views.py b/tests/unit/test_views.py
--- a/tests/unit/test_views.py
+++ b/tests/unit/test_views.py
@@ -17,7 +17,9 @@
 from elasticsearch_dsl import Q
 from webob.multidict import MultiDict
 
-from pyramid.httpexceptions import HTTPNotFound
+from pyramid.httpexceptions import (
+    HTTPNotFound, HTTPBadRequest,
+)
 
 from warehouse import views
 from warehouse.views import (
@@ -386,6 +388,26 @@ def test_raises_404_with_pagenum_too_high(self, monkeypatch, db_request):
         ]
         assert url_maker_factory.calls == [pretend.call(db_request)]
 
+    def test_raises_400_with_pagenum_type_str(self, monkeypatch, db_request):
+        params = MultiDict({"page": "abc"})
+        db_request.params = params
+
+        es_query = pretend.stub()
+        db_request.es = pretend.stub(query=lambda *a, **kw: es_query)
+
+        page_obj = pretend.stub(page_count=10)
+        page_cls = pretend.call_recorder(lambda *a, **kw: page_obj)
+        monkeypatch.setattr(views, "ElasticsearchPage", page_cls)
+
+        url_maker = pretend.stub()
+        url_maker_factory = pretend.call_recorder(lambda request: url_maker)
+        monkeypatch.setattr(views, "paginate_url_factory", url_maker_factory)
+
+        with pytest.raises(HTTPBadRequest):
+            search(db_request)
+
+        assert page_cls.calls == []
+
 
 def test_health():
     request = pretend.stub(