We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
boto.glacier.utils.compute_hashes_from_fileobj No Longer Works with Binary Files
Commit a4c9a781f47a61ddde2b3a3802b93c1ed29cdf16 added a `.encode('utf-8')` to the result of the first read in `boto.glacier.utils.compute_hashes_from_fileobj` (although it doesn't attempt to encode the results of the reads in the loop below that).

This breaks for binary files for me with python 2.6.6:

```
(glacier)[cperl@localhost ~]$ dd if=/dev/urandom of=/tmp/foo.bin bs=1M count=1
1+0 records in
1+0 records out
1048576 bytes (1.0 MB) copied, 0.110299 s, 9.5 MB/s

(glacier)[cperl@localhost ~]$ python
Python 2.6.6 (r266:84292, Nov 22 2013, 12:16:22) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import boto.glacier.utils
>>> with open("/tmp/foo.bin", 'r') as f:
...     boto.glacier.utils.compute_hashes_from_fileobj(f)
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/home/cperl/virtualenv/glacier/lib/python2.6/site-packages/boto/glacier/utils.py", line 127, in compute_hashes_from_fileobj
    chunk = fileobj.read(chunk_size).encode('utf-8')
UnicodeDecodeError: 'ascii' codec can't decode byte 0xf4 in position 0: ordinal not in range(128)
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `boto/glacier/utils.py`
Content:
```
1 # Copyright (c) 2012 Amazon.com, Inc. or its affiliates.  All Rights Reserved
2 #
3 # Permission is hereby granted, free of charge, to any person obtaining a
4 # copy of this software and associated documentation files (the
5 # "Software"), to deal in the Software without restriction, including
6 # without limitation the rights to use, copy, modify, merge, publish, dis-
7 # tribute, sublicense, and/or sell copies of the Software, and to permit
8 # persons to whom the Software is furnished to do so, subject to the fol-
9 # lowing conditions:
10 #
11 # The above copyright notice and this permission notice shall be included
12 # in all copies or substantial portions of the Software.
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
16 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
17 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 # IN THE SOFTWARE.
21 #
22 import hashlib
23 import math
24 import binascii
25 
26 
27 _MEGABYTE = 1024 * 1024
28 DEFAULT_PART_SIZE = 4 * _MEGABYTE
29 MAXIMUM_NUMBER_OF_PARTS = 10000
30 
31 
32 def minimum_part_size(size_in_bytes, default_part_size=DEFAULT_PART_SIZE):
33     """Calculate the minimum part size needed for a multipart upload.
34 
35     Glacier allows a maximum of 10,000 parts per upload.  It also
36     states that the maximum archive size is 10,000 * 4 GB, which means
37     the part size can range from 1MB to 4GB (provided it is one 1MB
38     multiplied by a power of 2).
39 
40     This function will compute what the minimum part size must be in
41     order to upload a file of size ``size_in_bytes``.
42 
43     It will first check if ``default_part_size`` is sufficient for
44     a part size given the ``size_in_bytes``.  If this is not the case,
45     then the smallest part size than can accomodate a file of size
46     ``size_in_bytes`` will be returned.
47 
48     If the file size is greater than the maximum allowed archive
49     size of 10,000 * 4GB, a ``ValueError`` will be raised.
50 
51     """
52     # The default part size (4 MB) will be too small for a very large
53     # archive, as there is a limit of 10,000 parts in a multipart upload.
54     # This puts the maximum allowed archive size with the default part size
55     # at 40,000 MB. We need to do a sanity check on the part size, and find
56     # one that works if the default is too small.
57     part_size = _MEGABYTE
58     if (default_part_size * MAXIMUM_NUMBER_OF_PARTS) < size_in_bytes:
59         if size_in_bytes > (4096 * _MEGABYTE * 10000):
60             raise ValueError("File size too large: %s" % size_in_bytes)
61         min_part_size = size_in_bytes / 10000
62         power = 3
63         while part_size < min_part_size:
64             part_size = math.ldexp(_MEGABYTE, power)
65             power += 1
66         part_size = int(part_size)
67     else:
68         part_size = default_part_size
69     return part_size
70 
71 
72 def chunk_hashes(bytestring, chunk_size=_MEGABYTE):
73     chunk_count = int(math.ceil(len(bytestring) / float(chunk_size)))
74     hashes = []
75     for i in range(chunk_count):
76         start = i * chunk_size
77         end = (i + 1) * chunk_size
78         hashes.append(hashlib.sha256(bytestring[start:end]).digest())
79     if not hashes:
80         return [hashlib.sha256(b'').digest()]
81     return hashes
82 
83 
84 def tree_hash(fo):
85     """
86     Given a hash of each 1MB chunk (from chunk_hashes) this will hash
87     together adjacent hashes until it ends up with one big one. So a
88     tree of hashes.
89     """
90     hashes = []
91     hashes.extend(fo)
92     while len(hashes) > 1:
93         new_hashes = []
94         while True:
95             if len(hashes) > 1:
96                 first = hashes.pop(0)
97                 second = hashes.pop(0)
98                 new_hashes.append(hashlib.sha256(first + second).digest())
99             elif len(hashes) == 1:
100                 only = hashes.pop(0)
101                 new_hashes.append(only)
102             else:
103                 break
104         hashes.extend(new_hashes)
105     return hashes[0]
106 
107 
108 def compute_hashes_from_fileobj(fileobj, chunk_size=1024 * 1024):
109     """Compute the linear and tree hash from a fileobj.
110 
111     This function will compute the linear/tree hash of a fileobj
112     in a single pass through the fileobj.
113 
114     :param fileobj: A file like object.
115 
116     :param chunk_size: The size of the chunks to use for the tree
117         hash.  This is also the buffer size used to read from
118         `fileobj`.
119 
120     :rtype: tuple
121     :return: A tuple of (linear_hash, tree_hash).  Both hashes
122         are returned in hex.
123 
124     """
125     linear_hash = hashlib.sha256()
126     chunks = []
127     chunk = fileobj.read(chunk_size).encode('utf-8')
128     while chunk:
129         linear_hash.update(chunk)
130         chunks.append(hashlib.sha256(chunk).digest())
131         chunk = fileobj.read(chunk_size)
132     if not chunks:
133         chunks = [hashlib.sha256(b'').digest()]
134     return linear_hash.hexdigest(), bytes_to_hex(tree_hash(chunks))
135 
136 
137 def bytes_to_hex(str_as_bytes):
138     return binascii.hexlify(str_as_bytes)
139 
140 
141 def tree_hash_from_str(str_as_bytes):
142     """
143 
144     :type str_as_bytes: str
145     :param str_as_bytes: The string for which to compute the tree hash.
146 
147     :rtype: str
148     :return: The computed tree hash, returned as hex.
149 
150     """
151     return bytes_to_hex(tree_hash(chunk_hashes(str_as_bytes)))
152 
153 
154 class ResettingFileSender(object):
155     def __init__(self, archive):
156         self._archive = archive
157         self._starting_offset = archive.tell()
158 
159     def __call__(self, connection, method, path, body, headers):
160         try:
161             connection.request(method, path, self._archive, headers)
162             return connection.getresponse()
163         finally:
164             self._archive.seek(self._starting_offset)
165
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/boto/glacier/utils.py b/boto/glacier/utils.py
--- a/boto/glacier/utils.py
+++ b/boto/glacier/utils.py
@@ -23,6 +23,8 @@
 import math
 import binascii
 
+from boto.compat import six
+
 
 _MEGABYTE = 1024 * 1024
 DEFAULT_PART_SIZE = 4 * _MEGABYTE
@@ -122,10 +124,19 @@
         are returned in hex.
 
     """
+    # Python 3+, not binary
+    if six.PY3 and hasattr(fileobj, 'mode') and 'b' not in fileobj.mode:
+        raise ValueError('File-like object must be opened in binary mode!')
+
     linear_hash = hashlib.sha256()
     chunks = []
-    chunk = fileobj.read(chunk_size).encode('utf-8')
+    chunk = fileobj.read(chunk_size)
     while chunk:
+        # It's possible to get a file-like object that has no mode (checked
+        # above) and returns something other than bytes (e.g. str). So here
+        # we try to catch that and encode to bytes.
+        if not isinstance(chunk, bytes):
+            chunk = chunk.encode(getattr(fileobj, 'encoding', '') or 'utf-8')
         linear_hash.update(chunk)
         chunks.append(hashlib.sha256(chunk).digest())
         chunk = fileobj.read(chunk_size)

{"golden_diff": "diff --git a/boto/glacier/utils.py b/boto/glacier/utils.py\n--- a/boto/glacier/utils.py\n+++ b/boto/glacier/utils.py\n@@ -23,6 +23,8 @@\n import math\n import binascii\n \n+from boto.compat import six\n+\n \n _MEGABYTE = 1024 * 1024\n DEFAULT_PART_SIZE = 4 * _MEGABYTE\n@@ -122,10 +124,19 @@\n         are returned in hex.\n \n     \"\"\"\n+    # Python 3+, not binary\n+    if six.PY3 and hasattr(fileobj, 'mode') and 'b' not in fileobj.mode:\n+        raise ValueError('File-like object must be opened in binary mode!')\n+\n     linear_hash = hashlib.sha256()\n     chunks = []\n-    chunk = fileobj.read(chunk_size).encode('utf-8')\n+    chunk = fileobj.read(chunk_size)\n     while chunk:\n+        # It's possible to get a file-like object that has no mode (checked\n+        # above) and returns something other than bytes (e.g. str). So here\n+        # we try to catch that and encode to bytes.\n+        if not isinstance(chunk, bytes):\n+            chunk = chunk.encode(getattr(fileobj, 'encoding', '') or 'utf-8')\n         linear_hash.update(chunk)\n         chunks.append(hashlib.sha256(chunk).digest())\n         chunk = fileobj.read(chunk_size)\n", "issue": "boto.glacier.utils.compute_hashes_from_fileobj No Longer Works with Binary Files\nCommit a4c9a781f47a61ddde2b3a3802b93c1ed29cdf16 added a `.encode('utf-8')` to the result of the first read in `boto.glacier.utils.compute_hashes_from_fileobj` (although it doesn't attempt to encode the results of the reads in the loop below that).\n\nThis breaks for binary files for me with python 2.6.6:\n\n```\n(glacier)[cperl@localhost ~]$ dd if=/dev/urandom of=/tmp/foo.bin bs=1M count=1\n1+0 records in\n1+0 records out\n1048576 bytes (1.0 MB) copied, 0.110299 s, 9.5 MB/s\n\n(glacier)[cperl@localhost ~]$ python\nPython 2.6.6 (r266:84292, Nov 22 2013, 12:16:22) \n[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n>>> import boto.glacier.utils\n>>> with open(\"/tmp/foo.bin\", 'r') as f:\n...     boto.glacier.utils.compute_hashes_from_fileobj(f)\n... \nTraceback (most recent call last):\n  File \"<stdin>\", line 2, in <module>\n  File \"/home/cperl/virtualenv/glacier/lib/python2.6/site-packages/boto/glacier/utils.py\", line 127, in compute_hashes_from_fileobj\n    chunk = fileobj.read(chunk_size).encode('utf-8')\nUnicodeDecodeError: 'ascii' codec can't decode byte 0xf4 in position 0: ordinal not in range(128)\n```\n\n", "before_files": [{"content": "# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.  All Rights Reserved\n#\n# Permission is hereby granted, free of charge, to any person obtaining a\n# copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish, dis-\n# tribute, sublicense, and/or sell copies of the Software, and to permit\n# persons to whom the Software is furnished to do so, subject to the fol-\n# lowing conditions:\n#\n# The above copyright notice and this permission notice shall be included\n# in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-\n# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\n# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n# IN THE SOFTWARE.\n#\nimport hashlib\nimport math\nimport binascii\n\n\n_MEGABYTE = 1024 * 1024\nDEFAULT_PART_SIZE = 4 * _MEGABYTE\nMAXIMUM_NUMBER_OF_PARTS = 10000\n\n\ndef minimum_part_size(size_in_bytes, default_part_size=DEFAULT_PART_SIZE):\n    \"\"\"Calculate the minimum part size needed for a multipart upload.\n\n    Glacier allows a maximum of 10,000 parts per upload.  It also\n    states that the maximum archive size is 10,000 * 4 GB, which means\n    the part size can range from 1MB to 4GB (provided it is one 1MB\n    multiplied by a power of 2).\n\n    This function will compute what the minimum part size must be in\n    order to upload a file of size ``size_in_bytes``.\n\n    It will first check if ``default_part_size`` is sufficient for\n    a part size given the ``size_in_bytes``.  If this is not the case,\n    then the smallest part size than can accomodate a file of size\n    ``size_in_bytes`` will be returned.\n\n    If the file size is greater than the maximum allowed archive\n    size of 10,000 * 4GB, a ``ValueError`` will be raised.\n\n    \"\"\"\n    # The default part size (4 MB) will be too small for a very large\n    # archive, as there is a limit of 10,000 parts in a multipart upload.\n    # This puts the maximum allowed archive size with the default part size\n    # at 40,000 MB. We need to do a sanity check on the part size, and find\n    # one that works if the default is too small.\n    part_size = _MEGABYTE\n    if (default_part_size * MAXIMUM_NUMBER_OF_PARTS) < size_in_bytes:\n        if size_in_bytes > (4096 * _MEGABYTE * 10000):\n            raise ValueError(\"File size too large: %s\" % size_in_bytes)\n        min_part_size = size_in_bytes / 10000\n        power = 3\n        while part_size < min_part_size:\n            part_size = math.ldexp(_MEGABYTE, power)\n            power += 1\n        part_size = int(part_size)\n    else:\n        part_size = default_part_size\n    return part_size\n\n\ndef chunk_hashes(bytestring, chunk_size=_MEGABYTE):\n    chunk_count = int(math.ceil(len(bytestring) / float(chunk_size)))\n    hashes = []\n    for i in range(chunk_count):\n        start = i * chunk_size\n        end = (i + 1) * chunk_size\n        hashes.append(hashlib.sha256(bytestring[start:end]).digest())\n    if not hashes:\n        return [hashlib.sha256(b'').digest()]\n    return hashes\n\n\ndef tree_hash(fo):\n    \"\"\"\n    Given a hash of each 1MB chunk (from chunk_hashes) this will hash\n    together adjacent hashes until it ends up with one big one. So a\n    tree of hashes.\n    \"\"\"\n    hashes = []\n    hashes.extend(fo)\n    while len(hashes) > 1:\n        new_hashes = []\n        while True:\n            if len(hashes) > 1:\n                first = hashes.pop(0)\n                second = hashes.pop(0)\n                new_hashes.append(hashlib.sha256(first + second).digest())\n            elif len(hashes) == 1:\n                only = hashes.pop(0)\n                new_hashes.append(only)\n            else:\n                break\n        hashes.extend(new_hashes)\n    return hashes[0]\n\n\ndef compute_hashes_from_fileobj(fileobj, chunk_size=1024 * 1024):\n    \"\"\"Compute the linear and tree hash from a fileobj.\n\n    This function will compute the linear/tree hash of a fileobj\n    in a single pass through the fileobj.\n\n    :param fileobj: A file like object.\n\n    :param chunk_size: The size of the chunks to use for the tree\n        hash.  This is also the buffer size used to read from\n        `fileobj`.\n\n    :rtype: tuple\n    :return: A tuple of (linear_hash, tree_hash).  Both hashes\n        are returned in hex.\n\n    \"\"\"\n    linear_hash = hashlib.sha256()\n    chunks = []\n    chunk = fileobj.read(chunk_size).encode('utf-8')\n    while chunk:\n        linear_hash.update(chunk)\n        chunks.append(hashlib.sha256(chunk).digest())\n        chunk = fileobj.read(chunk_size)\n    if not chunks:\n        chunks = [hashlib.sha256(b'').digest()]\n    return linear_hash.hexdigest(), bytes_to_hex(tree_hash(chunks))\n\n\ndef bytes_to_hex(str_as_bytes):\n    return binascii.hexlify(str_as_bytes)\n\n\ndef tree_hash_from_str(str_as_bytes):\n    \"\"\"\n\n    :type str_as_bytes: str\n    :param str_as_bytes: The string for which to compute the tree hash.\n\n    :rtype: str\n    :return: The computed tree hash, returned as hex.\n\n    \"\"\"\n    return bytes_to_hex(tree_hash(chunk_hashes(str_as_bytes)))\n\n\nclass ResettingFileSender(object):\n    def __init__(self, archive):\n        self._archive = archive\n        self._starting_offset = archive.tell()\n\n    def __call__(self, connection, method, path, body, headers):\n        try:\n            connection.request(method, path, self._archive, headers)\n            return connection.getresponse()\n        finally:\n            self._archive.seek(self._starting_offset)\n", "path": "boto/glacier/utils.py"}], "after_files": [{"content": "# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.  All Rights Reserved\n#\n# Permission is hereby granted, free of charge, to any person obtaining a\n# copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish, dis-\n# tribute, sublicense, and/or sell copies of the Software, and to permit\n# persons to whom the Software is furnished to do so, subject to the fol-\n# lowing conditions:\n#\n# The above copyright notice and this permission notice shall be included\n# in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-\n# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\n# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n# IN THE SOFTWARE.\n#\nimport hashlib\nimport math\nimport binascii\n\nfrom boto.compat import six\n\n\n_MEGABYTE = 1024 * 1024\nDEFAULT_PART_SIZE = 4 * _MEGABYTE\nMAXIMUM_NUMBER_OF_PARTS = 10000\n\n\ndef minimum_part_size(size_in_bytes, default_part_size=DEFAULT_PART_SIZE):\n    \"\"\"Calculate the minimum part size needed for a multipart upload.\n\n    Glacier allows a maximum of 10,000 parts per upload.  It also\n    states that the maximum archive size is 10,000 * 4 GB, which means\n    the part size can range from 1MB to 4GB (provided it is one 1MB\n    multiplied by a power of 2).\n\n    This function will compute what the minimum part size must be in\n    order to upload a file of size ``size_in_bytes``.\n\n    It will first check if ``default_part_size`` is sufficient for\n    a part size given the ``size_in_bytes``.  If this is not the case,\n    then the smallest part size than can accomodate a file of size\n    ``size_in_bytes`` will be returned.\n\n    If the file size is greater than the maximum allowed archive\n    size of 10,000 * 4GB, a ``ValueError`` will be raised.\n\n    \"\"\"\n    # The default part size (4 MB) will be too small for a very large\n    # archive, as there is a limit of 10,000 parts in a multipart upload.\n    # This puts the maximum allowed archive size with the default part size\n    # at 40,000 MB. We need to do a sanity check on the part size, and find\n    # one that works if the default is too small.\n    part_size = _MEGABYTE\n    if (default_part_size * MAXIMUM_NUMBER_OF_PARTS) < size_in_bytes:\n        if size_in_bytes > (4096 * _MEGABYTE * 10000):\n            raise ValueError(\"File size too large: %s\" % size_in_bytes)\n        min_part_size = size_in_bytes / 10000\n        power = 3\n        while part_size < min_part_size:\n            part_size = math.ldexp(_MEGABYTE, power)\n            power += 1\n        part_size = int(part_size)\n    else:\n        part_size = default_part_size\n    return part_size\n\n\ndef chunk_hashes(bytestring, chunk_size=_MEGABYTE):\n    chunk_count = int(math.ceil(len(bytestring) / float(chunk_size)))\n    hashes = []\n    for i in range(chunk_count):\n        start = i * chunk_size\n        end = (i + 1) * chunk_size\n        hashes.append(hashlib.sha256(bytestring[start:end]).digest())\n    if not hashes:\n        return [hashlib.sha256(b'').digest()]\n    return hashes\n\n\ndef tree_hash(fo):\n    \"\"\"\n    Given a hash of each 1MB chunk (from chunk_hashes) this will hash\n    together adjacent hashes until it ends up with one big one. So a\n    tree of hashes.\n    \"\"\"\n    hashes = []\n    hashes.extend(fo)\n    while len(hashes) > 1:\n        new_hashes = []\n        while True:\n            if len(hashes) > 1:\n                first = hashes.pop(0)\n                second = hashes.pop(0)\n                new_hashes.append(hashlib.sha256(first + second).digest())\n            elif len(hashes) == 1:\n                only = hashes.pop(0)\n                new_hashes.append(only)\n            else:\n                break\n        hashes.extend(new_hashes)\n    return hashes[0]\n\n\ndef compute_hashes_from_fileobj(fileobj, chunk_size=1024 * 1024):\n    \"\"\"Compute the linear and tree hash from a fileobj.\n\n    This function will compute the linear/tree hash of a fileobj\n    in a single pass through the fileobj.\n\n    :param fileobj: A file like object.\n\n    :param chunk_size: The size of the chunks to use for the tree\n        hash.  This is also the buffer size used to read from\n        `fileobj`.\n\n    :rtype: tuple\n    :return: A tuple of (linear_hash, tree_hash).  Both hashes\n        are returned in hex.\n\n    \"\"\"\n    # Python 3+, not binary\n    if six.PY3 and hasattr(fileobj, 'mode') and 'b' not in fileobj.mode:\n        raise ValueError('File-like object must be opened in binary mode!')\n\n    linear_hash = hashlib.sha256()\n    chunks = []\n    chunk = fileobj.read(chunk_size)\n    while chunk:\n        # It's possible to get a file-like object that has no mode (checked\n        # above) and returns something other than bytes (e.g. str). So here\n        # we try to catch that and encode to bytes.\n        if not isinstance(chunk, bytes):\n            chunk = chunk.encode(getattr(fileobj, 'encoding', '') or 'utf-8')\n        linear_hash.update(chunk)\n        chunks.append(hashlib.sha256(chunk).digest())\n        chunk = fileobj.read(chunk_size)\n    if not chunks:\n        chunks = [hashlib.sha256(b'').digest()]\n    return linear_hash.hexdigest(), bytes_to_hex(tree_hash(chunks))\n\n\ndef bytes_to_hex(str_as_bytes):\n    return binascii.hexlify(str_as_bytes)\n\n\ndef tree_hash_from_str(str_as_bytes):\n    \"\"\"\n\n    :type str_as_bytes: str\n    :param str_as_bytes: The string for which to compute the tree hash.\n\n    :rtype: str\n    :return: The computed tree hash, returned as hex.\n\n    \"\"\"\n    return bytes_to_hex(tree_hash(chunk_hashes(str_as_bytes)))\n\n\nclass ResettingFileSender(object):\n    def __init__(self, archive):\n        self._archive = archive\n        self._starting_offset = archive.tell()\n\n    def __call__(self, connection, method, path, body, headers):\n        try:\n            connection.request(method, path, self._archive, headers)\n            return connection.getresponse()\n        finally:\n            self._archive.seek(self._starting_offset)\n", "path": "boto/glacier/utils.py"}]}