diff --git a/api/handlers/listhandler.py b/api/handlers/listhandler.py
index 81a59942dad5e2abdec27abaf54339358180d453..e4f34312b540e81534c3dbc35f857097a3879c16 100644
--- a/api/handlers/listhandler.py
+++ b/api/handlers/listhandler.py
@@ -342,7 +342,8 @@ class FileListHandler(ListHandler):
             self.abort(400, 'ticket not for this resource or source IP')
         return ticket
 
-    def _build_zip_info(self, filepath):
+    @staticmethod
+    def build_zip_info(filepath):
         """
         Builds a json response containing member and comment info for a zipfile
         """
@@ -436,7 +437,7 @@ class FileListHandler(ListHandler):
         # Request for info about zipfile
         elif self.is_true('info'):
             try:
-                info = self._build_zip_info(filepath)
+                info = self.build_zip_info(filepath)
             except zipfile.BadZipfile:
                 self.abort(400, 'not a zip file')
             return info
@@ -815,13 +816,43 @@ class AnalysesHandler(ListHandler):
                     util.path_from_hash(fileinfo['hash'])
                 )
                 filename = fileinfo['name']
-                self.response.app_iter = open(filepath, 'rb')
-                self.response.headers['Content-Length'] = str(fileinfo['size']) # must be set after setting app_iter
-                if self.is_true('view'):
-                    self.response.headers['Content-Type'] = str(fileinfo.get('mimetype', 'application/octet-stream'))
+
+                # Request for info about zipfile
+                if self.is_true('info'):
+                    try:
+                        info = FileListHandler.build_zip_info(filepath)
+                    except zipfile.BadZipfile:
+                        self.abort(400, 'not a zip file')
+                    return info
+
+                # Request to download zipfile member
+                elif self.get_param('member') is not None:
+                    zip_member = self.get_param('member')
+                    try:
+                        with zipfile.ZipFile(filepath) as zf:
+                            self.response.headers['Content-Type'] = util.guess_mimetype(zip_member)
+                            self.response.write(zf.open(zip_member).read())
+                    except zipfile.BadZipfile:
+                        self.abort(400, 'not a zip file')
+                    except KeyError:
+                        self.abort(400, 'zip file contains no such member')
+                    # log download if we haven't already for this ticket
+                    if ticket:
+                        if not ticket.get('logged', False):
+                            self.log_user_access(AccessType.download_file, cont_name=cont_name, cont_id=_id)
+                            config.db.downloads.update_one({'_id': ticket_id}, {'$set': {'logged': True}})
+                    else:
+                        self.log_user_access(AccessType.download_file, cont_name=cont_name, cont_id=_id)
+
+                # Request to download the file itself
                 else:
-                    self.response.headers['Content-Type'] = 'application/octet-stream'
-                    self.response.headers['Content-Disposition'] = 'attachment; filename=' + str(filename)
+                    self.response.app_iter = open(filepath, 'rb')
+                    self.response.headers['Content-Length'] = str(fileinfo['size']) # must be set after setting app_iter
+                    if self.is_true('view'):
+                        self.response.headers['Content-Type'] = str(fileinfo.get('mimetype', 'application/octet-stream'))
+                    else:
+                        self.response.headers['Content-Type'] = 'application/octet-stream'
+                        self.response.headers['Content-Disposition'] = 'attachment; filename=' + str(filename)
 
             # log download if we haven't already for this ticket
             if ticket: