diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 175fc9b7e5..62959a9367 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -315,6 +315,15 @@ file-like object for your body:: with open('massive-body', 'rb') as f: requests.post('http://some.url/streamed', data=f) +.. warning:: It is strongly recommended that you open files in `binary mode`_. + This is because Requests may attempt to provide the + ``Content-Length`` header for you, and if it does this value will + be set to the number of *bytes* in the file. Errors may occur if + you open the file in *text mode*. + +.. _binary mode: https://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files + + .. _chunk-encoding: Chunk-Encoded Requests @@ -362,6 +371,15 @@ To do that, just set files to a list of tuples of (form_field_name, file_info): ... } +.. warning:: It is strongly recommended that you open files in `binary mode`_. + This is because Requests may attempt to provide the + ``Content-Length`` header for you, and if it does this value will + be set to the number of *bytes* in the file. Errors may occur if + you open the file in *text mode*. + +.. _binary mode: https://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files + + .. _event-hooks: Event Hooks @@ -519,7 +537,7 @@ any request to the given scheme and exact hostname. } Note that proxy URLs must include the scheme. - + .. _compliance: Compliance diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 096693fbe5..5117ed0c86 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -302,6 +302,14 @@ support this, but there is a separate package which does - For sending multiple files in one request refer to the :ref:`advanced ` section. +.. warning:: It is strongly recommended that you open files in `binary mode`_. + This is because Requests may attempt to provide the + ``Content-Length`` header for you, and if it does this value will + be set to the number of *bytes* in the file. Errors may occur if + you open the file in *text mode*. + +.. _binary mode: https://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files + Response Status Codes --------------------- diff --git a/requests/__init__.py b/requests/__init__.py index 3d8188a707..f7924dc895 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -62,7 +62,8 @@ from .status_codes import codes from .exceptions import ( RequestException, Timeout, URLRequired, - TooManyRedirects, HTTPError, ConnectionError + TooManyRedirects, HTTPError, ConnectionError, + FileModeWarning, ) # Set default logging handler to avoid "No handler found" warnings. @@ -75,3 +76,8 @@ def emit(self, record): pass logging.getLogger(__name__).addHandler(NullHandler()) + +import warnings + +# FileModeWarnings go off per the default. +warnings.simplefilter('default', FileModeWarning, append=True) diff --git a/requests/exceptions.py b/requests/exceptions.py index 89135a802e..ba0b910e31 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -97,3 +97,18 @@ class StreamConsumedError(RequestException, TypeError): class RetryError(RequestException): """Custom retries logic failed""" + + +# Warnings + + +class RequestsWarning(Warning): + """Base warning for Requests.""" + pass + + +class FileModeWarning(RequestsWarning, DeprecationWarning): + """ + A file was opened in text mode, but Requests determined its binary length. + """ + pass diff --git a/requests/utils.py b/requests/utils.py index 4a8c6d7e06..5975d0f1f0 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -29,7 +29,7 @@ basestring) from .cookies import RequestsCookieJar, cookiejar_from_dict from .structures import CaseInsensitiveDict -from .exceptions import InvalidURL +from .exceptions import InvalidURL, FileModeWarning _hush_pyflakes = (RequestsCookieJar,) @@ -60,7 +60,22 @@ def super_len(o): except io.UnsupportedOperation: pass else: - return os.fstat(fileno).st_size + filesize = os.fstat(fileno).st_size + + # Having used fstat to determine the file length, we need to + # confirm that this file was opened up in binary mode. + if 'b' not in o.mode: + warnings.warn(( + "Requests has determined the content-length for this " + "request using the binary size of the file: however, the " + "file has been opened in text mode (i.e. without the 'b' " + "flag in the mode). This may lead to an incorrect " + "content-length. In Requests 3.0, support will be removed " + "for files in text mode."), + FileModeWarning + ) + + return filesize if hasattr(o, 'getvalue'): # e.g. BytesIO, cStringIO.StringIO