chiark / gitweb /
fix handling unreadable images in update.extract_apk_icons
[fdroidserver.git] / fdroidserver / update.py
index b6a33f77674eed7c0667dd16aedfcb7cd184f250..3015e337ca6325813337e1a8c6bb6dd66a7f4178 100644 (file)
@@ -35,7 +35,7 @@ from argparse import ArgumentParser
 import collections
 from binascii import hexlify
 
-from PIL import Image
+from PIL import Image, PngImagePlugin
 import logging
 
 from . import _
@@ -84,6 +84,8 @@ GRAPHIC_NAMES = ('featureGraphic', 'icon', 'promoGraphic', 'tvBanner')
 SCREENSHOT_DIRS = ('phoneScreenshots', 'sevenInchScreenshots',
                    'tenInchScreenshots', 'tvScreenshots', 'wearScreenshots')
 
+BLANK_PNG_INFO = PngImagePlugin.PngInfo()
+
 
 def dpi_to_px(density):
     return (int(density) * 48) / 160
@@ -371,7 +373,8 @@ def resize_icon(iconpath, density):
             im.thumbnail((size, size), Image.ANTIALIAS)
             logging.debug("%s was too large at %s - new size is %s" % (
                 iconpath, oldsize, im.size))
-            im.save(iconpath, "PNG")
+            im.save(iconpath, "PNG", optimize=True,
+                    pnginfo=BLANK_PNG_INFO, icc_profile=None)
 
     except Exception as e:
         logging.error(_("Failed resizing {path}: {error}".format(path=iconpath, error=e)))
@@ -677,20 +680,28 @@ def _strip_and_copy_image(inpath, outpath):
 
     Sadly, image metadata like EXIF can be used to exploit devices.
     It is not used at all in the F-Droid ecosystem, so its much safer
-    just to remove it entirely.  PNG does not have the same kind of
-    issues.
+    just to remove it entirely.
 
     """
 
-    if common.has_extension(inpath, 'png'):
-        shutil.copy(inpath, outpath)
-    else:
-        with open(inpath) as fp:
+    extension = common.get_extension(inpath)[1]
+    if os.path.isdir(outpath):
+        outpath = os.path.join(outpath, os.path.basename(inpath))
+    if extension == 'png':
+        with open(inpath, 'rb') as fp:
+            in_image = Image.open(fp)
+            in_image.save(outpath, "PNG", optimize=True,
+                          pnginfo=BLANK_PNG_INFO, icc_profile=None)
+    elif extension == 'jpg' or extension == 'jpeg':
+        with open(inpath, 'rb') as fp:
             in_image = Image.open(fp)
             data = list(in_image.getdata())
             out_image = Image.new(in_image.mode, in_image.size)
         out_image.putdata(data)
         out_image.save(outpath, "JPEG", optimize=True)
+    else:
+        raise FDroidException(_('Unsupported file type "{extension}" for repo graphic')
+                              .format(extension=extension))
 
 
 def copy_triple_t_store_metadata(apps):
@@ -1344,10 +1355,13 @@ def process_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk=Fal
         apkzip = zipfile.ZipFile(apkfile, 'r')
 
         manifest = apkzip.getinfo('AndroidManifest.xml')
-        if manifest.date_time[1] == 0:  # month can't be zero
-            logging.debug(_('AndroidManifest.xml has no date'))
-        else:
-            common.check_system_clock(datetime(*manifest.date_time), apkfilename)
+        # 1980-0-0 means zeroed out, any other invalid date should trigger a warning
+        if (1980, 0, 0) != manifest.date_time[0:3]:
+            try:
+                common.check_system_clock(datetime(*manifest.date_time), apkfilename)
+            except ValueError as e:
+                logging.warning(_("{apkfilename}'s AndroidManifest.xml has a bad date: ")
+                                .format(apkfilename=apkfile) + str(e))
 
         # extract icons from APK zip file
         iconfilename = "%s.%s.png" % (apk['packageName'], apk['versionCode'])
@@ -1461,6 +1475,7 @@ def extract_apk_icons(icon_filename, apk, apkzip, repo_dir):
         icon_path = os.path.join(get_icon_dir(repo_dir, '0'), icon_filename)
         with open(icon_path, 'wb') as f:
             f.write(get_icon_bytes(apkzip, icon_src))
+        im = None
         try:
             im = Image.open(icon_path)
             dpi = px_to_dpi(im.size[0])
@@ -1476,6 +1491,9 @@ def extract_apk_icons(icon_filename, apk, apkzip, repo_dir):
         except Exception as e:
             logging.warning(_("Failed reading {path}: {error}")
                             .format(path=icon_path, error=e))
+        finally:
+            if im:
+                im.close()
 
     if apk['icons']:
         apk['icon'] = icon_filename
@@ -1512,7 +1530,8 @@ def fill_missing_icon_densities(empty_densities, icon_filename, apk, repo_dir):
             size = dpi_to_px(density)
 
             im.thumbnail((size, size), Image.ANTIALIAS)
-            im.save(icon_path, "PNG")
+            im.save(icon_path, "PNG", optimize=True,
+                    pnginfo=BLANK_PNG_INFO, icc_profile=None)
             empty_densities.remove(density)
         except Exception as e:
             logging.warning("Invalid image file at %s: %s", last_icon_path, e)