Add a method to zip api to get the list of files inside it.

This new functionality will be used to inspect zip files and determine
if they need to be code signed based on metadata.

Bug: https://github.com/flutter/flutter/issues/124055
Change-Id: I7af1bd0f2130e0c7ed49fa5320941d506114b248
Reviewed-on: https://flutter-review.googlesource.com/c/recipes/+/41540
Reviewed-by: Ricardo Amador <ricardoamador@google.com>
Commit-Queue: Godofredo Contreras <godofredoc@google.com>
diff --git a/recipe_modules/zip/api.py b/recipe_modules/zip/api.py
index c955cb1..c150ad2 100644
--- a/recipe_modules/zip/api.py
+++ b/recipe_modules/zip/api.py
@@ -41,6 +41,24 @@
     pkg.add_directory(directory)
     pkg.zip(step_name)
 
+  def namelist(self, step_name, zip_file):
+    """Step to get the name list of |zip_file|.
+
+    Args:
+      step_name: display name of a step.
+      zip_file: path to a zip file to get its namelist, should exist.
+    """
+    script_input = {
+      'zip_file': str(zip_file),
+    }
+    names_step = self.m.step(
+      step_name,
+      [ 'python3', self.resource('namelist.py') ],
+      stdin=self.m.json.input(script_input),
+      stdout=self.m.json.output(),
+    )
+    return names_step.stdout or []
+
   def unzip(self, step_name, zip_file, output, quiet=False):
     """Step to uncompress |zip_file| into |output| directory.
 
diff --git a/recipe_modules/zip/examples/full.expected/linux.json b/recipe_modules/zip/examples/full.expected/linux.json
index 107db36..e2ecff0 100644
--- a/recipe_modules/zip/examples/full.expected/linux.json
+++ b/recipe_modules/zip/examples/full.expected/linux.json
@@ -75,6 +75,20 @@
   },
   {
     "cmd": [
+      "python3",
+      "RECIPE_MODULE[flutter::zip]/resources/namelist.py"
+    ],
+    "name": "namelist",
+    "stdin": "{\"zip_file\": \"[CLEANUP]/zip-example_tmp_1/output.zip\"}",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@[@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"/a/b/c.txt\"@@@",
+      "@@@STEP_LOG_LINE@json.output@]@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
       "vpython3",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
diff --git a/recipe_modules/zip/examples/full.expected/mac.json b/recipe_modules/zip/examples/full.expected/mac.json
index 107db36..e2ecff0 100644
--- a/recipe_modules/zip/examples/full.expected/mac.json
+++ b/recipe_modules/zip/examples/full.expected/mac.json
@@ -75,6 +75,20 @@
   },
   {
     "cmd": [
+      "python3",
+      "RECIPE_MODULE[flutter::zip]/resources/namelist.py"
+    ],
+    "name": "namelist",
+    "stdin": "{\"zip_file\": \"[CLEANUP]/zip-example_tmp_1/output.zip\"}",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@[@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"/a/b/c.txt\"@@@",
+      "@@@STEP_LOG_LINE@json.output@]@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
       "vpython3",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
diff --git a/recipe_modules/zip/examples/full.expected/win.json b/recipe_modules/zip/examples/full.expected/win.json
index dfc9302..d06edb3 100644
--- a/recipe_modules/zip/examples/full.expected/win.json
+++ b/recipe_modules/zip/examples/full.expected/win.json
@@ -75,6 +75,20 @@
   },
   {
     "cmd": [
+      "python3",
+      "RECIPE_MODULE[flutter::zip]\\resources\\namelist.py"
+    ],
+    "name": "namelist",
+    "stdin": "{\"zip_file\": \"[CLEANUP]\\\\zip-example_tmp_1\\\\output.zip\"}",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@[@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"/a/b/c.txt\"@@@",
+      "@@@STEP_LOG_LINE@json.output@]@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
       "vpython3",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
diff --git a/recipe_modules/zip/examples/full.py b/recipe_modules/zip/examples/full.py
index 5e99730..a3aaf08 100644
--- a/recipe_modules/zip/examples/full.py
+++ b/recipe_modules/zip/examples/full.py
@@ -9,6 +9,7 @@
   'recipe_engine/path',
   'recipe_engine/platform',
   'recipe_engine/step',
+  'recipe_engine/json',
 ]
 
 def RunSteps(api):
@@ -38,6 +39,12 @@
   # List unzipped content.
   with api.context(cwd=temp.join('output')):
     api.step('listing', ['find'])
+
+  # Retrieve zip file names list.
+  expected_namelist = ['/a/b/c.txt']
+  namelist = api.zip.namelist('namelist', temp.join('output.zip'))
+  assert namelist == expected_namelist
+
   # Clean up.
   api.file.rmtree('cleanup', temp)
 
@@ -47,4 +54,5 @@
     yield api.test(
         platform,
         api.platform.name(platform),
+        api.zip.namelist('namelist', ['/a/b/c.txt'])
     )
diff --git a/recipe_modules/zip/resources/namelist.py b/recipe_modules/zip/resources/namelist.py
new file mode 100644
index 0000000..1ab5dec
--- /dev/null
+++ b/recipe_modules/zip/resources/namelist.py
@@ -0,0 +1,24 @@
+# Copyright 2023 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import json
+import os
+import sys
+import zipfile
+
+def main():
+  # See zip/api.py, def unzip(...) for format of |data|.
+  data = json.load(sys.stdin)
+  zip_file = data['zip_file']
+
+  # Archive path should exist and be an absolute path to a file.
+  assert os.path.exists(zip_file), zip_file
+  assert os.path.isfile(zip_file), zip_file
+
+  with zipfile.ZipFile(zip_file) as artifact_zip:
+    json.dumps(artifact_zip.namelist())
+  return 0
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/recipe_modules/zip/test_api.py b/recipe_modules/zip/test_api.py
new file mode 100644
index 0000000..185dbf8
--- /dev/null
+++ b/recipe_modules/zip/test_api.py
@@ -0,0 +1,25 @@
+# Copyright 2023 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import json
+
+from recipe_engine import recipe_test_api
+
+class ZipTestApi(recipe_test_api.RecipeTestApi): # pragma: no cover
+  """Test api for zip module."""
+
+  def namelist(self, name, output):
+    """Generated namelist step data for testing.
+
+    Args:
+      name: The name of the step to generate step data for.
+      output: A list of strings representing the names of files
+        inside the zip file.
+    """
+    return self.override_step_data(
+        name,
+        stdout=self.m.json.output(output),
+        retcode=0
+    )
+