Repository: python-telegram-bot/python-telegram-bot

Issue:
[BUG] Passing non-bytes file input leads to error
https://t.me/pythontelegrambotgroup/396541

TL;DR:

`send_document(open('text_file', 'rb'))` works but `send_document(open('text_file', 'r'))` raises is error.
This is, because we try to guess if the file is an image using `imghdr.what(None, stream)` in `InputFile.is_image`, which only works if `stream` is a bytes stream.
If I comment the `is_image` out, the file is sent without issue, so I guess we should just check if the input is bytes before calling `is_image`


Discussion:
After some internal discussion, it's probably better to just 

```python
try:
    image = self.is_image(None, stream)
    …
except Exception:
    logger.debug('Cant check if image, assuming that not', exc_info=True)
    return False
```

Resolving patch:
diff --git a/telegram/files/inputfile.py b/telegram/files/inputfile.py
--- a/telegram/files/inputfile.py
+++ b/telegram/files/inputfile.py
@@ -20,14 +20,14 @@
 """This module contains an object that represents a Telegram InputFile."""
 
 import imghdr
+import logging
 import mimetypes
 import os
 from typing import IO, Optional, Tuple
 from uuid import uuid4
 
-from telegram import TelegramError
-
 DEFAULT_MIME_TYPE = 'application/octet-stream'
+logger = logging.getLogger(__name__)
 
 
 class InputFile:
@@ -59,13 +59,14 @@ def __init__(self, obj: IO, filename: str = None, attach: bool = None):
         elif hasattr(obj, 'name') and not isinstance(obj.name, int):
             self.filename = os.path.basename(obj.name)
 
-        try:
-            self.mimetype = self.is_image(self.input_file_content)
-        except TelegramError:
-            if self.filename:
-                self.mimetype = mimetypes.guess_type(self.filename)[0] or DEFAULT_MIME_TYPE
-            else:
-                self.mimetype = DEFAULT_MIME_TYPE
+        image_mime_type = self.is_image(self.input_file_content)
+        if image_mime_type:
+            self.mimetype = image_mime_type
+        elif self.filename:
+            self.mimetype = mimetypes.guess_type(self.filename)[0] or DEFAULT_MIME_TYPE
+        else:
+            self.mimetype = DEFAULT_MIME_TYPE
+
         if not self.filename:
             self.filename = self.mimetype.replace('/', '.')
 
@@ -74,21 +75,27 @@ def field_tuple(self) -> Tuple[str, bytes, str]:
         return self.filename, self.input_file_content, self.mimetype
 
     @staticmethod
-    def is_image(stream: bytes) -> str:
+    def is_image(stream: bytes) -> Optional[str]:
         """Check if the content file is an image by analyzing its headers.
 
         Args:
             stream (:obj:`bytes`): A byte stream representing the content of a file.
 
         Returns:
-            :obj:`str`: The str mime-type of an image.
+            :obj:`str` | :obj:`None`: The mime-type of an image, if the input is an image, or
+            :obj:`None` else.
 
         """
-        image = imghdr.what(None, stream)
-        if image:
-            return 'image/%s' % image
-
-        raise TelegramError('Could not parse file content')
+        try:
+            image = imghdr.what(None, stream)
+            if image:
+                return f'image/{image}'
+            return None
+        except Exception:
+            logger.debug(
+                "Could not parse file content. Assuming that file is not an image.", exc_info=True
+            )
+            return None
 
     @staticmethod
     def is_file(obj: object) -> bool:


Test patch:
diff --git a/tests/data/text_file.txt b/tests/data/text_file.txt
new file mode 100644
--- /dev/null
+++ b/tests/data/text_file.txt
@@ -0,0 +1 @@
+PTB Rocks!
\ No newline at end of file
diff --git a/tests/test_inputfile.py b/tests/test_inputfile.py
--- a/tests/test_inputfile.py
+++ b/tests/test_inputfile.py
@@ -17,6 +17,7 @@
 #
 # You should have received a copy of the GNU Lesser Public License
 # along with this program.  If not, see [http://www.gnu.org/licenses/].
+import logging
 import os
 import subprocess
 import sys
@@ -48,7 +49,7 @@ def test_subprocess_pipe(self):
             # to kill it.
             pass
 
-    def test_mimetypes(self):
+    def test_mimetypes(self, caplog):
         # Only test a few to make sure logic works okay
         assert InputFile(open('tests/data/telegram.jpg', 'rb')).mimetype == 'image/jpeg'
         assert InputFile(open('tests/data/telegram.webp', 'rb')).mimetype == 'image/webp'
@@ -65,6 +66,13 @@ def test_mimetypes(self):
         )
         assert InputFile(BytesIO(b'blah')).mimetype == 'application/octet-stream'
 
+        # Test string file
+        with caplog.at_level(logging.DEBUG):
+            assert InputFile(open('tests/data/text_file.txt', 'r')).mimetype == 'text/plain'
+
+            assert len(caplog.records) == 1
+            assert caplog.records[0].getMessage().startswith('Could not parse file content')
+
     def test_filenames(self):
         assert InputFile(open('tests/data/telegram.jpg', 'rb')).filename == 'telegram.jpg'
         assert InputFile(open('tests/data/telegram.jpg', 'rb'), filename='blah').filename == 'blah'