From 387eebc4d69e0c89fd27a0bbe20ab31a5ea4be20 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 13 Dec 2017 11:51:34 +0100 Subject: [PATCH] update: strip all metadata from PNGs This strips metadata and optimizes the compression of all PNGs copied from the app's source repo as well as all the icons extracted from the APKs. There have been exploits delivered via image metadata, and F-Droid isn't using it all, so its best to just remove it. This unfortunately uncompresses and recompresses the files. Luckily, that's a lossless procedure with PNGs, and we might end up with smaller files. The only tool I could find that strips without changing the image data is exiftool, but that is written in Perl. --- fdroidserver/update.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/fdroidserver/update.py b/fdroidserver/update.py index b6a33f77..4611b127 100644 --- a/fdroidserver/update.py +++ b/fdroidserver/update.py @@ -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): @@ -1512,7 +1523,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)