We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Support for packageurl
Support to identify package with package url would be nice and it will be easy to integrate with other tool chain.
https://github.com/package-url/purl-spec

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `gcp/api/server.py`
Content:
```
1 # Copyright 2021 Google LLC
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 """API server implementation."""
15 
16 import argparse
17 from concurrent import futures
18 import functools
19 import logging
20 import os
21 import sys
22 import time
23 
24 from google.cloud import ndb
25 import grpc
26 
27 import osv
28 from osv import ecosystems
29 from osv import semver_index
30 import osv_service_v1_pb2
31 import osv_service_v1_pb2_grpc
32 
33 _PROJECT = 'oss-vdb'
34 _OSS_FUZZ_TRACKER_URL = 'https://bugs.chromium.org/p/oss-fuzz/issues/detail?id='
35 
36 _SHUTDOWN_GRACE_DURATION = 5
37 
38 _AUTHORIZATION_HEADER_PREFIX = 'Bearer '
39 _EXPECTED_AUDIENCE = 'https://db.oss-fuzz.com'
40 
41 _ndb_client = ndb.Client()
42 
43 
44 def ndb_context(func):
45   """Wrapper to create an NDB context."""
46 
47   @functools.wraps(func)
48   def wrapper(*args, **kwargs):
49     with _ndb_client.context():
50       return func(*args, **kwargs)
51 
52   return wrapper
53 
54 
55 class OSVServicer(osv_service_v1_pb2_grpc.OSVServicer):
56   """V1 OSV servicer."""
57 
58   @ndb_context
59   def GetVulnById(self, request, context):
60     """Return a `Vulnerability` object for a given OSV ID.
61     """
62     bug = osv.Bug.get_by_id(request.id)
63     if not bug or bug.status == osv.BugStatus.UNPROCESSED:
64       context.abort(grpc.StatusCode.NOT_FOUND, 'Bug not found.')
65       return None
66 
67     if not bug.public:
68       context.abort(grpc.StatusCode.PERMISSION_DENIED, 'Permission denied.')
69       return None
70 
71     return bug_to_response(bug)
72 
73   @ndb_context
74   def QueryAffected(self, request, context):
75     """Query vulnerabilities for a particular project at a given commit or
76     version."""
77     if request.query.HasField('package'):
78       package_name = request.query.package.name
79       ecosystem = request.query.package.ecosystem
80     else:
81       package_name = ''
82       ecosystem = ''
83 
84     if request.query.WhichOneof('param') == 'commit':
85       bugs = query_by_commit(request.query.commit, to_response=bug_to_response)
86     elif request.query.WhichOneof('param') == 'version':
87       bugs = query_by_version(
88           package_name,
89           ecosystem,
90           request.query.version,
91           to_response=bug_to_response)
92     else:
93       context.abort(grpc.StatusCode.INVALID_ARGUMENT, 'Invalid query.')
94 
95     return osv_service_v1_pb2.VulnerabilityList(vulns=bugs)
96 
97   def GetVulnByIdNew(self, request, context):
98     """Return a `Vulnerability` object for a given OSV ID.
99     """
100     return self.GetVulnById(request, context)
101 
102   def QueryAffectedNew(self, request, context):
103     """Query vulnerabilities for a particular project at a given commit or
104     version."""
105     return self.QueryAffected(request, context)
106 
107 
108 def bug_to_response(bug):
109   """Convert a Bug entity to a response object."""
110   return bug.to_vulnerability(v0_7=False, v0_8=True, include_source=True)
111 
112 
113 def _get_bugs(bug_ids, to_response=bug_to_response):
114   """Get bugs from bug ids."""
115   bugs = ndb.get_multi([ndb.Key(osv.Bug, bug_id) for bug_id in bug_ids])
116   return [
117       to_response(bug)
118       for bug in bugs
119       if bug and bug.status == osv.BugStatus.PROCESSED
120   ]
121 
122 
123 def query_by_commit(commit, to_response=bug_to_response):
124   """Query by commit."""
125   query = osv.AffectedCommit.query(osv.AffectedCommit.commit == commit,
126                                    osv.AffectedCommit.public == True)  # pylint: disable=singleton-comparison
127   bug_ids = []
128   for affected_commit in query:
129     bug_ids.append(affected_commit.bug_id)
130 
131   return _get_bugs(bug_ids, to_response=to_response)
132 
133 
134 def _is_semver_affected(affected_packages, package_name, ecosystem, version):
135   """Returns whether or not the given version is within an affected SEMVER
136   range."""
137   version = semver_index.parse(version)
138 
139   affected = False
140   for affected_package in affected_packages:
141     if package_name and package_name != affected_package.package.name:
142       continue
143 
144     if ecosystem and ecosystem != affected_package.package.ecosystem:
145       continue
146 
147     for affected_range in affected_package.ranges:
148       if affected_range.type != 'SEMVER':
149         continue
150 
151       for event in osv.sorted_events('', affected_range.type,
152                                      affected_range.events):
153         if (event.type == 'introduced' and
154             (event.value == '0' or version >= semver_index.parse(event.value))):
155           affected = True
156 
157         if event.type == 'fixed' and version >= semver_index.parse(event.value):
158           affected = False
159 
160   return affected
161 
162 
163 def _query_by_semver(query, package_name, ecosystem, version):
164   """Query by semver."""
165   if not semver_index.is_valid(version):
166     return []
167 
168   query = query.filter(
169       osv.Bug.semver_fixed_indexes > semver_index.normalize(version))
170 
171   return [
172       bug for bug in query if _is_semver_affected(
173           bug.affected_packages, package_name, ecosystem, version)
174   ]
175 
176 
177 def _query_by_generic_version(base_query, version):
178   """Query by generic version."""
179   # Try without normalizing.
180   query = base_query.filter(osv.Bug.affected_fuzzy == version)
181   results = list(query)
182   if results:
183     return results
184 
185   # Try again after normalizing.
186   query = base_query.filter(
187       osv.Bug.affected_fuzzy == osv.normalize_tag(version))
188   return list(query)
189 
190 
191 def query_by_version(project, ecosystem, version, to_response=bug_to_response):
192   """Query by (fuzzy) version."""
193   ecosystem_info = ecosystems.get(ecosystem)
194   is_semver = ecosystem_info and ecosystem_info.is_semver
195   query = osv.Bug.query(osv.Bug.status == osv.BugStatus.PROCESSED,
196                         osv.Bug.project == project, osv.Bug.public == True)  # pylint: disable=singleton-comparison
197   if ecosystem:
198     query = query.filter(osv.Bug.ecosystem == ecosystem)
199 
200   bugs = []
201   if ecosystem:
202     if is_semver:
203       # Ecosystem supports semver only.
204       bugs.extend(_query_by_semver(query, project, ecosystem, version))
205     else:
206       bugs.extend(_query_by_generic_version(query, version))
207   else:
208     # Unspecified ecosystem. Try both.
209     bugs.extend(_query_by_semver(query, project, ecosystem, version))
210     bugs.extend(_query_by_generic_version(query, version))
211 
212   return [to_response(bug) for bug in bugs]
213 
214 
215 def serve(port):
216   """Configures and runs the bookstore API server."""
217   server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
218   osv_service_v1_pb2_grpc.add_OSVServicer_to_server(OSVServicer(), server)
219   server.add_insecure_port('[::]:{}'.format(port))
220   server.start()
221 
222   print('Listening on port {}'.format(port))
223   try:
224     while True:
225       time.sleep(3600)
226   except KeyboardInterrupt:
227     server.stop(_SHUTDOWN_GRACE_DURATION)
228 
229 
230 def main():
231   """Entrypoint."""
232   logging.basicConfig(stream=sys.stderr)
233   logging.getLogger().setLevel(logging.INFO)
234 
235   parser = argparse.ArgumentParser(
236       formatter_class=argparse.RawDescriptionHelpFormatter)
237   parser.add_argument(
238       '--port',
239       type=int,
240       default=None,
241       help='The port to listen on.'
242       'If arg is not set, will listen on the $PORT env var.'
243       'If env var is empty, defaults to 8000.')
244 
245   args = parser.parse_args()
246   port = args.port
247   if not port:
248     port = os.environ.get('PORT')
249   if not port:
250     port = 8000
251 
252   serve(port)
253 
254 
255 if __name__ == '__main__':
256   main()
257
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/gcp/api/server.py b/gcp/api/server.py
--- a/gcp/api/server.py
+++ b/gcp/api/server.py
@@ -77,9 +77,11 @@
     if request.query.HasField('package'):
       package_name = request.query.package.name
       ecosystem = request.query.package.ecosystem
+      purl = request.query.package.purl
     else:
       package_name = ''
       ecosystem = ''
+      purl = ''
 
     if request.query.WhichOneof('param') == 'commit':
       bugs = query_by_commit(request.query.commit, to_response=bug_to_response)
@@ -87,6 +89,7 @@
       bugs = query_by_version(
           package_name,
           ecosystem,
+          purl,
           request.query.version,
           to_response=bug_to_response)
     else:
@@ -131,7 +134,8 @@
   return _get_bugs(bug_ids, to_response=to_response)
 
 
-def _is_semver_affected(affected_packages, package_name, ecosystem, version):
+def _is_semver_affected(affected_packages, package_name, ecosystem, purl,
+                        version):
   """Returns whether or not the given version is within an affected SEMVER
   range."""
   version = semver_index.parse(version)
@@ -144,6 +148,9 @@
     if ecosystem and ecosystem != affected_package.package.ecosystem:
       continue
 
+    if purl and purl != affected_package.package.purl:
+      continue
+
     for affected_range in affected_package.ranges:
       if affected_range.type != 'SEMVER':
         continue
@@ -160,7 +167,7 @@
   return affected
 
 
-def _query_by_semver(query, package_name, ecosystem, version):
+def _query_by_semver(query, package_name, ecosystem, purl, version):
   """Query by semver."""
   if not semver_index.is_valid(version):
     return []
@@ -170,7 +177,7 @@
 
   return [
       bug for bug in query if _is_semver_affected(
-          bug.affected_packages, package_name, ecosystem, version)
+          bug.affected_packages, package_name, ecosystem, purl, version)
   ]
 
 
@@ -188,12 +195,21 @@
   return list(query)
 
 
-def query_by_version(project, ecosystem, version, to_response=bug_to_response):
+def query_by_version(project,
+                     ecosystem,
+                     purl,
+                     version,
+                     to_response=bug_to_response):
   """Query by (fuzzy) version."""
   ecosystem_info = ecosystems.get(ecosystem)
   is_semver = ecosystem_info and ecosystem_info.is_semver
-  query = osv.Bug.query(osv.Bug.status == osv.BugStatus.PROCESSED,
-                        osv.Bug.project == project, osv.Bug.public == True)  # pylint: disable=singleton-comparison
+  if project:
+    query = osv.Bug.query(osv.Bug.status == osv.BugStatus.PROCESSED,
+                          osv.Bug.project == project, osv.Bug.public == True)  # pylint: disable=singleton-comparison
+  elif purl:
+    query = osv.Bug.query(osv.Bug.status == osv.BugStatus.PROCESSED,
+                          osv.Bug.purl == purl, osv.Bug.public == True)  # pylint: disable=singleton-comparison
+
   if ecosystem:
     query = query.filter(osv.Bug.ecosystem == ecosystem)
 
@@ -201,12 +217,12 @@
   if ecosystem:
     if is_semver:
       # Ecosystem supports semver only.
-      bugs.extend(_query_by_semver(query, project, ecosystem, version))
+      bugs.extend(_query_by_semver(query, project, ecosystem, purl, version))
     else:
       bugs.extend(_query_by_generic_version(query, version))
   else:
     # Unspecified ecosystem. Try both.
-    bugs.extend(_query_by_semver(query, project, ecosystem, version))
+    bugs.extend(_query_by_semver(query, project, ecosystem, purl, version))
     bugs.extend(_query_by_generic_version(query, version))
 
   return [to_response(bug) for bug in bugs]

{"golden_diff": "diff --git a/gcp/api/server.py b/gcp/api/server.py\n--- a/gcp/api/server.py\n+++ b/gcp/api/server.py\n@@ -77,9 +77,11 @@\n     if request.query.HasField('package'):\n       package_name = request.query.package.name\n       ecosystem = request.query.package.ecosystem\n+      purl = request.query.package.purl\n     else:\n       package_name = ''\n       ecosystem = ''\n+      purl = ''\n \n     if request.query.WhichOneof('param') == 'commit':\n       bugs = query_by_commit(request.query.commit, to_response=bug_to_response)\n@@ -87,6 +89,7 @@\n       bugs = query_by_version(\n           package_name,\n           ecosystem,\n+          purl,\n           request.query.version,\n           to_response=bug_to_response)\n     else:\n@@ -131,7 +134,8 @@\n   return _get_bugs(bug_ids, to_response=to_response)\n \n \n-def _is_semver_affected(affected_packages, package_name, ecosystem, version):\n+def _is_semver_affected(affected_packages, package_name, ecosystem, purl,\n+                        version):\n   \"\"\"Returns whether or not the given version is within an affected SEMVER\n   range.\"\"\"\n   version = semver_index.parse(version)\n@@ -144,6 +148,9 @@\n     if ecosystem and ecosystem != affected_package.package.ecosystem:\n       continue\n \n+    if purl and purl != affected_package.package.purl:\n+      continue\n+\n     for affected_range in affected_package.ranges:\n       if affected_range.type != 'SEMVER':\n         continue\n@@ -160,7 +167,7 @@\n   return affected\n \n \n-def _query_by_semver(query, package_name, ecosystem, version):\n+def _query_by_semver(query, package_name, ecosystem, purl, version):\n   \"\"\"Query by semver.\"\"\"\n   if not semver_index.is_valid(version):\n     return []\n@@ -170,7 +177,7 @@\n \n   return [\n       bug for bug in query if _is_semver_affected(\n-          bug.affected_packages, package_name, ecosystem, version)\n+          bug.affected_packages, package_name, ecosystem, purl, version)\n   ]\n \n \n@@ -188,12 +195,21 @@\n   return list(query)\n \n \n-def query_by_version(project, ecosystem, version, to_response=bug_to_response):\n+def query_by_version(project,\n+                     ecosystem,\n+                     purl,\n+                     version,\n+                     to_response=bug_to_response):\n   \"\"\"Query by (fuzzy) version.\"\"\"\n   ecosystem_info = ecosystems.get(ecosystem)\n   is_semver = ecosystem_info and ecosystem_info.is_semver\n-  query = osv.Bug.query(osv.Bug.status == osv.BugStatus.PROCESSED,\n-                        osv.Bug.project == project, osv.Bug.public == True)  # pylint: disable=singleton-comparison\n+  if project:\n+    query = osv.Bug.query(osv.Bug.status == osv.BugStatus.PROCESSED,\n+                          osv.Bug.project == project, osv.Bug.public == True)  # pylint: disable=singleton-comparison\n+  elif purl:\n+    query = osv.Bug.query(osv.Bug.status == osv.BugStatus.PROCESSED,\n+                          osv.Bug.purl == purl, osv.Bug.public == True)  # pylint: disable=singleton-comparison\n+\n   if ecosystem:\n     query = query.filter(osv.Bug.ecosystem == ecosystem)\n \n@@ -201,12 +217,12 @@\n   if ecosystem:\n     if is_semver:\n       # Ecosystem supports semver only.\n-      bugs.extend(_query_by_semver(query, project, ecosystem, version))\n+      bugs.extend(_query_by_semver(query, project, ecosystem, purl, version))\n     else:\n       bugs.extend(_query_by_generic_version(query, version))\n   else:\n     # Unspecified ecosystem. Try both.\n-    bugs.extend(_query_by_semver(query, project, ecosystem, version))\n+    bugs.extend(_query_by_semver(query, project, ecosystem, purl, version))\n     bugs.extend(_query_by_generic_version(query, version))\n \n   return [to_response(bug) for bug in bugs]\n", "issue": "Support for packageurl\nSupport to identify package with package url would be nice and it will be easy to integrate with other tool chain.\r\nhttps://github.com/package-url/purl-spec\n", "before_files": [{"content": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"API server implementation.\"\"\"\n\nimport argparse\nfrom concurrent import futures\nimport functools\nimport logging\nimport os\nimport sys\nimport time\n\nfrom google.cloud import ndb\nimport grpc\n\nimport osv\nfrom osv import ecosystems\nfrom osv import semver_index\nimport osv_service_v1_pb2\nimport osv_service_v1_pb2_grpc\n\n_PROJECT = 'oss-vdb'\n_OSS_FUZZ_TRACKER_URL = 'https://bugs.chromium.org/p/oss-fuzz/issues/detail?id='\n\n_SHUTDOWN_GRACE_DURATION = 5\n\n_AUTHORIZATION_HEADER_PREFIX = 'Bearer '\n_EXPECTED_AUDIENCE = 'https://db.oss-fuzz.com'\n\n_ndb_client = ndb.Client()\n\n\ndef ndb_context(func):\n  \"\"\"Wrapper to create an NDB context.\"\"\"\n\n  @functools.wraps(func)\n  def wrapper(*args, **kwargs):\n    with _ndb_client.context():\n      return func(*args, **kwargs)\n\n  return wrapper\n\n\nclass OSVServicer(osv_service_v1_pb2_grpc.OSVServicer):\n  \"\"\"V1 OSV servicer.\"\"\"\n\n  @ndb_context\n  def GetVulnById(self, request, context):\n    \"\"\"Return a `Vulnerability` object for a given OSV ID.\n    \"\"\"\n    bug = osv.Bug.get_by_id(request.id)\n    if not bug or bug.status == osv.BugStatus.UNPROCESSED:\n      context.abort(grpc.StatusCode.NOT_FOUND, 'Bug not found.')\n      return None\n\n    if not bug.public:\n      context.abort(grpc.StatusCode.PERMISSION_DENIED, 'Permission denied.')\n      return None\n\n    return bug_to_response(bug)\n\n  @ndb_context\n  def QueryAffected(self, request, context):\n    \"\"\"Query vulnerabilities for a particular project at a given commit or\n    version.\"\"\"\n    if request.query.HasField('package'):\n      package_name = request.query.package.name\n      ecosystem = request.query.package.ecosystem\n    else:\n      package_name = ''\n      ecosystem = ''\n\n    if request.query.WhichOneof('param') == 'commit':\n      bugs = query_by_commit(request.query.commit, to_response=bug_to_response)\n    elif request.query.WhichOneof('param') == 'version':\n      bugs = query_by_version(\n          package_name,\n          ecosystem,\n          request.query.version,\n          to_response=bug_to_response)\n    else:\n      context.abort(grpc.StatusCode.INVALID_ARGUMENT, 'Invalid query.')\n\n    return osv_service_v1_pb2.VulnerabilityList(vulns=bugs)\n\n  def GetVulnByIdNew(self, request, context):\n    \"\"\"Return a `Vulnerability` object for a given OSV ID.\n    \"\"\"\n    return self.GetVulnById(request, context)\n\n  def QueryAffectedNew(self, request, context):\n    \"\"\"Query vulnerabilities for a particular project at a given commit or\n    version.\"\"\"\n    return self.QueryAffected(request, context)\n\n\ndef bug_to_response(bug):\n  \"\"\"Convert a Bug entity to a response object.\"\"\"\n  return bug.to_vulnerability(v0_7=False, v0_8=True, include_source=True)\n\n\ndef _get_bugs(bug_ids, to_response=bug_to_response):\n  \"\"\"Get bugs from bug ids.\"\"\"\n  bugs = ndb.get_multi([ndb.Key(osv.Bug, bug_id) for bug_id in bug_ids])\n  return [\n      to_response(bug)\n      for bug in bugs\n      if bug and bug.status == osv.BugStatus.PROCESSED\n  ]\n\n\ndef query_by_commit(commit, to_response=bug_to_response):\n  \"\"\"Query by commit.\"\"\"\n  query = osv.AffectedCommit.query(osv.AffectedCommit.commit == commit,\n                                   osv.AffectedCommit.public == True)  # pylint: disable=singleton-comparison\n  bug_ids = []\n  for affected_commit in query:\n    bug_ids.append(affected_commit.bug_id)\n\n  return _get_bugs(bug_ids, to_response=to_response)\n\n\ndef _is_semver_affected(affected_packages, package_name, ecosystem, version):\n  \"\"\"Returns whether or not the given version is within an affected SEMVER\n  range.\"\"\"\n  version = semver_index.parse(version)\n\n  affected = False\n  for affected_package in affected_packages:\n    if package_name and package_name != affected_package.package.name:\n      continue\n\n    if ecosystem and ecosystem != affected_package.package.ecosystem:\n      continue\n\n    for affected_range in affected_package.ranges:\n      if affected_range.type != 'SEMVER':\n        continue\n\n      for event in osv.sorted_events('', affected_range.type,\n                                     affected_range.events):\n        if (event.type == 'introduced' and\n            (event.value == '0' or version >= semver_index.parse(event.value))):\n          affected = True\n\n        if event.type == 'fixed' and version >= semver_index.parse(event.value):\n          affected = False\n\n  return affected\n\n\ndef _query_by_semver(query, package_name, ecosystem, version):\n  \"\"\"Query by semver.\"\"\"\n  if not semver_index.is_valid(version):\n    return []\n\n  query = query.filter(\n      osv.Bug.semver_fixed_indexes > semver_index.normalize(version))\n\n  return [\n      bug for bug in query if _is_semver_affected(\n          bug.affected_packages, package_name, ecosystem, version)\n  ]\n\n\ndef _query_by_generic_version(base_query, version):\n  \"\"\"Query by generic version.\"\"\"\n  # Try without normalizing.\n  query = base_query.filter(osv.Bug.affected_fuzzy == version)\n  results = list(query)\n  if results:\n    return results\n\n  # Try again after normalizing.\n  query = base_query.filter(\n      osv.Bug.affected_fuzzy == osv.normalize_tag(version))\n  return list(query)\n\n\ndef query_by_version(project, ecosystem, version, to_response=bug_to_response):\n  \"\"\"Query by (fuzzy) version.\"\"\"\n  ecosystem_info = ecosystems.get(ecosystem)\n  is_semver = ecosystem_info and ecosystem_info.is_semver\n  query = osv.Bug.query(osv.Bug.status == osv.BugStatus.PROCESSED,\n                        osv.Bug.project == project, osv.Bug.public == True)  # pylint: disable=singleton-comparison\n  if ecosystem:\n    query = query.filter(osv.Bug.ecosystem == ecosystem)\n\n  bugs = []\n  if ecosystem:\n    if is_semver:\n      # Ecosystem supports semver only.\n      bugs.extend(_query_by_semver(query, project, ecosystem, version))\n    else:\n      bugs.extend(_query_by_generic_version(query, version))\n  else:\n    # Unspecified ecosystem. Try both.\n    bugs.extend(_query_by_semver(query, project, ecosystem, version))\n    bugs.extend(_query_by_generic_version(query, version))\n\n  return [to_response(bug) for bug in bugs]\n\n\ndef serve(port):\n  \"\"\"Configures and runs the bookstore API server.\"\"\"\n  server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))\n  osv_service_v1_pb2_grpc.add_OSVServicer_to_server(OSVServicer(), server)\n  server.add_insecure_port('[::]:{}'.format(port))\n  server.start()\n\n  print('Listening on port {}'.format(port))\n  try:\n    while True:\n      time.sleep(3600)\n  except KeyboardInterrupt:\n    server.stop(_SHUTDOWN_GRACE_DURATION)\n\n\ndef main():\n  \"\"\"Entrypoint.\"\"\"\n  logging.basicConfig(stream=sys.stderr)\n  logging.getLogger().setLevel(logging.INFO)\n\n  parser = argparse.ArgumentParser(\n      formatter_class=argparse.RawDescriptionHelpFormatter)\n  parser.add_argument(\n      '--port',\n      type=int,\n      default=None,\n      help='The port to listen on.'\n      'If arg is not set, will listen on the $PORT env var.'\n      'If env var is empty, defaults to 8000.')\n\n  args = parser.parse_args()\n  port = args.port\n  if not port:\n    port = os.environ.get('PORT')\n  if not port:\n    port = 8000\n\n  serve(port)\n\n\nif __name__ == '__main__':\n  main()\n", "path": "gcp/api/server.py"}], "after_files": [{"content": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"API server implementation.\"\"\"\n\nimport argparse\nfrom concurrent import futures\nimport functools\nimport logging\nimport os\nimport sys\nimport time\n\nfrom google.cloud import ndb\nimport grpc\n\nimport osv\nfrom osv import ecosystems\nfrom osv import semver_index\nimport osv_service_v1_pb2\nimport osv_service_v1_pb2_grpc\n\n_PROJECT = 'oss-vdb'\n_OSS_FUZZ_TRACKER_URL = 'https://bugs.chromium.org/p/oss-fuzz/issues/detail?id='\n\n_SHUTDOWN_GRACE_DURATION = 5\n\n_AUTHORIZATION_HEADER_PREFIX = 'Bearer '\n_EXPECTED_AUDIENCE = 'https://db.oss-fuzz.com'\n\n_ndb_client = ndb.Client()\n\n\ndef ndb_context(func):\n  \"\"\"Wrapper to create an NDB context.\"\"\"\n\n  @functools.wraps(func)\n  def wrapper(*args, **kwargs):\n    with _ndb_client.context():\n      return func(*args, **kwargs)\n\n  return wrapper\n\n\nclass OSVServicer(osv_service_v1_pb2_grpc.OSVServicer):\n  \"\"\"V1 OSV servicer.\"\"\"\n\n  @ndb_context\n  def GetVulnById(self, request, context):\n    \"\"\"Return a `Vulnerability` object for a given OSV ID.\n    \"\"\"\n    bug = osv.Bug.get_by_id(request.id)\n    if not bug or bug.status == osv.BugStatus.UNPROCESSED:\n      context.abort(grpc.StatusCode.NOT_FOUND, 'Bug not found.')\n      return None\n\n    if not bug.public:\n      context.abort(grpc.StatusCode.PERMISSION_DENIED, 'Permission denied.')\n      return None\n\n    return bug_to_response(bug)\n\n  @ndb_context\n  def QueryAffected(self, request, context):\n    \"\"\"Query vulnerabilities for a particular project at a given commit or\n    version.\"\"\"\n    if request.query.HasField('package'):\n      package_name = request.query.package.name\n      ecosystem = request.query.package.ecosystem\n      purl = request.query.package.purl\n    else:\n      package_name = ''\n      ecosystem = ''\n      purl = ''\n\n    if request.query.WhichOneof('param') == 'commit':\n      bugs = query_by_commit(request.query.commit, to_response=bug_to_response)\n    elif request.query.WhichOneof('param') == 'version':\n      bugs = query_by_version(\n          package_name,\n          ecosystem,\n          purl,\n          request.query.version,\n          to_response=bug_to_response)\n    else:\n      context.abort(grpc.StatusCode.INVALID_ARGUMENT, 'Invalid query.')\n\n    return osv_service_v1_pb2.VulnerabilityList(vulns=bugs)\n\n  def GetVulnByIdNew(self, request, context):\n    \"\"\"Return a `Vulnerability` object for a given OSV ID.\n    \"\"\"\n    return self.GetVulnById(request, context)\n\n  def QueryAffectedNew(self, request, context):\n    \"\"\"Query vulnerabilities for a particular project at a given commit or\n    version.\"\"\"\n    return self.QueryAffected(request, context)\n\n\ndef bug_to_response(bug):\n  \"\"\"Convert a Bug entity to a response object.\"\"\"\n  return bug.to_vulnerability(v0_7=False, v0_8=True, include_source=True)\n\n\ndef _get_bugs(bug_ids, to_response=bug_to_response):\n  \"\"\"Get bugs from bug ids.\"\"\"\n  bugs = ndb.get_multi([ndb.Key(osv.Bug, bug_id) for bug_id in bug_ids])\n  return [\n      to_response(bug)\n      for bug in bugs\n      if bug and bug.status == osv.BugStatus.PROCESSED\n  ]\n\n\ndef query_by_commit(commit, to_response=bug_to_response):\n  \"\"\"Query by commit.\"\"\"\n  query = osv.AffectedCommit.query(osv.AffectedCommit.commit == commit,\n                                   osv.AffectedCommit.public == True)  # pylint: disable=singleton-comparison\n  bug_ids = []\n  for affected_commit in query:\n    bug_ids.append(affected_commit.bug_id)\n\n  return _get_bugs(bug_ids, to_response=to_response)\n\n\ndef _is_semver_affected(affected_packages, package_name, ecosystem, purl,\n                        version):\n  \"\"\"Returns whether or not the given version is within an affected SEMVER\n  range.\"\"\"\n  version = semver_index.parse(version)\n\n  affected = False\n  for affected_package in affected_packages:\n    if package_name and package_name != affected_package.package.name:\n      continue\n\n    if ecosystem and ecosystem != affected_package.package.ecosystem:\n      continue\n\n    if purl and purl != affected_package.package.purl:\n      continue\n\n    for affected_range in affected_package.ranges:\n      if affected_range.type != 'SEMVER':\n        continue\n\n      for event in osv.sorted_events('', affected_range.type,\n                                     affected_range.events):\n        if (event.type == 'introduced' and\n            (event.value == '0' or version >= semver_index.parse(event.value))):\n          affected = True\n\n        if event.type == 'fixed' and version >= semver_index.parse(event.value):\n          affected = False\n\n  return affected\n\n\ndef _query_by_semver(query, package_name, ecosystem, purl, version):\n  \"\"\"Query by semver.\"\"\"\n  if not semver_index.is_valid(version):\n    return []\n\n  query = query.filter(\n      osv.Bug.semver_fixed_indexes > semver_index.normalize(version))\n\n  return [\n      bug for bug in query if _is_semver_affected(\n          bug.affected_packages, package_name, ecosystem, purl, version)\n  ]\n\n\ndef _query_by_generic_version(base_query, version):\n  \"\"\"Query by generic version.\"\"\"\n  # Try without normalizing.\n  query = base_query.filter(osv.Bug.affected_fuzzy == version)\n  results = list(query)\n  if results:\n    return results\n\n  # Try again after normalizing.\n  query = base_query.filter(\n      osv.Bug.affected_fuzzy == osv.normalize_tag(version))\n  return list(query)\n\n\ndef query_by_version(project,\n                     ecosystem,\n                     purl,\n                     version,\n                     to_response=bug_to_response):\n  \"\"\"Query by (fuzzy) version.\"\"\"\n  ecosystem_info = ecosystems.get(ecosystem)\n  is_semver = ecosystem_info and ecosystem_info.is_semver\n  if project:\n    query = osv.Bug.query(osv.Bug.status == osv.BugStatus.PROCESSED,\n                          osv.Bug.project == project, osv.Bug.public == True)  # pylint: disable=singleton-comparison\n  elif purl:\n    query = osv.Bug.query(osv.Bug.status == osv.BugStatus.PROCESSED,\n                          osv.Bug.purl == purl, osv.Bug.public == True)  # pylint: disable=singleton-comparison\n\n  if ecosystem:\n    query = query.filter(osv.Bug.ecosystem == ecosystem)\n\n  bugs = []\n  if ecosystem:\n    if is_semver:\n      # Ecosystem supports semver only.\n      bugs.extend(_query_by_semver(query, project, ecosystem, purl, version))\n    else:\n      bugs.extend(_query_by_generic_version(query, version))\n  else:\n    # Unspecified ecosystem. Try both.\n    bugs.extend(_query_by_semver(query, project, ecosystem, purl, version))\n    bugs.extend(_query_by_generic_version(query, version))\n\n  return [to_response(bug) for bug in bugs]\n\n\ndef serve(port):\n  \"\"\"Configures and runs the bookstore API server.\"\"\"\n  server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))\n  osv_service_v1_pb2_grpc.add_OSVServicer_to_server(OSVServicer(), server)\n  server.add_insecure_port('[::]:{}'.format(port))\n  server.start()\n\n  print('Listening on port {}'.format(port))\n  try:\n    while True:\n      time.sleep(3600)\n  except KeyboardInterrupt:\n    server.stop(_SHUTDOWN_GRACE_DURATION)\n\n\ndef main():\n  \"\"\"Entrypoint.\"\"\"\n  logging.basicConfig(stream=sys.stderr)\n  logging.getLogger().setLevel(logging.INFO)\n\n  parser = argparse.ArgumentParser(\n      formatter_class=argparse.RawDescriptionHelpFormatter)\n  parser.add_argument(\n      '--port',\n      type=int,\n      default=None,\n      help='The port to listen on.'\n      'If arg is not set, will listen on the $PORT env var.'\n      'If env var is empty, defaults to 8000.')\n\n  args = parser.parse_args()\n  port = args.port\n  if not port:\n    port = os.environ.get('PORT')\n  if not port:\n    port = 8000\n\n  serve(port)\n\n\nif __name__ == '__main__':\n  main()\n", "path": "gcp/api/server.py"}]}