diff --git a/setup.py b/setup.py index 6c6a0e57..fbf3b38b 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ install_requires = [ "pkginfo", - "requests >= 2.0", + "requests >= 2.3.0", "setuptools >= 0.7.0", ] diff --git a/twine/commands/upload.py b/twine/commands/upload.py index a7e57275..5979e628 100644 --- a/twine/commands/upload.py +++ b/twine/commands/upload.py @@ -30,6 +30,7 @@ import pkg_resources import requests +import twine.exceptions as exc from twine.utils import get_config, get_username, get_password from twine.wheel import Wheel from twine.wininst import WinInst @@ -210,11 +211,23 @@ def upload(dists, repository, sign, identity, username, password, comment, data=dict((k, v) for k, v in data.items() if v), files=filedata, auth=(username, password), + allow_redirects=False, ) # Bug 28. Try to silence a ResourceWarning by releasing the socket and # clearing the connection pool. resp.close() session.close() + + # Bug 92. If we get a redirect we should abort because something seems + # funky. The behaviour is not well defined and redirects being issued + # by PyPI should never happen in reality. This should catch malicious + # redirects as well. + if resp.is_redirect(): + raise exc.RedirectDetected( + ('"{0}" attempted to redirect to "{1}" during upload.' + ' Aborting...').format(config["respository"], + resp.headers["location"])) + # Otherwise, raise an HTTPError based on the status code. resp.raise_for_status() diff --git a/twine/exceptions.py b/twine/exceptions.py new file mode 100644 index 00000000..52fa6cbb --- /dev/null +++ b/twine/exceptions.py @@ -0,0 +1,24 @@ +# Copyright 2015 Ian Cordasco +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +class RedirectDetected(Exception): + """A redirect was detected that the user needs to resolve. + + In some cases, requests refuses to issue a new POST request after a + redirect. In order to prevent a confusing user experience, we raise this + exception to allow users to know the index they're uploading to is + redirecting them. + """ + pass